diff options
421 files changed, 13147 insertions, 5736 deletions
diff --git a/apct-tests/perftests/protolog/Android.bp b/apct-tests/perftests/tracing/Android.bp index 08e365be514a..08e365be514a 100644 --- a/apct-tests/perftests/protolog/Android.bp +++ b/apct-tests/perftests/tracing/Android.bp diff --git a/apct-tests/perftests/protolog/AndroidManifest.xml b/apct-tests/perftests/tracing/AndroidManifest.xml index 68125df99ec3..68125df99ec3 100644 --- a/apct-tests/perftests/protolog/AndroidManifest.xml +++ b/apct-tests/perftests/tracing/AndroidManifest.xml diff --git a/apct-tests/perftests/protolog/AndroidTest.xml b/apct-tests/perftests/tracing/AndroidTest.xml index 871a20ce4cef..871a20ce4cef 100644 --- a/apct-tests/perftests/protolog/AndroidTest.xml +++ b/apct-tests/perftests/tracing/AndroidTest.xml diff --git a/apct-tests/perftests/protolog/OWNERS b/apct-tests/perftests/tracing/OWNERS index 3f3308cfc75a..3f3308cfc75a 100644 --- a/apct-tests/perftests/protolog/OWNERS +++ b/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 index 92dd9be7b737..f4759b8bd35c 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(); + private final boolean mLogToProto; + private final boolean mLogToLogcat; - @Parameters(name="logToProto_{0}_logToLogcat_{1}") + /** + * Generates the parameters used for this test class + */ + @Parameters(name = "LOG_TO_{0}") public static Collection<Object[]> params() { - return Arrays.asList(new Object[][] { - { true, true }, - { true, false }, - { false, true }, - { false, false } - }); + var params = new ArrayList<Object[]>(); + + for (LogTo logTo : LogTo.values()) { + params.add(new Object[] { logTo }); + } + + return params; } - private final boolean mLogToProto; - private final boolean mLogToLogcat; + public ProtoLogPerfTest(LogTo logTo) { + mLogToProto = switch (logTo) { + case ALL, PROTO_ONLY -> true; + case LOGCAT_ONLY, NONE -> false; + }; - public ProtoLogPerfTest(boolean logToProto, boolean logToLogcat) { - mLogToProto = logToProto; - mLogToLogcat = logToLogcat; + mLogToLogcat = switch (logTo) { + case ALL, LOGCAT_ONLY -> true; + case PROTO_ONLY, NONE -> false; + }; } @BeforeClass @@ -66,11 +77,11 @@ public class ProtoLogPerfTest { } @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 @@ public class ProtoLogPerfTest { } @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 @@ public class ProtoLogPerfTest { } @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 @@ public class ProtoLogPerfTest { 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 613b7842ae3a..e5389b4f96fb 100644 --- a/apex/jobscheduler/service/aconfig/job.aconfig +++ b/apex/jobscheduler/service/aconfig/job.aconfig @@ -65,3 +65,13 @@ flag { 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 d65a66c83fcc..be8e304a8101 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 @@ public final class JobServiceContext implements ServiceConnection { 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 @@ public final class JobServiceContext implements ServiceConnection { 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 @@ public final class JobServiceContext implements ServiceConnection { 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 @@ public final class JobServiceContext implements ServiceConnection { 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 341be3d53844..533f9f66434b 100644 --- a/api/Android.bp +++ b/api/Android.bp @@ -87,6 +87,7 @@ combined_apis { "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 ed8ab06810f9..4e6dacff290e 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -8756,6 +8756,8 @@ package android.app.appfunctions { 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 @@ package android.app.appfunctions { 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 { diff --git a/core/api/system-current.txt b/core/api/system-current.txt index f26522b9b919..3637ca763e0a 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -6746,6 +6746,14 @@ package android.hardware.soundtrigger { 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/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 8b3ee24db025..e44e7768724e 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -31,6 +31,7 @@ import android.app.admin.IDevicePolicyManager; 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 @@ public final class SystemServiceRegistry { @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/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig index 29a5048daae6..edbbd5b22ddd 100644 --- a/core/java/android/app/admin/flags/flags.aconfig +++ b/core/java/android/app/admin/flags/flags.aconfig @@ -105,6 +105,14 @@ flag { 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" @@ -139,7 +147,6 @@ flag { bug: "293441361" } -# Fully rolled out and must not be used. flag { name: "assist_content_user_restriction_enabled" is_exported: true @@ -166,7 +173,6 @@ flag { bug: "304999634" } -# Fully rolled out and must not be used. flag { name: "esim_management_enabled" is_exported: true diff --git a/core/java/android/app/appfunctions/AppFunctionManager.java b/core/java/android/app/appfunctions/AppFunctionManager.java index b6240a79dda8..0ee902632f5f 100644 --- a/core/java/android/app/appfunctions/AppFunctionManager.java +++ b/core/java/android/app/appfunctions/AppFunctionManager.java @@ -50,8 +50,7 @@ public final class AppFunctionManager { * 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 @@ public final class AppFunctionManager { } 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 000000000000..e4784b4ef69d --- /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 000000000000..3169f0e48877 --- /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 000000000000..fdd12b0b49d3 --- /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 6259d165d132..22bc9088f5e4 100644 --- a/core/java/android/app/appfunctions/AppFunctionService.java +++ b/core/java/android/app/appfunctions/AppFunctionService.java @@ -81,8 +81,9 @@ public abstract class AppFunctionService extends Service { // 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 class AppFunctionService extends Service { 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 000000000000..6d4172aff8b3 --- /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 9fb3375a2446..58d4088f733e 100644 --- a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java +++ b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java @@ -162,6 +162,55 @@ public final class ExecuteAppFunctionResponse implements Parcelable { } /** + * 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 @@ public final class ExecuteAppFunctionResponse implements Parcelable { @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 000000000000..afc549517abe --- /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 ee9114f74092..93d62cfeb537 100644 --- a/core/java/android/companion/flags.aconfig +++ b/core/java/android/companion/flags.aconfig @@ -10,13 +10,6 @@ flag { } 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 d7a517a2c067..ffed5366d31b 100644 --- a/core/java/android/content/AttributionSource.java +++ b/core/java/android/content/AttributionSource.java @@ -629,11 +629,10 @@ public final class AttributionSource implements Parcelable { * 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 final class AttributionSource implements Parcelable { */ 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 @@ public final class AttributionSource implements Parcelable { 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 final class AttributionSource implements Parcelable { */ public @NonNull Builder setPid(int value) { checkNotUsed(); - mBuilderFieldsSet |= 0x2; mAttributionSourceState.pid = value; return this; } @@ -677,7 +685,6 @@ public final class AttributionSource implements Parcelable { */ public @NonNull Builder setPackageName(@Nullable String value) { checkNotUsed(); - mBuilderFieldsSet |= 0x4; mAttributionSourceState.packageName = value; return this; } @@ -687,7 +694,6 @@ public final class AttributionSource implements Parcelable { */ public @NonNull Builder setAttributionTag(@Nullable String value) { checkNotUsed(); - mBuilderFieldsSet |= 0x8; mAttributionSourceState.attributionTag = value; return this; } @@ -718,11 +724,11 @@ public final class AttributionSource implements Parcelable { */ @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 @@ public final class AttributionSource implements Parcelable { @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 final class AttributionSource implements Parcelable { */ 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 @@ public final class AttributionSource implements Parcelable { if (value == null) { throw new IllegalArgumentException("Null AttributionSource not permitted."); } - mBuilderFieldsSet |= 0x40; mAttributionSourceState.next = new AttributionSourceState[]{value.mAttributionSourceState}; return this; @@ -769,28 +772,7 @@ public final class AttributionSource implements Parcelable { /** 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 @@ public final class AttributionSource implements Parcelable { } 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 109b0a8be7f8..6a96a54d93ba 100644 --- a/core/java/android/hardware/display/BrightnessInfo.java +++ b/core/java/android/hardware/display/BrightnessInfo.java @@ -158,6 +158,8 @@ public final class BrightnessInfo implements Parcelable { 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 03cf7c5f926c..2a362381a003 100644 --- a/core/java/android/hardware/input/InputManagerGlobal.java +++ b/core/java/android/hardware/input/InputManagerGlobal.java @@ -567,7 +567,7 @@ public final class InputManagerGlobal { 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/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java index bfff4dbdd627..9f3e3ad8c01e 100644 --- a/core/java/android/hardware/soundtrigger/SoundTrigger.java +++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java @@ -29,6 +29,7 @@ import static android.system.OsConstants.EPIPE; 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 @@ public class SoundTrigger { /***************************************************************************** * 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 @@ public class SoundTrigger { } }; + /** + * 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 @@ public class SoundTrigger { } @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 92b630f3063a..80f39bfbdc21 100644 --- a/core/java/android/os/Handler.java +++ b/core/java/android/os/Handler.java @@ -46,13 +46,13 @@ import java.lang.reflect.Modifier; * 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 @@ public class Handler { */ 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 @@ public class Handler { * 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 @@ public class Handler { /** * 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 @@ public class Handler { { 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 @@ public class Handler { } /** - * + * * 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 @@ public class Handler { { 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 @@ public class Handler { /** * 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 @@ public class Handler { * @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 class Handler { 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 @@ public class Handler { * {@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 @@ public class Handler { * 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 class Handler { 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 @@ public class Handler { * <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 @@ public class Handler { * 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 @@ public class Handler { /** * 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 @@ public class Handler { /** * 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 @@ public class Handler { } /** - * 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 @@ public class Handler { * 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 @@ public class Handler { * 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 @@ public class Handler { * <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 @@ public class Handler { /** * 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 @@ public class Handler { } // 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 a1db9be0b693..702fdc2bbaa6 100644 --- a/core/java/android/os/Message.java +++ b/core/java/android/os/Message.java @@ -41,6 +41,9 @@ public final class Message implements Parcelable { * 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 c801fabf9427..46ae9d8682ee 100644 --- a/core/java/android/os/SharedMemory.java +++ b/core/java/android/os/SharedMemory.java @@ -379,6 +379,14 @@ public final class SharedMemory implements Parcelable, Closeable { 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/util/Half.java b/core/java/android/util/Half.java index fe536a6e4e68..22583acb75ce 100644 --- a/core/java/android/util/Half.java +++ b/core/java/android/util/Half.java @@ -19,6 +19,7 @@ package android.util; 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 @@ import libcore.util.FP16; * <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 c7e93c19484f..b80146505a1b 100644 --- a/core/java/android/view/ImeBackAnimationController.java +++ b/core/java/android/view/ImeBackAnimationController.java @@ -149,15 +149,17 @@ public class ImeBackAnimationController implements OnBackAnimationCallback { 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 @@ public class ImeBackAnimationController implements OnBackAnimationCallback { 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 @@ public class ImeBackAnimationController implements OnBackAnimationCallback { 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 @@ public class ImeBackAnimationController implements OnBackAnimationCallback { 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 6343313b2e01..e90b1c0fc167 100644 --- a/core/java/android/view/ImeInsetsSourceConsumer.java +++ b/core/java/android/view/ImeInsetsSourceConsumer.java @@ -70,7 +70,14 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer { "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 7c8cd932f737..8fdf91a2d87c 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -1197,7 +1197,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation 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 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation 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 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation // 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 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation 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 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation // 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 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation 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 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } } 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 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation */ 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 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation 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 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation 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 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation 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 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation 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/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 0e1625aaedd8..f021bdfe478f 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -6094,6 +6094,12 @@ public final class ViewRootImpl implements ViewParent, } 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 a4cea3364998..ab29df357268 100644 --- a/core/java/android/view/accessibility/AccessibilityManager.java +++ b/core/java/android/view/accessibility/AccessibilityManager.java @@ -1051,6 +1051,52 @@ public final class AccessibilityManager { } /** + * 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 72a1fe424906..bf79a2c6c6ea 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.IAccessibilityInteractionConnection; 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 @@ interface IAccessibilityManager { @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 000000000000..fe6c8e25dd00 --- /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 defe61e506af..b21a490cc506 100644 --- a/core/java/android/webkit/flags.aconfig +++ b/core/java/android/webkit/flags.aconfig @@ -9,3 +9,12 @@ flag { 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 58b5757a47bb..b8a11cf063e2 100644 --- a/core/java/android/window/ITaskFragmentOrganizerController.aidl +++ b/core/java/android/window/ITaskFragmentOrganizerController.aidl @@ -47,6 +47,19 @@ interface ITaskFragmentOrganizerController { 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 027d323bfcd1..4cc0d8a77a2b 100644 --- a/core/java/android/window/TaskFragmentOrganizer.java +++ b/core/java/android/window/TaskFragmentOrganizer.java @@ -34,6 +34,7 @@ import android.app.Activity; 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 @@ public class TaskFragmentOrganizer extends WindowOrganizer { } /** + * 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 6ce9725f95b0..cd31850b281c 100644 --- a/core/java/android/window/flags/responsible_apis.aconfig +++ b/core/java/android/window/flags/responsible_apis.aconfig @@ -71,3 +71,11 @@ flag { 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/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java index e14249cc028b..e0529b339d4a 100644 --- a/core/java/com/android/internal/policy/PhoneWindow.java +++ b/core/java/com/android/internal/policy/PhoneWindow.java @@ -3333,6 +3333,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { 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 f8a143693c83..1938cdb0ba84 100644 --- a/core/java/com/android/internal/statusbar/StatusBarIcon.java +++ b/core/java/com/android/internal/statusbar/StatusBarIcon.java @@ -51,6 +51,19 @@ public class StatusBarIcon implements Parcelable { 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 class StatusBarIcon implements Parcelable { 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 @@ public class StatusBarIcon implements Parcelable { @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 @@ public class StatusBarIcon implements Parcelable { 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 @@ public class StatusBarIcon implements Parcelable { @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 @@ public class StatusBarIcon implements Parcelable { 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 @@ public class StatusBarIcon implements Parcelable { 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 2abdd57662eb..90cb10aa62b2 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -108,6 +108,7 @@ cc_library_shared_for_libandroid_runtime { "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 638591f130ab..46710b5d3edc 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 @@ static jboolean android_media_AudioSystem_isBluetoothVariableLatencyEnabled(JNIE 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; + } + } + } } + }) {} + + 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; } - }); + } } - ~JavaSystemPropertyListener() { - mCleanupSignal.store(true); - mListenerThread.join(); - JNIEnv* env = GetOrAttachJNIEnvironment(gVm); - env->DeleteGlobalRef(mCallback); + private: + void fireUpdate() { + const auto threadEnv = GetOrAttachJNIEnvironment(gVm); + threadEnv->CallVoidMethod(mCallback.get(), gRunnableClassInfo.run); } - private: - jobject mCallback; - android::base::CachedProperty mCachedProperty; - std::thread mListenerThread; - std::atomic<bool> mCleanupSignal{false}; - std::string mLastVal = ""; + // 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 @@ static void android_media_AudioSystem_listenForSystemPropertyChange(JNIEnv *env, 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 @@ static const JNINativeMethod gMethods[] = 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/tests/batterystatstests/BatteryStatsViewer/AndroidManifest.xml b/core/tests/batterystatstests/BatteryStatsViewer/AndroidManifest.xml index 94bde68fdf11..127dbfd25b09 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 000000000000..c23c1484cf59 --- /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 000000000000..b01648838695 --- /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 4d9b591c0990..00ffda867d6a 100644 --- a/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java +++ b/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java @@ -254,11 +254,8 @@ public class ImeBackAnimationControllerTest { 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 @@ public class ImeBackAnimationControllerTest { 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 @@ public class ImeBackAnimationControllerTest { // 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 @@ public class ImeBackAnimationControllerTest { 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 ce7e85868e8c..bec8b1f76394 100644 --- a/core/tests/coretests/src/android/view/InsetsControllerTest.java +++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java @@ -1022,7 +1022,7 @@ public class InsetsControllerTest { } @Test - public void testImeRequestedVisibleDuringPredictiveBackAnim() { + public void testImeRequestedVisibleDuringPredictiveBackAnimWithoutCallback() { prepareControls(); InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { // show ime as initial state @@ -1051,6 +1051,42 @@ public class InsetsControllerTest { } @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 @@ public class InsetsControllerTest { }); } + @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/statusbar/StatusBarIconTest.java b/core/tests/coretests/src/com/android/internal/statusbar/StatusBarIconTest.java index b183ecb50591..149e132a0df4 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 static com.google.common.truth.Truth.assertThat; 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 @@ public class StatusBarIconTest { 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 @@ public class StatusBarIconTest { 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 000000000000..7dfbe2cced34 --- /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 a99e20101c3b..683f6149a203 100644 --- a/graphics/java/android/graphics/Matrix44.java +++ b/graphics/java/android/graphics/Matrix44.java @@ -19,6 +19,7 @@ package android.graphics; 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 @@ import java.util.Arrays; * 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 618e6dcc4433..c7b89412cc47 100644 --- a/graphics/java/android/graphics/Outline.java +++ b/graphics/java/android/graphics/Outline.java @@ -21,6 +21,7 @@ import android.annotation.IntDef; 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 @@ import java.lang.annotation.RetentionPolicy; * @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 748d66cb5f6c..76c1715475ae 100644 --- a/graphics/java/android/graphics/ParcelableColorSpace.java +++ b/graphics/java/android/graphics/ParcelableColorSpace.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; 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 @@ import android.os.Parcelable; * {@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 3ec5b9cc7dae..a872e03db3b5 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 1eb95c1efb08..9ea2943bc6da 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 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { @NonNull private final TaskFragmentCallback mCallback; + @VisibleForTesting + @Nullable + TaskFragmentAnimationController mAnimationController; + /** * Callback that notifies the controller about changes to task fragments. */ @@ -87,6 +91,25 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { 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 24b56ae471e5..5657647ff5c0 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 @@ import java.util.function.BiConsumer; 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. 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 563732027a6c..abc7b291fc32 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 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { 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 000000000000..33220c44a3b5 --- /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 000000000000..d7eb9a01f57c --- /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 000000000000..d9b73a8290f5 --- /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 000000000000..1f866c3b99c9 --- /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 8911d18b9b97..ac004c301598 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 androidx.window.extensions.embedding.EmbeddingTestUtils.createTest 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 @@ public class JetpackTaskFragmentOrganizerTest { } @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 000000000000..a1e9f08585f6 --- /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 index 9999f08ee03c..b92b8ef657a3 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 @@ public class RegionSamplingHelper implements View.OnAttachStateChangeListener, 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/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java index 3e758bbad29b..4622dcffb3cc 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 @@ public class Bubble implements BubbleViewProvider { 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 829af08e612a..e873cbd6341d 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 @@ public class BubbleInfo implements Parcelable { @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 @@ public class BubbleInfo implements Parcelable { mTitle = title; mAppName = appName; mIsImportantConversation = isImportantConversation; + mShowAppBadge = showAppBadge; } private BubbleInfo(Parcel source) { @@ -73,6 +75,7 @@ public class BubbleInfo implements Parcelable { mTitle = source.readString(); mAppName = source.readString(); mIsImportantConversation = source.readBoolean(); + mShowAppBadge = source.readBoolean(); } public String getKey() { @@ -115,6 +118,10 @@ public class BubbleInfo implements Parcelable { return mIsImportantConversation; } + public boolean showAppBadge() { + return mShowAppBadge; + } + /** * Whether this bubble is currently being hidden from the stack. */ @@ -172,6 +179,7 @@ public class BubbleInfo implements Parcelable { parcel.writeString(mTitle); parcel.writeString(mAppName); parcel.writeBoolean(mIsImportantConversation); + parcel.writeBoolean(mShowAppBadge); } @NonNull 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 90f8276240a7..4b30ed0dfa7c 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 @@ class DesktopTasksController( * 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 @@ class DesktopTasksController( 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 4c5258f2bfcd..f4df42cde10f 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 @@ class ReturnToDragStartAnimator( } /** 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 @@ class ReturnToDragStartAnimator( .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/pip2/animation/PipEnterExitAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterExitAnimator.java index 8a9302bcfc98..8ebdc96c21a3 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.animation.ValueAnimator; 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 @@ public class PipEnterExitAnimator extends ValueAnimator @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 @@ public class PipEnterExitAnimator extends ValueAnimator 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 @@ public class PipEnterExitAnimator extends ValueAnimator @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 class PipEnterExitAnimator extends ValueAnimator 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 7f168800fb29..262c14d2bfe3 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.graphics.Rect; 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 @@ public class PipTaskListener implements ShellTaskOrganizer.TaskListener, : 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 44baabdd5e2e..f93233ec7461 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.content.Context; 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 @@ public class PipTransition extends PipTransitionController implements 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 @@ public class PipTransition extends PipTransitionController implements 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/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index ac35459347c6..c88c1e28b011 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 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } 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 000000000000..5a69b27d9fbb --- /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 0f0d2df4337c..5f759e8ee682 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 @@ import org.junit.runners.BlockJUnit4ClassRunner @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 533be8895fb9..b616e5347ac9 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 @@ import org.junit.runners.BlockJUnit4ClassRunner @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 b85d7936efc2..a9ed13a099f3 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.Test 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 @@ open class AutoEnterPipOnGoToHomeTest(flicker: LegacyFlickerTest) : EnterPipTran 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 @@ open class AutoEnterPipOnGoToHomeTest(flicker: LegacyFlickerTest) : EnterPipTran } } + /** 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 @@ open class AutoEnterPipOnGoToHomeTest(flicker: LegacyFlickerTest) : EnterPipTran 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 70be58f06548..5b7521a37a6c 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.junit.FlickerParametersRunnerFactory 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 @@ class PipAspectRatioChangeTest(flicker: LegacyFlickerTest) : PipTransition(flick 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 90b9798c6329..cbd4a528474a 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 @@ class PipDragThenSnapTest(flicker: LegacyFlickerTest) : PipTransition(flicker) { 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 ed2a0a718c6c..578a9b536289 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 @@ open class SetRequestedOrientationWhilePinned(flicker: LegacyFlickerTest) : PipT @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 8cb81b46cf4d..f57335c2081b 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 @@ abstract class ClosePipTransition(flicker: LegacyFlickerTest) : PipTransition(fl } } + @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 0742cf9c5887..ce84eb644042 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 @@ abstract class ExitPipToAppTransition(flicker: LegacyFlickerTest) : PipTransitio } } + @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 99c1ad2aaa4e..bc2bfdbe1df1 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 @@ package com.android.wm.shell.flicker.pip.common 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 @@ abstract class PipTransition(flicker: LegacyFlickerTest) : BaseTest(flicker) { .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 499870220190..f31722d3c1a5 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 @@ package com.android.wm.shell; 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 @@ public class TestShellExecutor implements ShellExecutor { 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 5b22eddcb6ee..6695a1e56567 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 @@ class BubbleInfoTest : ShellTestCase() { "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 5474e539f286..10557dd9b439 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.isA 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 @@ class DesktopTasksControllerTest : ShellTestCase() { } @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 @@ class DesktopTasksControllerTest : ShellTestCase() { 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 @@ class DesktopTasksControllerTest : ShellTestCase() { 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 index 2a6754cbdd1a..d3e291f7dd1f 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 android.view.ViewRootImpl 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.never 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 @@ class RegionSamplingHelperTest : SysuiTestCase() { 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 @@ class RegionSamplingHelperTest : SysuiTestCase() { 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 @@ class RegionSamplingHelperTest : SysuiTestCase() { @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 0b5c6784b73d..be0549b6655d 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.anyInt 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 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { @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 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { 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 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { @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 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { 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 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { 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 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { 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 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { @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 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { 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 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { 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 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { 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 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { 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/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index a255f730b0f3..ebdfd3e41aa1 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -2655,7 +2655,16 @@ public class AudioSystem /** * 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/java/android/nfc/INfcAdapter.aidl b/nfc/java/android/nfc/INfcAdapter.aidl index 6c0f93354683..e2ec95215d1a 100644 --- a/nfc/java/android/nfc/INfcAdapter.aidl +++ b/nfc/java/android/nfc/INfcAdapter.aidl @@ -62,7 +62,7 @@ interface INfcAdapter 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 @@ interface INfcAdapter 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/NfcActivityManager.java b/nfc/java/android/nfc/NfcActivityManager.java index 0eb846d6c72a..909eca7b0c9d 100644 --- a/nfc/java/android/nfc/NfcActivityManager.java +++ b/nfc/java/android/nfc/NfcActivityManager.java @@ -236,7 +236,8 @@ public final class NfcActivityManager extends IAppCallback.Stub 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 @@ public final class NfcActivityManager extends IAppCallback.Stub 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 525e2c58a82c..22ae612b5cfc 100644 --- a/nfc/java/android/nfc/NfcAdapter.java +++ b/nfc/java/android/nfc/NfcAdapter.java @@ -1731,7 +1731,8 @@ public final class NfcAdapter { } 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 @@ public final class NfcAdapter { || (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/packages/SettingsLib/ActionButtonsPreference/res/drawable/half_rounded_left_bk.xml b/packages/SettingsLib/ActionButtonsPreference/res/drawable/half_rounded_left_bk.xml index 8a25726ecd17..0d4ef3c52e4c 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 7e626e50255a..307277264ff6 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 9f4980beb76f..f1790f9ba351 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 67b5107576a7..f0da7b4f457f 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/src/com/android/settingslib/notification/modes/ZenIconKeys.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenIconKeys.java index 0a0b65b50e8b..79dabf029c49 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 @@ class ZenIconKeys { * 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 fe0f98af1186..43c6c5050850 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 android.util.LruCache; 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 @@ public class ZenIconLoader { 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 e64b0c6d8e74..14b0c252aff5 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 @@ public class ZenModeTest { } @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 @@ public class ZenModeTest { 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 c1bb55c70073..d26a9066e075 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -949,36 +949,37 @@ android_robolectric_test { 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/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index df4b51a9b7b9..1ce171609e5b 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -363,6 +363,13 @@ flag { } 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" @@ -373,6 +380,17 @@ flag { } 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 " @@ -588,16 +606,6 @@ flag { } 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/compose/facade/enabled/src/com/android/systemui/scene/NotificationsShadeOverlayModule.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/NotificationsShadeOverlayModule.kt new file mode 100644 index 000000000000..e55520a09103 --- /dev/null +++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/NotificationsShadeOverlayModule.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.scene + +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 + +@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 04bcc3624532..c60e11e585f2 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 @@ fun AlternateBouncer( 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 000000000000..37888f29ab6b --- /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/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 1db96cfd7c53..c9b801319b7f 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 @@ constructor( 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 @@ constructor( 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 @@ constructor( 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 072e91a25444..d4f3b5b6d6a6 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.ExperimentalAnimationApi 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 @@ fun ColumnVolumeSliders( modifier: Modifier = Modifier, ) { require(viewModels.isNotEmpty()) - val transition = updateTransition(isExpanded, label = "CollapsableSliders") Column(modifier = modifier) { Box( modifier = Modifier.fillMaxWidth(), @@ -106,8 +104,9 @@ fun ColumnVolumeSliders( 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 @@ fun ColumnVolumeSliders( 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 5ffb6f82fbba..1cc0fb2aad9b 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.foundation.layout.padding 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 const val VolumePanelTestTag = "VolumePanel" private val padding = 24.dp @Composable -@OptIn(ExperimentalComposeUiApi::class) fun VolumePanelRoot( viewModel: VolumePanelViewModel, modifier: Modifier = Modifier, @@ -54,18 +53,20 @@ fun VolumePanelRoot( 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/Key.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt index 3f8f5e742079..ced177ccb9a0 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 @@ class TransitionKey(debugName: String, identity: Any = Object()) : Key(debugName 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/PredictiveBackHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt index be4fea10602f..e9300119924e 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,7 +18,7 @@ package com.android.compose.animation.scene 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 @@ -43,7 +43,9 @@ internal fun PredictiveBackHandler( 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,7 +62,7 @@ private suspend fun <T : ContentKey> animate( 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 ) { @@ -70,12 +72,7 @@ private suspend fun <T : ContentKey> animate( 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, ) } @@ -84,9 +81,15 @@ private suspend fun <T : ContentKey> animate( progress.collect { backEvent -> animation.dragOffset = backEvent.progress } // Back gesture successful. - animateOffset(animation.toContent) + animateOffset( + animation.toContent, + animation.contentTransition.transformationSpec.progressSpec + ) } catch (e: CancellationException) { // Back gesture cancelled. + // If the back gesture is cancelled, the progress is animated back to 0f by the system. + // Since the remaining change in progress is usually very small, the progressSpec is omitted + // and the default spring spec used instead. animateOffset(animation.fromContent) } } 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 b3f74f749a0e..e4534302267a 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 @@ sealed class UserActionResult( ) { 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 @@ sealed class UserActionResult( } /** 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 @@ sealed class UserActionResult( * 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 } 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 cefcff75f13a..e65ed9b7dc97 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 @@ internal constructor( 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 57ff597d7314..be9c5670ceae 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 @@ -320,7 +320,7 @@ internal class SwipeAnimation<T : ContentKey>( fun animateOffset( initialVelocity: Float, targetContent: T, - spec: SpringSpec<Float>? = null, + spec: AnimationSpec<Float>? = null, ): OffsetAnimation { val initialProgress = progress // Skip the animation if we have already reached the target content and the overscroll does 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 c5b6cdf12385..9284ffddcee3 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 @@ package com.android.compose.animation.scene 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 @@ class PredictiveBackHandlerTest { @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 @@ class PredictiveBackHandlerTest { 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/TransitionDslTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt index bed6cefa459d..f8068e612f11 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 @@ class TransitionDslTest { } @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/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt index 7707a600c691..5f6ea1c0eb43 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.util.LatencyTracker 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.never 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 @@ import com.android.systemui.Flags as AconfigFlags 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 @@ class KeyguardPasswordViewControllerTest : SysuiTestCase() { 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 @@ class KeyguardPasswordViewControllerTest : SysuiTestCase() { fakeFeatureFlags, mSelectedUserInteractor, keyguardKeyboardInteractor, + null, ) } @@ -187,9 +189,11 @@ class KeyguardPasswordViewControllerTest : SysuiTestCase() { 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 @@ class KeyguardPasswordViewControllerTest : SysuiTestCase() { 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 0054d137bd2c..2af3b00ed95a 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 @@ public class KeyguardPinBasedInputViewControllerTest extends SysuiTestCase { 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 f7f69d373959..fabc357c2a68 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.flags.Flags 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 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { featureFlags, mSelectedUserInteractor, keyguardKeyboardInteractor, + null, ) kosmos = testKosmos() @@ -279,7 +281,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { 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 d244482c05ee..58c3fec5b45e 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 @@ package com.android.systemui.ambient.touch; 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.ext.junit.runners.AndroidJUnit4; 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.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import java.util.Collections; import java.util.Optional; @SmallTest @@ -106,15 +106,13 @@ public class BouncerFullscreenSwipeTouchHandlerTest extends SysuiTestCase { 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 class BouncerFullscreenSwipeTouchHandlerTest extends SysuiTestCase { public void setup() { mKosmos = new KosmosJavaAdapter(this); MockitoAnnotations.initMocks(this); - mUserTracker = new FakeUserTracker(); mTouchHandler = new BouncerSwipeTouchHandler( mKosmos.getTestScope(), mScrimManager, @@ -138,24 +135,21 @@ public class BouncerFullscreenSwipeTouchHandlerTest extends SysuiTestCase { 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 b85e32b381df..95681671b545 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 @@ package com.android.systemui.ambient.touch; 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.ext.junit.runners.AndroidJUnit4; 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.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import java.util.Collections; import java.util.Optional; @SmallTest @@ -116,9 +116,6 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { UiEventLogger mUiEventLogger; @Mock - LockPatternUtils mLockPatternUtils; - - @Mock ActivityStarter mActivityStarter; @Mock @@ -127,11 +124,12 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { @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 class BouncerSwipeTouchHandlerTest extends SysuiTestCase { public void setup() { mKosmos = new KosmosJavaAdapter(this); MockitoAnnotations.initMocks(this); - mUserTracker = new FakeUserTracker(); mTouchHandler = new BouncerSwipeTouchHandler( mKosmos.getTestScope(), mScrimManager, @@ -156,24 +153,21 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { 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 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { */ @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 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { */ @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/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt index 99fcbb854e3a..777ddab4e259 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.domain.interactor.sceneInteractor 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 @@ class CommunalInteractorTest : SysuiTestCase() { 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 @@ class CommunalInteractorTest : SysuiTestCase() { 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 @@ class CommunalInteractorTest : SysuiTestCase() { } } + @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/dreams/DreamOverlayServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt index d21a8270ae54..5e6ff73e5e5e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt @@ -1139,6 +1139,39 @@ class DreamOverlayServiceTest : SysuiTestCase() { } @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 3b8ffcd91c48..17e3006812ca 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 @@ class FromAlternateBouncerTransitionInteractorTest : SysuiTestCase() { 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 9273dcef0de0..33f3cd4ac167 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 @@ class FromAodTransitionInteractorTest : SysuiTestCase() { 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 6708ffa2a091..12039c135985 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 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() { 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 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() { 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 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() { 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 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() { 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 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() { 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 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() { 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 3e1f4f6da5e4..3b2b12c4363d 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 @@ class KeyguardRootViewModelTest(flags: FlagsParameterization) : SysuiTestCase() } @Test + @DisableSceneContainer fun alpha_transitionBetweenHubAndDream_isZero() = testScope.runTest { val alpha by collectLastValue(underTest.alpha(viewState)) @@ -388,8 +389,8 @@ class KeyguardRootViewModelTest(flags: FlagsParameterization) : SysuiTestCase() 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 000000000000..cdba4dbfe921 --- /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 5925819f27a7..a18f450cdc9e 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.DisableFlags 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.data.repository.fakeZenModeReposito 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 @@ class ModesTileDataInteractorTest : SysuiTestCase() { @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 @@ class ModesTileDataInteractorTest : SysuiTestCase() { @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)) ) + + // Tile starts with the generic Modes icon. runCurrent() - assertThat(dataList.map { it.icon }).containsExactly(null).inOrder() + assertThat(tileData?.icon).isEqualTo(MODES_ICON) + assertThat(tileData?.iconResId).isEqualTo(MODES_DRAWABLE_ID) - // Add an inactive mode: state hasn't changed, so this shouldn't cause another emission + // 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 @@ class ModesTileDataInteractorTest : SysuiTestCase() { 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 4b7564981855..cd5812710292 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 @@ class ModesTileUserActionInteractorTest : SysuiTestCase() { 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 @@ class ModesTileUserActionInteractorTest : SysuiTestCase() { 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 @@ class ModesTileUserActionInteractorTest : SysuiTestCase() { @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 @@ class ModesTileUserActionInteractorTest : SysuiTestCase() { @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 a41f15d8ff82..f7bdcb8086ef 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 @@ package com.android.systemui.qs.tiles.impl.modes.ui 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 @@ class ModesTileMapperTest : SysuiTestCase() { @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.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 e2149d907688..424afe171f96 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.EnableSceneContainer 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 @@ import org.mockito.Mockito.verify @RunWith(AndroidJUnit4::class) @RunWithLooper @EnableSceneContainer +@DisableFlags(com.android.systemui.Flags.FLAG_DUAL_SHADE) class QuickSettingsSceneContentViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() @@ -64,6 +72,8 @@ class QuickSettingsSceneContentViewModelTest : SysuiTestCase() { 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 @@ class QuickSettingsSceneContentViewModelTest : SysuiTestCase() { footerActionsViewModelFactory = footerActionsViewModelFactory, footerActionsController = footerActionsController, mediaCarouselInteractor = kosmos.mediaCarouselInteractor, + shadeInteractor = shadeInteractor, + sceneInteractor = sceneInteractor, ) + underTest.activateIn(testScope) } @Test @@ -122,4 +135,16 @@ class QuickSettingsSceneContentViewModelTest : SysuiTestCase() { 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 45a8b3e968cd..db58c8500768 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.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest -import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -64,14 +63,10 @@ class QuickSettingsShadeSceneActionsViewModelTest : SysuiTestCase() { 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 @@ class QuickSettingsShadeSceneActionsViewModelTest : SysuiTestCase() { @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 @@ class QuickSettingsShadeSceneActionsViewModelTest : SysuiTestCase() { @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 @@ class QuickSettingsShadeSceneActionsViewModelTest : SysuiTestCase() { 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 @@ class QuickSettingsShadeSceneActionsViewModelTest : SysuiTestCase() { 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 @@ class QuickSettingsShadeSceneActionsViewModelTest : SysuiTestCase() { @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 @@ class QuickSettingsShadeSceneActionsViewModelTest : SysuiTestCase() { @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 @@ class QuickSettingsShadeSceneActionsViewModelTest : SysuiTestCase() { @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 @@ class QuickSettingsShadeSceneActionsViewModelTest : SysuiTestCase() { @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 dd4432dd9797..900f2a464588 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 com.google.common.truth.Truth.assertThat 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 @@ class SceneActionsViewModelTest : SysuiTestCase() { 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/policy/domain/interactor/ZenModeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt index 20d3a7b6b7b5..639d34d5e74d 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 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 @@ class ZenModeInteractorTest : SysuiTestCase() { } @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 33f379d020b7..a0f64314098c 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 android.provider.Settings 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.mockModesDialogDelegate 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 @@ class ModesDialogViewModelTest : SysuiTestCase() { mockDialogEventLogger ) - @Before - fun setUp() { - ZenIconLoader.setInstance(ZenIconLoader(MoreExecutors.newDirectExecutorService())) - } - @Test fun tiles_filtersOutUserDisabledModes() = testScope.runTest { @@ -338,6 +330,83 @@ class ModesDialogViewModelTest : SysuiTestCase() { } @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 0f56d0bcc7eb..fa7f37c7ba16 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 @@ class MediaOutputComponentInteractorTest : SysuiTestCase() { assertThat(model) .isEqualTo( MediaOutputComponentModel.Calling( - AudioOutputDevice.BuiltIn(builtInDeviceName, testIcon), - false, + device = AudioOutputDevice.BuiltIn(builtInDeviceName, testIcon), + isInAudioSharing = false, + canOpenAudioSwitcher = false, ) ) } @@ -101,6 +102,9 @@ class MediaOutputComponentInteractorTest : SysuiTestCase() { 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 @@ class MediaOutputComponentInteractorTest : SysuiTestCase() { assertThat(device) .isEqualTo(AudioOutputDevice.BuiltIn("built_in_media", testIcon)) assertThat(isInAudioSharing).isFalse() + assertThat(canOpenAudioSwitcher).isTrue() } } } @@ -129,8 +134,9 @@ class MediaOutputComponentInteractorTest : SysuiTestCase() { 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 e6cc6cf766c6..1307301774c8 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> @@ -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/systemui/statusbar/pipeline/mobile/data/model/ServiceStateModel.kt b/packages/SystemUI/src/com/android/keyguard/AuthInteractionProperties.kt index cce3eb02023b..efa13c632087 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ServiceStateModel.kt +++ b/packages/SystemUI/src/com/android/keyguard/AuthInteractionProperties.kt @@ -14,18 +14,16 @@ * limitations under the License. */ -package com.android.systemui.statusbar.pipeline.mobile.data.model +package com.android.keyguard -import android.telephony.ServiceState +import android.os.VibrationAttributes +import com.google.android.msdl.domain.InteractionProperties /** - * Simplified representation of a [ServiceState] for use in SystemUI. Add any fields that we need to - * extract from service state here for consumption downstream + * This class represents the set of [InteractionProperties] that only hold [VibrationAttributes] for + * the case of user authentication. */ -data class ServiceStateModel(val isEmergencyOnly: Boolean) { - companion object { - fun fromServiceState(serviceState: ServiceState): ServiceStateModel { - return ServiceStateModel(isEmergencyOnly = serviceState.isEmergencyOnly) - } - } -} +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 dad440083f70..64ccbe1a3f5a 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java @@ -19,7 +19,9 @@ package com.android.keyguard; 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.flags.FeatureFlags; 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 @@ public abstract class KeyguardAbsKeyInputViewController<T extends KeyguardAbsKey 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 @@ public abstract class KeyguardAbsKeyInputViewController<T extends KeyguardAbsKey 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 @@ public abstract class KeyguardAbsKeyInputViewController<T extends KeyguardAbsKey mLatencyTracker = latencyTracker; mFalsingCollector = falsingCollector; mEmergencyButtonController = emergencyButtonController; + mMSDLPlayer = msdlPlayer; } abstract void resetState(); @@ -178,6 +187,7 @@ public abstract class KeyguardAbsKeyInputViewController<T extends KeyguardAbsKey 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 @@ public abstract class KeyguardAbsKeyInputViewController<T extends KeyguardAbsKey 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 @@ public abstract class KeyguardAbsKeyInputViewController<T extends KeyguardAbsKey } } + 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 db14a0f67fca..45fdbc6bc888 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.user.domain.interactor.SelectedUserInteractor; 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 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> 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 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> 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 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> KeyguardViewController keyguardViewController, FeatureFlags featureFlags, SelectedUserInteractor selectedUserInteractor, UiEventLogger uiEventLogger, - KeyguardKeyboardInteractor keyguardKeyboardInteractor) { + KeyguardKeyboardInteractor keyguardKeyboardInteractor, + MSDLPlayer msdlPlayer) { mKeyguardUpdateMonitor = keyguardUpdateMonitor; mLockPatternUtils = lockPatternUtils; mLatencyTracker = latencyTracker; @@ -246,6 +253,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> mSelectedUserInteractor = selectedUserInteractor; mUiEventLogger = uiEventLogger; mKeyguardKeyboardInteractor = keyguardKeyboardInteractor; + mMSDLPlayer = msdlPlayer; } /** Create a new {@link KeyguardInputViewController}. */ @@ -268,14 +276,14 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> 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 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> 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 490ad5c4136d..6983a06bb0e6 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java @@ -19,9 +19,13 @@ package com.android.keyguard; 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.statusbar.policy.DevicePostureController; 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 @@ public class KeyguardPasswordViewController 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 @@ public class KeyguardPasswordViewController 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 0f61233ac64f..dd7c3e4f8a3a 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.flags.FeatureFlags; 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 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB 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 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB 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 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB 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 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB 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 f4cda0204036..7fc038f98a85 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java @@ -18,6 +18,7 @@ package com.android.keyguard; 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.res.R; 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 @@ public class KeyguardPinViewController 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 afd42cbfdb25..61f9800c351b 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.DeviceEntryFaceAuthInt 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 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard 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 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard } if (KeyguardWmStateRefactor.isEnabled()) { - mKeyguardTransitionInteractor.startDismissKeyguardTransition( + mKeyguardDismissTransitionInteractor.startDismissKeyguardTransition( "KeyguardSecurityContainerController#finish"); } } @@ -458,7 +458,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard DeviceProvisionedController deviceProvisionedController, FaceAuthAccessibilityDelegate faceAuthAccessibilityDelegate, DevicePolicyManager devicePolicyManager, - KeyguardTransitionInteractor keyguardTransitionInteractor, + KeyguardDismissTransitionInteractor keyguardDismissTransitionInteractor, Lazy<PrimaryBouncerInteractor> primaryBouncerInteractor, Provider<DeviceEntryInteractor> deviceEntryInteractor ) { @@ -490,7 +490,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard 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 3ef3418bfed4..ce5b5d76332d 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 android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; 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.flags.FeatureFlags; 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 @@ public class KeyguardSimPinViewController 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 46225c7ea58a..86b29b2aa7d4 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.flags.FeatureFlags; 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 @@ public class KeyguardSimPukViewController 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 9b45fa47cf21..f731186f6d01 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -3811,7 +3811,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab 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 dcfa775dcabf..4fb80de2d4ec 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 androidx.annotation.Nullable; 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 @@ public class NumPadKey extends ViewGroup implements NumPadAnimationListener { @Nullable private NumPadAnimator mAnimator; private int mOrientation; + @Nullable + private MSDLPlayer mMSDLPlayer; private View.OnClickListener mListener = new View.OnClickListener() { @Override @@ -221,8 +227,12 @@ public class NumPadKey extends ViewGroup implements NumPadAnimationListener { // 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 @@ public class NumPadKey extends ViewGroup implements NumPadAnimationListener { super.onInitializeAccessibilityNodeInfo(info); info.setTextEntryKey(true); } + + public void setMSDLPlayer(@Nullable MSDLPlayer player) { + mMSDLPlayer = player; + } } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java index 9b6501eb1e57..2f0ca6e6bf9d 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 @@ class WindowMagnificationSettings implements MagnificationGestureDetector.OnGest } 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 e4b7b7e69c61..275147e6694c 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 static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL; 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 @@ public class AccessibilityFloatingMenuController implements 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 @@ public class AccessibilityFloatingMenuController implements @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 @@ public class AccessibilityFloatingMenuController implements KeyguardUpdateMonitor keyguardUpdateMonitor, SecureSettings secureSettings, DisplayTracker displayTracker, - NavigationModeController navigationModeController) { + NavigationModeController navigationModeController, + Handler handler) { mContext = context; mWindowManager = windowManager; mViewCaptureAwareWindowManager = viewCaptureAwareWindowManager; @@ -121,6 +123,7 @@ public class AccessibilityFloatingMenuController implements mSecureSettings = secureSettings; mDisplayTracker = displayTracker; mNavigationModeController = navigationModeController; + mHandler = handler; mIsKeyguardVisible = false; } @@ -159,6 +162,8 @@ public class AccessibilityFloatingMenuController implements mAccessibilityButtonModeObserver.addListener(this); mAccessibilityButtonTargetsObserver.addListener(this); mKeyguardUpdateMonitor.registerCallback(mKeyguardCallback); + mAccessibilityManager.registerUserInitializationCompleteCallback( + mUserInitializationCompleteCallback); } /** @@ -172,7 +177,7 @@ public class AccessibilityFloatingMenuController implements */ private void handleFloatingMenuVisibility(boolean keyguardVisible, @AccessibilityButtonMode int mode, String targets) { - if (keyguardVisible) { + if (keyguardVisible || mIsUserInInitialization) { destroyFloatingMenu(); return; } @@ -210,4 +215,18 @@ public class AccessibilityFloatingMenuController implements 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 03f282e8f9a3..bb80396c70fb 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 @@ interface QSAccessibilityModule { @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 @@ interface QSAccessibilityModule { 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 @@ interface QSAccessibilityModule { 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 a093f58b88ba..fd06bb174ea3 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 android.view.VelocityTracker 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.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.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 @@ constructor( 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 @@ constructor( @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 @@ constructor( // 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 @@ constructor( 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/bouncer/ui/viewmodel/BouncerMessageViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt index 05b46564c5ba..e54dc7dbdebb 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 @@ -155,7 +155,7 @@ constructor( emptyFlow() } } - .collectLatest { messageViewModel -> message.value = messageViewModel } + .collect { messageViewModel -> message.value = messageViewModel } } private suspend fun listenForSimBouncerEvents() { @@ -170,7 +170,7 @@ constructor( 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 2a272714db37..2d57e5b4f204 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.bouncer.domain.interactor.BouncerInteractor 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 @@ constructor( 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 b985fc4efece..adc4bc9a14f3 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 @@ -160,7 +160,7 @@ constructor( launch { userSwitcher.selectedUser .map { it.image.toBitmap() } - .collectLatest { _selectedUserImage.value = it } + .collect { _selectedUserImage.value = it } } launch { @@ -187,34 +187,32 @@ constructor( ) } } - .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 fc860e53aac5..2493cf1a101b 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.delay 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 @@ constructor( combine(isInputEnabled, isTextFieldFocused) { hasInput, hasFocus -> hasInput && !hasFocus } - .collectLatest { _isTextFieldFocusRequested.value = it } - } - launch { - selectedUserInteractor.selectedUser.collectLatest { _selectedUserId.value = it } + .collect { _isTextFieldFocusRequested.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 @@ constructor( ) { 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 60ec3019d09d..0a866b43429f 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.coroutineScope 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 @@ constructor( 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 db78a98d96af..df6ca9bf0511 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.coroutineScope 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 @@ constructor( } else { interactor.hintedPinLength } - .collectLatest { _hintedPinLength.value = it } + .collect { _hintedPinLength.value = it } } launch { combine( @@ -135,17 +134,17 @@ constructor( 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 3cdb57318e8d..aef5f1f422d1 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 @@ sealed class Icon { } /** 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 9b96341bdd8e..b570e14c646a 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.domain.interactor.SceneInteractor 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 @@ constructor( sceneInteractor: SceneInteractor, @CommunalLog logBuffer: LogBuffer, @CommunalTableLog tableLogBuffer: TableLogBuffer, + private val managedProfileController: ManagedProfileController ) { private val logger = Logger(logBuffer, "CommunalInteractor") @@ -401,12 +403,7 @@ constructor( /** 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/domain/interactor/DeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt index dff391a32d6b..f5914104d87f 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 @@ interface DeviceEntryFaceAuthInteractor : CoreStartable { fun unregisterListener(listener: FaceAuthenticationListener) fun onUdfpsSensorTouched() + fun onAssistantTriggeredOnLockScreen() + fun onDeviceLifted() - fun onQsExpansionStared() + + fun onQsExpansionStarted() + 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 de5d0aa5aad7..b7d2a57d9a41 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 @@ class NoopDeviceEntryFaceAuthInteractor @Inject constructor() : DeviceEntryFaceA override fun isFaceAuthEnabledAndEnrolled(): Boolean = false override fun isFaceAuthStrong(): Boolean = false + override fun start() = Unit override fun registerListener(listener: FaceAuthenticationListener) {} @@ -59,13 +60,17 @@ class NoopDeviceEntryFaceAuthInteractor @Inject constructor() : DeviceEntryFaceA override fun onDeviceLifted() {} - override fun onQsExpansionStared() {} + override fun onQsExpansionStarted() {} 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 183e0e96e765..5ef63d9b856c 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.CoroutineScope 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 @@ constructor( } } .launchIn(applicationScope) + + if (SceneContainerFlag.isEnabled) { + sceneInteractor + .get() + .transitionState + .filter { it.isTransitioning(from = Scenes.QuickSettings, to = Scenes.Shade) } + .distinctUntilChanged() + .onEach { onQsExpansionStarted() } + .launchIn(applicationScope) + } } private val isBouncerVisible: Flow<Boolean> by lazy { @@ -239,7 +250,7 @@ constructor( runFaceAuth(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED, true) } - override fun onQsExpansionStared() { + override fun onQsExpansionStarted() { runFaceAuth(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_QS_EXPANDED, true) } 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 40e2f174cfb7..1f5878b4698f 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.filter 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 @@ constructor( 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 @@ constructor( // 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 @@ constructor( 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 @@ constructor( 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 e3f740e6ff72..1c263ae4b2bb 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 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ mShadeExpanded = expanded; updateLifecycleStateLocked(); + updateGestureBlockingLocked(); }); } }; @@ -215,6 +216,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ mBouncerShowing = bouncerShowing; updateLifecycleStateLocked(); + updateGestureBlockingLocked(); }); } }; @@ -248,7 +250,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ * others. */ public void reset(String source) { - reset(()-> {}, source); + reset(() -> {}, source); } /** @@ -525,11 +527,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ mStarted = true; updateRedirectWakeup(); - - if (!isDreamInPreviewMode()) { - mGestureInteractor.addGestureBlockedMatcher(DREAM_TYPE_MATCHER, - GestureInteractor.Scope.Global); - } + updateGestureBlockingLocked(); } private void updateRedirectWakeup() { @@ -553,6 +551,18 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ 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(); } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 8c82900810be..d38c9520eb7a 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 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } 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 180afb2b259a..e89594e58aa0 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.RemoteAnimationTarget 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 @@ constructor( private val activityTaskManagerService: IActivityTaskManager, private val keyguardStateController: KeyguardStateController, private val keyguardSurfaceBehindAnimator: KeyguardSurfaceBehindParamsApplier, - private val keyguardTransitionInteractor: KeyguardTransitionInteractor, + private val keyguardDismissTransitionInteractor: KeyguardDismissTransitionInteractor, ) { /** @@ -148,7 +148,7 @@ constructor( // 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 1042ae32fea0..e4b0f6ea69a9 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 javax.inject.Inject 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.onEach 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 000000000000..c19bbbce3b4b --- /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 4aef8084e5e7..44aafabb103a 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 @@ constructor( @Application scope: CoroutineScope, val repository: KeyguardRepository, val biometricSettingsRepository: BiometricSettingsRepository, - transitionInteractor: KeyguardTransitionInteractor, + keyguardDismissTransitionInteractor: KeyguardDismissTransitionInteractor, internalTransitionInteractor: InternalKeyguardTransitionInteractor, ) { @@ -94,7 +94,9 @@ constructor( 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 92e2a911317f..e19b72e26567 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.dagger.qualifiers.Application 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 @@ class KeyguardTransitionInteractor 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 @@ constructor( 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 @@ constructor( } return if (SceneContainerFlag.isEnabled) { - flow.filter { + flow.filter { step -> val fromScene = when (edge) { is Edge.StateToState -> edge.from?.mapToSceneContainerScene() @@ -226,8 +224,23 @@ constructor( 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 @@ constructor( } /** - * 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 @@ constructor( 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 f0bf4029ab39..9b8d9ea11edf 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.device 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 @@ constructor( 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/transitions/DeviceEntryIconTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt index 0032c2f036d3..e2ad46353c51 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.LockscreenToGoneTransitionView 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 @@ abstract class DeviceEntryIconTransitionModule { @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 55fc71823685..99160f8a9158 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 @@ constructor( // 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 6f8389fc8b7c..9f6821051757 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 @@ constructor( alternateBouncerToDozingTransitionViewModel: AlternateBouncerToDozingTransitionViewModel, dreamingToAodTransitionViewModel: DreamingToAodTransitionViewModel, primaryBouncerToLockscreenTransitionViewModel: PrimaryBouncerToLockscreenTransitionViewModel, + occludedToDozingTransitionViewModel: OccludedToDozingTransitionViewModel, ) { val color: Flow<Int> = deviceEntryIconViewModel.useBackgroundProtection.flatMapLatest { useBackground -> @@ -103,6 +104,7 @@ constructor( 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 ebdcaa0c91a6..eaa61a113ee6 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.Edge 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 @@ constructor( 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 @@ constructor( .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 fcafd5e9151d..adb63b7b3e69 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.MutableStateFlow 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 @@ constructor( 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 7383f57bc4e5..2819e617629d 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 com.android.systemui.util.kotlin.filterValuesNotNull 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 @@ constructor( } } } - .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 af019300c764..4fb2b9b35e88 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 @@ import kotlinx.coroutines.flow.Flow class OccludedToDozingTransitionViewModel @Inject constructor( + deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor, animationFlow: KeyguardTransitionAnimationFlow, -) { +) : DeviceEntryIconTransition { private val transitionAnimation = animationFlow.setup( duration = FromOccludedTransitionInteractor.TO_DOZING_DURATION, @@ -50,4 +55,17 @@ constructor( 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 378a147c2c82..bcf748e7573f 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 @@ fun createActionsFromState( 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 @@ private fun getStandardAction( 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 415449f4454f..4555810ee0ef 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.data.repository.MediaDataRepository 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 @@ class MediaDataProcessor( .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 c78220e42d1a..95ca11c9b451 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 @@ object MediaControlDrawables { 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 @@ object MediaControlDrawables { 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 f4601340ee42..64820e0d0ced 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.common.shared.model.Icon 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 @@ class MediaControlViewModel( }, 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 c706c3e97c96..e8c90c1fc9bf 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.UserContextProvider; import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.ShadeViewController; import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor; -import com.android.systemui.shared.navigationbar.RegionSamplingHelper; import com.android.systemui.shared.recents.utilities.Utilities; import com.android.systemui.shared.rotation.RotationButtonController; import com.android.systemui.shared.rotation.RotationPolicyUtil; @@ -166,6 +165,7 @@ import com.android.systemui.util.DeviceConfigProxy; 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 000000000000..db988f62b99d --- /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 d948dfd7d3b9..c75b601ab4a8 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.DisposableEffect 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.FixedColumnsSizeViewModel 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 @@ constructor( 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 a9027ff92996..eeb55ca19bc3 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 @@ fun QuickQuickSettings( 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 79c2eb90af20..24af09d62313 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.BoxScope 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 @@ fun Tile( // 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 @@ private fun TileContainer( 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 @@ private fun LargeTileContent( // 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 @@ private fun TileIcon( 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 @@ private fun TileIcon( } } -@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 @@ fun tileHeight(iconWithLabel: Boolean = false): Dp { return if (iconWithLabel) { TileDefaults.IconTileWithLabelHeight } else { - dimensionResource(id = R.dimen.qs_tile_height) + TileDefaults.TileHeight } } @@ -749,6 +743,14 @@ private object TileDefaults { 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 @@ private object TileDefaults { fun animateIconShape(state: Int): Shape { return animateShape( state = state, - activeCornerRadius = ActiveTileCornerRadius, + activeCornerRadius = ActiveIconCornerRadius, label = "QSTileCornerRadius", ) } @@ -821,7 +823,7 @@ private object TileDefaults { 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 6d63d26d4300..313cb30d84ff 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.content.Intent 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 @@ constructor( 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 @@ constructor( 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 @@ constructor( 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 71f8639c1e22..89b9eee52f2a 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 @@ public class InternetDialogDelegate implements mInternetDialogController.isAirplaneModeEnabled() ? View.VISIBLE : View.GONE); mWifiRecyclerView.setLayoutManager(new LinearLayoutManager(context)); mWifiRecyclerView.setAdapter(mAdapter); + + updateDialogUI(getWifiNetworkContent()); } @Override @@ -315,6 +317,7 @@ public class InternetDialogDelegate implements } mLifecycleRegistry.setCurrentState(Lifecycle.State.RESUMED); + mInternetDialogController.onStart(this, mCanConfigWifi); if (!mCanConfigWifi) { hideWifiViews(); @@ -402,10 +405,12 @@ public class InternetDialogDelegate implements 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 @@ public class InternetDialogDelegate implements 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 3f18fc2066eb..6173091b3b99 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.app.Flags 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 @@ constructor( */ 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 904ff3aaad26..db4812342050 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 @@ import com.android.systemui.common.shared.model.Icon 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 83c3335ebffb..7f571b135fc8 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 @@ constructor( ) : 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 af55f5a9a968..2bb5dc66bc16 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 com.android.systemui.scene.ui.viewmodel.SceneActionsViewModel 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 @@ constructor( } } } - .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 93bf73fbfae5..a264f5142293 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 @@ constructor( 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 @@ constructor( 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 d2967b87b967..9956a46d4701 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 com.android.systemui.shade.shared.model.ShadeAlignment 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 @@ constructor( } } } - .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 fe5cbb18f046..000781acec58 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 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis 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 6e8997346fc4..98cf941d2c85 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.KeyguardStateCallbackStartabl 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 @@ import dagger.multibindings.IntoMap [ EmptySceneModule::class, GoneSceneModule::class, + NotificationsShadeOverlayModule::class, NotificationsShadeSceneModule::class, NotificationsShadeSessionModule::class, QuickSettingsSceneModule::class, @@ -99,6 +101,10 @@ interface KeyguardlessSceneContainerFrameworkModule { 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 7d63b4ce0044..86b242782308 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt @@ -28,6 +28,7 @@ import com.android.systemui.scene.domain.startable.KeyguardStateCallbackStartabl 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 @@ -50,6 +51,7 @@ import dagger.multibindings.IntoMap QuickSettingsSceneModule::class, ShadeSceneModule::class, QuickSettingsShadeSceneModule::class, + NotificationsShadeOverlayModule::class, NotificationsShadeSceneModule::class, NotificationsShadeSessionModule::class, SceneDomainModule::class, @@ -108,6 +110,10 @@ interface SceneContainerFrameworkModule { 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 c176ccad7e74..2d40845df802 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 @@ constructor( 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 @@ constructor( return when { toDistance == 0 -> Clear toDistance > fromDistance -> Push + stack.peek() != to -> null toDistance < fromDistance -> Pop else -> error( @@ -103,7 +95,10 @@ constructor( } 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/model/Overlays.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Overlays.kt new file mode 100644 index 000000000000..0bb02e92c9ba --- /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 b707a5ae4739..88d4c4f63fdf 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.domain.interactor.ShadeInteractor 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 @@ constructor( } } } - .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 368e4fa06a26..076613005959 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 @@ import kotlinx.coroutines.flow.asStateFlow * 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 474afa8bcb9d..56afb79c40d4 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.ViewGroup.MarginLayoutParams 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 @@ constructor( } 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 922997d08c25..000000000000 --- 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 3ad4075a2b89..ee1008d26414 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 @@ fun interface ScreenshotRequestProcessor { 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 9db1f24d2e2e..ad5e772934c8 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.res.R; 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 @@ public class AppClipsActivity extends ComponentActivity { // 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 class AppClipsActivity extends ComponentActivity { 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 @@ public class AppClipsActivity extends ComponentActivity { * 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 0e312f979d0c..30c33c5224ee 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.content.ClipData 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 44f767aa321e..2cb9fe7f1a9d 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 @@ package com.android.systemui.screenshot.policy 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 @@ interface ScreenshotPolicyModule { @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 e7ee961e1888..edff4bfa2d14 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 @@ interface DisplayTracker { 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 05f19ef2f043..b9f9b929d962 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 @@ public abstract class MultiUserUtilsModule { @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 4639e2235346..3bb494b7deca 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 @@ constructor( */ 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 @@ constructor( ), { (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 @@ constructor( 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 5537c6b95340..31813b240c37 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -191,7 +191,6 @@ import com.android.systemui.statusbar.notification.PropertyAnimator; 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; @@ -608,7 +607,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump 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; @@ -774,7 +772,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump ActivityStarter activityStarter, SharedNotificationContainerInteractor sharedNotificationContainerInteractor, ActiveNotificationsInteractor activeNotificationsInteractor, - HeadsUpNotificationInteractor headsUpNotificationInteractor, ShadeAnimationInteractor shadeAnimationInteractor, KeyguardViewConfigurator keyguardViewConfigurator, DeviceEntryFaceAuthInteractor deviceEntryFaceAuthInteractor, @@ -809,7 +806,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mKeyguardTransitionInteractor = keyguardTransitionInteractor; mSharedNotificationContainerInteractor = sharedNotificationContainerInteractor; mActiveNotificationsInteractor = activeNotificationsInteractor; - mHeadsUpNotificationInteractor = headsUpNotificationInteractor; mKeyguardInteractor = keyguardInteractor; mPowerInteractor = powerInteractor; mKeyguardViewConfigurator = keyguardViewConfigurator; diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java index 16aef6586ee9..34c0cb7c7a31 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 @@ public class QuickSettingsControllerImpl implements QuickSettingsController, Dum // When expanding QS, let's authenticate the user if possible, // this will speed up notification actions. if (height == 0 && !mKeyguardStateController.canDismissLockScreen()) { - mDeviceEntryFaceAuthInteractor.onQsExpansionStared(); + mDeviceEntryFaceAuthInteractor.onQsExpansionStarted(); } } 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 c210fc509cec..abf1f4cb8b85 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.awaitCancellation 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 @@ constructor( 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 c8abb1fe1dd5..a154e91feca1 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.coroutineScope 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 @@ constructor( 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 bdc0fdba1dea..ab719132b93e 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.domain.interactor.ShadeInteractor 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 @@ constructor( } } } - .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 7a79a22ebea2..7c707592f5ab 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.AssistedFactory 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 @@ constructor( 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 696e2225d286..d523bc1867c2 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 @@ public class NotificationShelf extends ActivatableNotificationView { } 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 6eadd2627399..2b44c2f9ea7f 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 androidx.core.graphics.ColorUtils; 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 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi /** 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 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi 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 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi @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/ron/demo/ui/viewmodel/DemoRonChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModel.kt index 84ccaec62867..cce9a1624d51 100644 --- 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 @@ -42,9 +42,9 @@ import kotlinx.coroutines.flow.asStateFlow * * Example adb commands: * - * To show a chip with the SysUI icon and custom text: + * 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 + * adb shell cmd statusbar demo-ron -p com.android.systemui -t 10min -c "\\#434343" * ``` * * To hide the chip: @@ -87,6 +87,17 @@ constructor( 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", @@ -119,21 +130,26 @@ constructor( 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, - // TODO(b/361346412): Include a demo with a custom color theme. - colors = ColorsModel.Themed, + colors = colors, text = currentText, ) } else { _chip.value = OngoingActivityChipModel.Shown.Timer( icon = appIcon, - // TODO(b/361346412): Include a demo with a custom color theme. - colors = ColorsModel.Themed, + colors = colors, startTimeMs = systemClock.elapsedRealtime(), onClickListener = null, ) 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 8a5165d8df7b..4b0fc5ab6059 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 @@ sealed interface ColorsModel { 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/commandline/ValueParser.kt b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/ValueParser.kt index 01083d9a7907..412c8c629e0e 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 @@ private val parseFloat: ValueParser<Float> = ValueParser { value -> ?: 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 ecb6d7fd7be4..406a66449f82 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.LogBuffer 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 @@ abstract class StatusBarModule { @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/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java index 4be638f5b41f..2f3719a34ac8 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 @@ public class AmbientState implements Dumpable { } /** - * @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 @@ public class AmbientState implements Dumpable { } /** - * @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 1f767aa1cbbc..64d71240073f 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 @@ -874,7 +874,7 @@ public class NotificationStackScrollLayout 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); @@ -1123,11 +1123,13 @@ public class NotificationStackScrollLayout @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); } @@ -1233,7 +1235,6 @@ public class NotificationStackScrollLayout if (mAmbientState.getStackTop() != stackTop) { mAmbientState.setStackTop(stackTop); onTopPaddingChanged(/* animate = */ isAddOrRemoveAnimationPending()); - setExpandedHeight(mExpandedHeight); } } @@ -1476,7 +1477,7 @@ public class NotificationStackScrollLayout @VisibleForTesting public void updateStackEndHeightAndStackHeight(float fraction) { - final float oldStackHeight = mAmbientState.getStackHeight(); + final float oldStackHeight = mAmbientState.getInterpolatedStackHeight(); if (SceneContainerFlag.isEnabled()) { final float endHeight; if (!shouldSkipHeightUpdate()) { @@ -1484,20 +1485,20 @@ public class NotificationStackScrollLayout } 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(); } } @@ -1531,7 +1532,7 @@ public class NotificationStackScrollLayout } @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 @@ -1541,7 +1542,7 @@ public class NotificationStackScrollLayout fraction = 1f - dozeAmount; } } - mAmbientState.setStackHeight( + mAmbientState.setInterpolatedStackHeight( MathUtils.lerp(endHeight * StackScrollAlgorithm.START_FRACTION, endHeight, fraction)); } @@ -1570,8 +1571,11 @@ public class NotificationStackScrollLayout // 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) { @@ -1580,6 +1584,19 @@ public class NotificationStackScrollLayout } } + 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; @@ -1592,6 +1609,7 @@ public class NotificationStackScrollLayout * @param height the expanded height of the panel */ public void setExpandedHeight(float height) { + SceneContainerFlag.assertInLegacyMode(); final boolean skipHeightUpdate = shouldSkipHeightUpdate(); updateStackPosition(); @@ -1723,6 +1741,7 @@ public class NotificationStackScrollLayout * Measured relative to the resting position. */ private float getExpandTranslationStart() { + SceneContainerFlag.assertInLegacyMode(); return -getTopPadding() + getMinExpansionHeight() - mShelf.getIntrinsicHeight(); } @@ -1731,6 +1750,7 @@ public class NotificationStackScrollLayout * Measured in absolute height. */ private float getAppearStartPosition() { + SceneContainerFlag.assertInLegacyMode(); if (isHeadsUpTransition()) { final NotificationSection firstVisibleSection = getFirstVisibleSection(); final int pinnedHeight = firstVisibleSection != null @@ -1786,6 +1806,7 @@ public class NotificationStackScrollLayout * have the shelf on its own) */ private float getAppearEndPosition() { + SceneContainerFlag.assertInLegacyMode(); if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { return getAppearEndPositionLegacy(); } @@ -1846,7 +1867,7 @@ public class NotificationStackScrollLayout */ @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. @@ -2522,10 +2543,33 @@ public class NotificationStackScrollLayout } @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, @@ -2536,19 +2580,15 @@ public class NotificationStackScrollLayout // 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(); } 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 08d3e9f2b634..e112c99e19b3 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 @@ -1408,6 +1408,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { } public float calculateAppearFraction(float height) { + SceneContainerFlag.assertInLegacyMode(); return mView.calculateAppearFraction(height); } 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 0c2b5aeb6feb..ef1bcfc45879 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 @@ public class StackScrollAlgorithm { 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 @@ public class StackScrollAlgorithm { || 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/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index e3242d13077f..7227b9380b61 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.keyboardShortcutHelperRewrite; 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 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { 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/ManagedProfileControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java index 56ea00cf0954..7ef1e416aa19 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 android.os.UserManager; 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 @@ public class ManagedProfileControllerImpl implements ManagedProfileController { 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 @@ public class ManagedProfileControllerImpl implements ManagedProfileController { 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 05bd1a7676ae..ba39c3bb4124 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 android.view.View; 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.SensorPrivacyController; 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 @@ public class PhoneStatusBarPolicy // 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 @@ public class PhoneStatusBarPolicy () -> 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/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 09b6b6895ebc..43f9af6016f1 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.domain.interactor.DeviceEntryInteractor; 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 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } } }; - private Lazy<WindowManagerLockscreenVisibilityInteractor> mWmLockscreenVisibilityInteractor; - private Lazy<KeyguardSurfaceBehindInteractor> mSurfaceBehindInteractor; private Lazy<KeyguardDismissActionInteractor> mKeyguardDismissActionInteractor; private final JavaAdapter mJavaAdapter; private StatusBarKeyguardViewManagerInteractor mStatusBarKeyguardViewManagerInteractor; @@ -394,11 +390,10 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb 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 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb 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 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } KeyguardTransitionInteractor mKeyguardTransitionInteractor; + KeyguardDismissTransitionInteractor mKeyguardDismissTransitionInteractor; CoroutineDispatcher mMainDispatcher; @Override @@ -1616,7 +1611,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } 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 ba5939835a08..d5fafe222c40 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.ArraySet; 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 java.util.Objects; 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 @@ public class StatusBarSignalPolicy implements SignalCallback, 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; - - // Track as little state as possible, and only for padding purposes - private boolean mIsAirplaneMode = false; + private final boolean mActivityEnabled; - private ArrayList<CallIndicatorIconState> mCallIndicatorStates = new ArrayList<>(); + private final ArrayList<CallIndicatorIconState> mCallIndicatorStates = new ArrayList<>(); private boolean mInitialized; @Inject @@ -80,15 +87,19 @@ public class StatusBarSignalPolicy implements SignalCallback, 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 @@ public class StatusBarSignalPolicy implements SignalCallback, 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 @@ public class StatusBarSignalPolicy implements SignalCallback, @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 class StatusBarSignalPolicy implements SignalCallback, } } + 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/phone/StatusBarSignalPolicyRefactor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicyRefactor.kt new file mode 100644 index 000000000000..0577f495dd4f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicyRefactor.kt @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.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 status_bar_signal_policy_refactor flag state. */ +@Suppress("NOTHING_TO_INLINE") +object StatusBarSignalPolicyRefactor { + /** The aconfig flag name */ + const val FLAG_NAME = Flags.FLAG_STATUS_BAR_SIGNAL_POLICY_REFACTOR + + /** A token used for dependency declaration */ + val token: FlagToken + get() = FlagToken(FLAG_NAME, isEnabled) + + /** Is the refactor enabled */ + @JvmStatic + inline val isEnabled + get() = Flags.statusBarSignalPolicyRefactor() + + /** + * Called to ensure code is only run when the flag is enabled. This protects users from the + * unintended behaviors caused by accidentally running new logic, while also crashing on an eng + * build to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun isUnexpectedlyInLegacyMode() = + RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME) + + /** + * Called to ensure code is only run when the flag is disabled. This will throw an exception if + * the flag is enabled to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/DarkIconManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/DarkIconManager.java index 8871dae3c620..6c303303c8f8 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 @@ public class DarkIconManager extends IconManager { } @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 5ad737684ca1..91ead614ffa4 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_BIND 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.os.Bundle; 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 @@ public class IconManager implements DemoModeCommandReceiver { }; } - @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 @@ public class IconManager implements DemoModeCommandReceiver { 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 @@ public class IconManager implements DemoModeCommandReceiver { 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 @@ public class IconManager implements DemoModeCommandReceiver { 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 @@ public class IconManager implements DemoModeCommandReceiver { ); } - 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 @@ public class IconManager implements DemoModeCommandReceiver { /** 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 ee528e915079..0459b9749e0a 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 @@ public interface StatusBarIconController { * @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 ad3a9e350c4b..9b6d32bd179d 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 @@ public class StatusBarIconControllerImpl implements Tunable, 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 @@ public class StatusBarIconControllerImpl implements Tunable, 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 @@ public class StatusBarIconControllerImpl implements Tunable, 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 @@ public class StatusBarIconControllerImpl implements Tunable, 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 @@ public class StatusBarIconControllerImpl implements Tunable, } } - 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/repository/MobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt index 5ad8bf1652b6..32e9c85bea81 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 android.telephony.SubscriptionManager 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 @@ interface MobileConnectionsRepository { 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 b0681525a137..b247da4f4219 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.SysUISingleton 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 @@ constructor( 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 a944e9133a31..3a79f3fb3573 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.dagger.qualifiers.Application 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 @@ constructor( 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 261258a58914..b756a05da182 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.Context 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.airplane.data.repository.Airplane 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.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapLatest @@ -175,8 +172,8 @@ constructor( } .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 @@ constructor( 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 26553e66ac5c..28fff4e68935 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 @@ constructor( .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 1a55f7d90e97..f5cfc8c5b307 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 android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID 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.DefaultConnecti 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 @@ constructor( } // 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/policy/PolicyModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt index 21ec14fc7f03..591d7af44db1 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 @@ interface PolicyModule { 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 71bcdfcba049..6cebcbd2731a 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 android.os.UserManager; 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.Module; import dagger.Provides; import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; import javax.inject.Named; @@ -236,4 +239,12 @@ public interface StatusBarPolicyModule { 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 a67b47a9a0c9..93c631f65df7 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.provider.Settings.Secure.ZEN_DURATION_PROMPT 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 @@ constructor( 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 @@ constructor( val modes: Flow<List<ZenMode>> = zenModeRepository.modes - val activeModes: Flow<List<ZenMode>> = - modes.map { modes -> modes.filter { mode -> mode.isActive } }.distinctUntilChanged() - - /** Flow returning the most prioritized of the active modes, if any. */ - val mainActiveMode: Flow<ZenMode?> = - activeModes.map { modes -> getMainActiveMode(modes) }.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 } - } + /** 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)) } + + ActiveZenModes(activeModesList.map { m -> m.name }, mainActiveMode) + } + .flowOn(bgDispatcher) + .distinctUntilChanged() - suspend fun getModeIcon(mode: ZenMode): Icon { - return iconLoader.getIcon(context, mode).await().drawable().asIcon() - } + val mainActiveMode: Flow<ZenModeInfo?> = + activeModes.map { a -> a.mainMode }.distinctUntilChanged() - /** - * 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 000000000000..569e517d6ed5 --- /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/src/com/android/systemui/statusbar/policy/domain/model/ZenModeInfo.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/model/ZenModeInfo.kt new file mode 100644 index 000000000000..5004f4c21371 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/model/ZenModeInfo.kt @@ -0,0 +1,23 @@ +/* + * 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.ZenIcon +import com.android.settingslib.notification.modes.ZenMode + +/** 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 3fffd9f77f1f..cacb3843866b 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.runtime.CompositionLocalProvider 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 @@ fun ModeTile(viewModel: ModeTileViewModel) { 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 @@ fun ModeTile(viewModel: ModeTileViewModel) { 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 7c1cb6a9b62e..abd24533e1c4 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 @@ data class ModeTileViewModel( 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 be90bec03e52..6764839c32d3 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.ACTION_AUTOMATIC_ZEN_RULE_SETTINGS 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 @@ constructor( 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 @@ constructor( } } }, - onLongClick = { openSettings(mode) } + onLongClick = { openSettings(mode) }, + onLongClickLabel = + context.resources.getString(R.string.accessibility_long_click_tile) ) } } @@ -127,23 +135,36 @@ constructor( 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/util/concurrency/GlobalConcurrencyModule.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/GlobalConcurrencyModule.java index ecf1165566dc..70774f13fe6b 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.Module; 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 @@ public abstract class GlobalConcurrencyModule { @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 4f77cd04f5fa..73728e698c09 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 @@ constructor( } .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 @@ constructor( 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 ba0b08210799..0e4cac0b2224 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 @@ sealed interface AudioOutputDevice { 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 @@ sealed interface AudioOutputDevice { 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 a270d5ffa9de..f94cbda49e91 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 @@ constructor( ) 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 31a89775e916..aa07cfd26bdb 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 @@ constructor( 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 220fb2b42eb8..6588b443b0af 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 @@ sealed interface MediaOutputComponentModel { 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 @@ sealed interface MediaOutputComponentModel { 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 8ba672d2a15e..42f88b48110c 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 36b42f28e202..e565de5c55ea 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 @@ constructor( } } 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 @@ constructor( 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 @@ constructor( 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 cfcd6b14010d..56d0bce23f17 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 @@ constructor( 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 @@ constructor( .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 4841c78f6da0..ea213cba9567 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 android.service.quickaccesswallet.QuickAccessWalletClient; 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.ClassKey; 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 @@ public abstract class WalletModule { @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 e724c60bfc54..487432eb5886 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.verifyZeroInteractions; 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.SysuiTestCase; 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 @@ public class KeyguardAbsKeyInputViewControllerTest extends SysuiTestCase { @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 @@ public class KeyguardAbsKeyInputViewControllerTest extends SysuiTestCase { 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 @@ public class KeyguardAbsKeyInputViewControllerTest extends SysuiTestCase { 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 36d4d122ab11..c43a1849d2a7 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 @@ class KeyguardPinViewControllerTest : SysuiTestCase() { 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 7151c429acf9..ea6c1bc5b31c 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 @@ class KeyguardSimPinViewControllerTest : SysuiTestCase() { 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 acae913459b3..c26365d00376 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 @@ class KeyguardSimPukViewControllerTest : SysuiTestCase() { 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 113a8c05ee66..5e37d4cd1faf 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 static org.mockito.Mockito.when; 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 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase { private AccessibilityManager mAccessibilityManager; private KeyguardUpdateMonitor mKeyguardUpdateMonitor; private AccessibilityFloatingMenuController mController; + private TestableLooper mTestableLooper; @Mock private AccessibilityButtonTargetsObserver mTargetsObserver; @Mock @@ -108,6 +110,7 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase { 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 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase { mKeyguardCallback.onKeyguardVisibilityChanged(false); mKeyguardCallback.onUserSwitching(fakeUserId); - mKeyguardCallback.onUserSwitchComplete(fakeUserId); + mController.mUserInitializationCompleteCallback.onUserInitializationComplete(1); + mTestableLooper.processAllMessages(); assertThat(mController.mFloatingMenu).isNotNull(); } @@ -346,7 +350,8 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase { 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/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt index 6aecc0e2b8fc..4883d1bb9400 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.android.systemui.util.mockito.eq 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 @@ -365,7 +366,7 @@ class DeviceEntryFaceAuthInteractorTest : SysuiTestCase() { testScope.runTest { underTest.start() - underTest.onQsExpansionStared() + underTest.onQsExpansionStarted() runCurrent() assertThat(faceAuthRepository.runningAuthRequest.value) @@ -373,6 +374,79 @@ class DeviceEntryFaceAuthInteractorTest : SysuiTestCase() { } @Test + @EnableSceneContainer + fun faceAuthIsRequestedWhenQuickSettingsIsExpandedToTheShade() = + testScope.runTest { + underTest.start() + faceAuthRepository.canRunFaceAuth.value = true + kosmos.sceneInteractor.snapToScene(toScene = Scenes.QuickSettings, "for-test") + runCurrent() + + kosmos.sceneInteractor.changeScene(toScene = Scenes.Shade, loggingReason = "for-test") + kosmos.sceneInteractor.setTransitionState( + MutableStateFlow( + ObservableTransitionState.Transition( + fromScene = Scenes.QuickSettings, + toScene = Scenes.Shade, + currentScene = flowOf(Scenes.QuickSettings), + progress = MutableStateFlow(0.2f), + isInitiatedByUserInput = true, + isUserInputOngoing = flowOf(false), + ) + ) + ) + + runCurrent() + assertThat(faceAuthRepository.runningAuthRequest.value) + .isEqualTo(Pair(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_QS_EXPANDED, true)) + } + + @Test + @EnableSceneContainer + fun faceAuthIsRequestedOnlyOnceWhenQuickSettingsIsExpandedToTheShade() = + testScope.runTest { + underTest.start() + faceAuthRepository.canRunFaceAuth.value = true + kosmos.sceneInteractor.snapToScene(toScene = Scenes.QuickSettings, "for-test") + runCurrent() + + kosmos.sceneInteractor.changeScene(toScene = Scenes.Shade, loggingReason = "for-test") + kosmos.sceneInteractor.setTransitionState( + MutableStateFlow( + ObservableTransitionState.Transition( + fromScene = Scenes.QuickSettings, + toScene = Scenes.Shade, + currentScene = flowOf(Scenes.QuickSettings), + progress = MutableStateFlow(0.2f), + isInitiatedByUserInput = true, + isUserInputOngoing = flowOf(false), + ) + ) + ) + + runCurrent() + assertThat(faceAuthRepository.runningAuthRequest.value) + .isEqualTo(Pair(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_QS_EXPANDED, true)) + faceAuthRepository.runningAuthRequest.value = null + + // expansion progress shouldn't trigger face auth again + kosmos.sceneInteractor.setTransitionState( + MutableStateFlow( + ObservableTransitionState.Transition( + fromScene = Scenes.QuickSettings, + toScene = Scenes.Shade, + currentScene = flowOf(Scenes.QuickSettings), + progress = MutableStateFlow(0.5f), + isInitiatedByUserInput = true, + isUserInputOngoing = flowOf(false), + ) + ) + ) + + assertThat(faceAuthRepository.runningAuthRequest.value).isNull() + } + + @Test fun faceAuthIsRequestedWhenNotificationPanelClicked() = testScope.runTest { underTest.start() 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 0ac04b61f13d..76539d776d3d 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 @@ class DisplayRepositoryTest : SysuiTestCase() { 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 4e1b12f70da5..43c7ed6a769d 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.ext.junit.runners.AndroidJUnit4 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 @@ class WindowManagerLockscreenVisibilityManagerTest : SysuiTestCase() { @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 @@ class WindowManagerLockscreenVisibilityManagerTest : SysuiTestCase() { 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 9eccd9fd3cdf..3459645ad034 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 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { 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 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { 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 19735e29834e..d2dcf4d38d0c 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 android.testing.TestableLooper.RunWithLooper 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.QSTileConfigProvider 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.Test 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 @@ import org.mockito.kotlin.whenever @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 @@ class ModesTileTest : SysuiTestCase() { @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 @@ class ModesTileTest : SysuiTestCase() { 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 b31d21e7f8b2..15da77dfe33d 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 @@ package com.android.systemui.screenshot 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 @@ class MessageContainerControllerTest : SysuiTestCase() { } @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 0847f01f12d3..000000000000 --- 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 a8d5008c61c6..eb1a04d8e4ff 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 @@ public final class AppClipsActivityTest extends SysuiTestCase { 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 774aa517672e..2e2ac3eb7183 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.FakeFeatureFlagsClassic 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.ArgumentMatchers.isNull 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 @@ class UserTrackerImplTest : SysuiTestCase() { 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 @@ class UserTrackerImplTest : SysuiTestCase() { 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 fun testNotInitialized() = testScope.runTest { assertThat(tracker.initialized).isFalse() } @Test(expected = IllegalStateException::class) - fun testGetUserIdBeforeInitThrowsException() = testScope.runTest { - tracker.userId - } + fun testGetUserIdBeforeInitThrowsException() = testScope.runTest { tracker.userId } @Test(expected = IllegalStateException::class) - fun testGetUserHandleBeforeInitThrowsException() = testScope.runTest { - tracker.userHandle - } + fun testGetUserHandleBeforeInitThrowsException() = testScope.runTest { tracker.userHandle } @Test(expected = IllegalStateException::class) - fun testGetUserContextBeforeInitThrowsException() = testScope.runTest { - tracker.userContext - } + fun testGetUserContextBeforeInitThrowsException() = testScope.runTest { tracker.userContext } @Test(expected = IllegalStateException::class) - fun testGetUserContentResolverBeforeInitThrowsException() = testScope.runTest { - tracker.userContentResolver - } + fun testGetUserContentResolverBeforeInitThrowsException() = + testScope.runTest { tracker.userContentResolver } @Test(expected = IllegalStateException::class) - fun testGetUserProfilesBeforeInitThrowsException() = testScope.runTest { - tracker.userProfiles - } + fun testGetUserProfilesBeforeInitThrowsException() = testScope.runTest { tracker.userProfiles } @Test - fun testInitialize() = testScope.runTest { - tracker.initialize(0) + fun testInitialize() = + testScope.runTest { + tracker.initialize(0) - assertThat(tracker.initialized).isTrue() - } + assertThat(tracker.initialized).isTrue() + } @Test - fun testReceiverRegisteredOnInitialize() = testScope.runTest { - tracker.initialize(0) + fun testReceiverRegisteredOnInitialize() = + testScope.runTest { + tracker.initialize(0) - val captor = ArgumentCaptor.forClass(IntentFilter::class.java) + 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() + 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) + fun testInitialValuesSet() = + testScope.runTest { + val testID = 4 + tracker.initialize(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) + verify(userManager).getProfiles(testID) - val info = tracker.userProfiles[0] - assertThat(info.id).isEqualTo(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) - @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) - } + val info = tracker.userProfiles[0] + assertThat(info.id).isEqualTo(testID) + } @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) + 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) } - 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 - - `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) + fun testManagedProfileAvailable() = + testScope.runTest { + tracker.initialize(0) + 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_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 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 - - `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) + fun testManagedProfileUnavailable() = + testScope.runTest { + tracker.initialize(0) + 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 or UserInfo.FLAG_QUIET_MODE, + UserManager.USER_TYPE_PROFILE_MANAGED + ) + infoProfile.profileGroupId = id + listOf(info, infoProfile) + } + + 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) } - // 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) - - `when`(userManager.getProfiles(anyInt())).thenAnswer { invocation -> - listOf(UserInfo(invocation.getArgument(0), "", UserInfo.FLAG_FULL)) + @Test + fun testManagedProfileStartedAndRemoved() = + testScope.runTest { + tracker.initialize(0) + 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) + } + + // 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) } - 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 testCallbackNotCalledOnAdd() = + testScope.runTest { + tracker.initialize(0) + val callback = TestCallback() - tracker.addCallback(callback, executor) + tracker.addCallback(callback, executor) - assertThat(callback.calledOnProfilesChanged).isEqualTo(0) - assertThat(callback.calledOnUserChanged).isEqualTo(0) - } + assertThat(callback.calledOnProfilesChanged).isEqualTo(0) + assertThat(callback.calledOnUserChanged).isEqualTo(0) + } @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) - } + 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()) - } + 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) - } + 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) + 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) } - 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) - } + 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 5a5cdcd99054..3ba1447eb406 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.log.logcatLogBuffer 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 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { // 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 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { 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 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { } @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 523d15c7548f..9481e5a52098 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.NotificationWakeUpCoordinator 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.NotificationListContain 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 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { 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 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { 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 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mActivityStarter, mSharedNotificationContainerInteractor, mActiveNotificationsInteractor, - mHeadsUpNotificationInteractor, mShadeAnimationInteractor, mKeyguardViewConfigurator, mDeviceEntryFaceAuthInteractor, 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 000000000000..593f87382b52 --- /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/ron/demo/ui/viewmodel/DemoRonChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModelTest.kt index 8576893216d0..118dea6376bb 100644 --- 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 @@ -25,6 +25,7 @@ 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 @@ -103,6 +104,22 @@ class DemoRonChipViewModelTest : SysuiTestCase() { @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) 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 8cf7473345a7..1efb3f74b04f 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 @@ class ValueParserTest : SysuiTestCase() { } @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 e4945fc6e847..1a1af2eecc00 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 @@ open class NotificationShelfTest : SysuiTestCase() { 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 @@ open class NotificationShelfTest : SysuiTestCase() { 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 @@ open class NotificationShelfTest : SysuiTestCase() { 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 @@ open class NotificationShelfTest : SysuiTestCase() { 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 @@ open class NotificationShelfTest : SysuiTestCase() { 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 @@ open class NotificationShelfTest : SysuiTestCase() { 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 @@ open class NotificationShelfTest : SysuiTestCase() { 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 @@ open class NotificationShelfTest : SysuiTestCase() { 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 @@ open class NotificationShelfTest : SysuiTestCase() { 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 8d1228ca5577..a18de68dfcfc 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 @@ -245,26 +245,12 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { 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; @@ -289,8 +275,8 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { 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 @@ -309,7 +295,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { // THEN stackHeight and stackEndHeight are the same verify(mAmbientState).setStackEndHeight(stackEndHeight); - verify(mAmbientState).setStackHeight(stackEndHeight); + verify(mAmbientState).setInterpolatedStackHeight(stackEndHeight); } @Test @@ -329,7 +315,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { // 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 @@ -349,7 +335,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { // THEN stackHeight is measured from the stack top verify(mAmbientState).setStackEndHeight(stackEndHeight); - verify(mAmbientState).setStackHeight(stackEndHeight); + verify(mAmbientState).setInterpolatedStackHeight(stackEndHeight); } @Test @@ -363,7 +349,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { clearInvocations(mAmbientState); mStackScroller.updateStackEndHeightAndStackHeight(1f); - verify(mAmbientState).setStackHeight(eq(300f)); + verify(mAmbientState).setInterpolatedStackHeight(eq(300f)); } @Test @@ -376,7 +362,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { clearInvocations(mAmbientState); mStackScroller.updateStackEndHeightAndStackHeight(expansionFraction); verify(mAmbientState, never()).setStackEndHeight(anyFloat()); - verify(mAmbientState).setStackHeight(anyFloat()); + verify(mAmbientState).setInterpolatedStackHeight(anyFloat()); } @Test @@ -391,14 +377,14 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { 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 @@ -440,6 +426,86 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { } @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; @@ -466,6 +532,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { } @Test + @DisableSceneContainer public void testSetExpandedHeight_withSplitShade_doesntInterpolateStackHeight() { mTestableResources .addOverride(R.bool.config_use_split_notification_shade, /* value= */ true); 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 7e790197ccd1..3e8bf4792f02 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 @@ class StackScrollAlgorithmTest : SysuiTestCase() { } @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 @@ class StackScrollAlgorithmTest : SysuiTestCase() { } @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 @@ class StackScrollAlgorithmTest : SysuiTestCase() { } @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 @@ class StackScrollAlgorithmTest : SysuiTestCase() { // 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 @@ class StackScrollAlgorithmTest : SysuiTestCase() { // 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 2f810276d7b2..c7919df7e0d0 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.content.pm.UserInfo 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.Mock 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 @@ class ManagedProfileControllerImplTest : SysuiTestCase() { @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 @@ class ManagedProfileControllerImplTest : SysuiTestCase() { 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 76dc65cbc915..2ed34735db1f 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 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.broadcast.BroadcastDispatcher 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.DateFormatUtil 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 @@ class PhoneStatusBarPolicyTest : SysuiTestCase() { 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 @@ class PhoneStatusBarPolicyTest : SysuiTestCase() { statusBarPolicy.init() clearInvocations(iconController) - fakeConnectedDisplayStateProvider.emit(State.CONNECTED) + fakeConnectedDisplayStateProvider.setState(State.CONNECTED) runCurrent() verify(iconController).setIconVisibility(CONNECTED_DISPLAY_SLOT, true) @@ -261,7 +261,8 @@ class PhoneStatusBarPolicyTest : SysuiTestCase() { statusBarPolicy.init() clearInvocations(iconController) - fakeConnectedDisplayStateProvider.emit(State.DISCONNECTED) + fakeConnectedDisplayStateProvider.setState(State.DISCONNECTED) + runCurrent() verify(iconController).setIconVisibility(CONNECTED_DISPLAY_SLOT, false) } @@ -272,9 +273,12 @@ class PhoneStatusBarPolicyTest : SysuiTestCase() { 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 @@ class PhoneStatusBarPolicyTest : SysuiTestCase() { 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 @@ class PhoneStatusBarPolicyTest : SysuiTestCase() { } @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 @@ class PhoneStatusBarPolicyTest : SysuiTestCase() { .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 @@ class PhoneStatusBarPolicyTest : SysuiTestCase() { .setType(AutomaticZenRule.TYPE_OTHER) .setActive(true) .setPackage(SystemZenRules.PACKAGE_ANDROID) - .setIconResId(456) + .setIconResId(android.R.drawable.ic_media_play) .build(), ) ) @@ -422,17 +427,25 @@ class PhoneStatusBarPolicyTest : SysuiTestCase() { 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 @@ class PhoneStatusBarPolicyTest : SysuiTestCase() { } @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 @@ class PhoneStatusBarPolicyTest : SysuiTestCase() { 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 @@ class PhoneStatusBarPolicyTest : SysuiTestCase() { 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 @@ class PhoneStatusBarPolicyTest : SysuiTestCase() { } 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 54c03e894804..3e3c046ce62e 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.dreams.DreamOverlayStateController; 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 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { 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 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { 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 000000000000..90732d0183d2 --- /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 26a57e4c1ca9..50a13b93ea15 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 @@ class StatusBarIconControllerImplTest : SysuiTestCase() { @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 @@ class StatusBarIconControllerImplTest : SysuiTestCase() { 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 76982ae12516..6de2caa59dd3 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.NetworkCapabilities.TRANSPORT_WIFI 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.log.table.TableLogBufferFactory 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.argumentCaptor import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.mock -import com.android.systemui.util.mockito.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.ArgumentMatchers.anyString 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 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { @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() + } + + @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 + } + } - // ... and ignores any other subId - val intent2 = serviceStateIntent(subId = 1, emergencyOnly = true) + // 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 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { */ 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 cc0eae7e6fea..e218fba1d07a 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.coroutines.collectLastValue 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 @@ class MobileIconsInteractorTest : SysuiTestCase() { 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 f486787e9cc7..0945742fb325 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.Network 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 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { // 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 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { 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 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { 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 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { // 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 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { // 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 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { getTunable().onTuningChanged(HIDDEN_ICONS_TUNABLE_KEY, "$SLOT_WIFI,$SLOT_MOBILE") assertThat(latest).containsExactly(ConnectivitySlot.WIFI) - - job.cancel() } @Test @@ -181,23 +170,21 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { 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 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { ) assertThat(latest).isEmpty() - - job.cancel() } @Test @@ -219,29 +204,25 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { 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 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { 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 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { 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 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { 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 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { 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 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { 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 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { 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 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { 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 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { assertThat(latest!!.mobile.isDefault).isTrue() assertThat(latest!!.carrierMerged.isDefault).isTrue() assertThat(latest!!.wifi.isDefault).isTrue() + } + + /** 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) - job.cancel() + 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 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { 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 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { 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 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) assertThat(latest!!.wifi.isDefault).isFalse() - - job.cancel() } @Test @@ -531,8 +517,7 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { @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 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { 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 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { 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 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { 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 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { 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 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { 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 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) assertThat(latest!!.isValidated).isFalse() - job.cancel() } @Test @@ -669,8 +647,7 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { 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 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) assertThat(latest).isEqualTo(SUB_1_ID) - job.cancel() } @Test @@ -689,8 +665,7 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { 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 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { 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 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) assertThat(latest).isNull() - job.cancel() } @Test @@ -728,8 +700,7 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { // 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 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { 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 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) assertThat(latest).isNull() - - job.cancel() } @Test @@ -862,6 +829,7 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { } @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 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { } @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 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { 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 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { 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/utils/src/com/android/settingslib/notification/modes/ZenIconLoaderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/settingslib/notification/modes/ZenIconLoaderKosmos.kt new file mode 100644 index 000000000000..8541d7704517 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/settingslib/notification/modes/ZenIconLoaderKosmos.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.notification.modes + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import com.google.common.util.concurrent.MoreExecutors + +val Kosmos.zenIconLoader by Fixture { ZenIconLoader(MoreExecutors.newDirectExecutorService()) } 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 4ad046cc095e..629fda6b610c 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.log.logcatLogBuffer 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 @@ val Kosmos.communalInteractor by Fixture { sceneInteractor = sceneInteractor, logBuffer = logcatLogBuffer("CommunalInteractor"), tableLogBuffer = mock(), + managedProfileController = fakeManagedProfileController ) } 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 e96aeada0212..5753c6c6e8ea 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 javax.inject.Inject 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 @@ class FakeDeviceEntryFaceAuthRepository @Inject constructor() : DeviceEntryFaceA 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 000000000000..82a5311269f0 --- /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 c6b5ed0b608f..007d2297e387 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 @@ val Kosmos.keyguardEnabledInteractor by 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 8e8f4b69e401..aa94c368e8f1 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 @@ val Kosmos.keyguardTransitionInteractor: KeyguardTransitionInteractor by 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 b9443bcaf650..7cf4083e8407 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 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 @@ val Kosmos.keyguardRootViewModel by Fixture { 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 a05e60672ef7..4196e54a085d 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 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi 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 953363d010fe..851a378f3165 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.deviceEntryInteractor 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 @@ class KosmosJavaAdapter() { 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 7dfe8027d783..7bc2483961bf 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.scenes by Fixture { fakeScenes } 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 000000000000..24481bf1f67e --- /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/phone/ManagedProfileControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ManagedProfileControllerKosmos.kt new file mode 100644 index 000000000000..ef04b9d907f1 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ManagedProfileControllerKosmos.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.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 + +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 8229575a128f..e7be639cf92a 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.SignalIcon 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 @@ class FakeMobileConnectionsRepository( 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 66be7e7a7a7e..61b53c9a2067 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 @@ val Kosmos.zenModeInteractor by Fixture { 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 2dbac670b298..0089199cfb88 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 @@ public class FakeStatusBarIconController extends BaseLeakChecker<IconManager> @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 83adc799b471..ad1292e0af0c 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 @@ object TestMediaDevicesFactory { ): MediaDevice = mock { whenever(name).thenReturn(deviceName) whenever(icon).thenReturn(deviceIcon) + whenever(deviceType).thenReturn(MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE) } fun wiredMediaDevice( @@ -77,6 +78,18 @@ object TestMediaDevicesFactory { 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 000000000000..751553b3acb9 --- /dev/null +++ b/ravenwood/.gitignore @@ -0,0 +1 @@ +*.bak diff --git a/ravenwood/TEST_MAPPING b/ravenwood/TEST_MAPPING index 7e2ee3e2a052..469759bc4b40 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,12 +34,71 @@ } ], "ravenwood-presubmit": [ + // AUTO-GENERATED-START + // DO NOT MODIFY MANUALLY + // Use scripts/update-test-mapping.sh to update it. { - "name": "RavenwoodMinimumTest", + "name": "AdServicesSharedLibrariesUnitTestsRavenwood", "host": true }, { - "name": "RavenwoodMockitoTest", + "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 }, { @@ -52,12 +106,65 @@ "host": true }, { - "name": "RavenwoodResApkTest", + "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 + }, + { + "name": "RavenwoodMockitoTest", + "host": true + }, + { + "name": "RavenwoodResApkTest", + "host": true + }, + { + "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 8dadd398ad55..09a0aa8dbaa2 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 @@ public class CallTracker { /** * 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 @@ public class CallTracker { * 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 000000000000..9d878f444e5e --- /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 000000000000..7e396c2080eb --- /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 1da93eba94f7..f237ba908507 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 class RavenwoodAwareTestRunnerHook { */ 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 000000000000..a84f16f619d3 --- /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 2b55ac52ab75..7a1609552ede 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 com.android.ravenwood.common.RavenwoodCommonUtils.isOnRavenwood; 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.Orderable; 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 @@ import java.lang.reflect.InvocationTargetException; /** * 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 @@ public class RavenwoodAwareTestRunner extends Runner implements Filterable, Orde 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 class RavenwoodAwareTestRunner extends Runner implements Filterable, Orde } 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; - } + try { + mTestClass = new TestClass(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; + } - // 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; - } + // 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(); + onRunnerInitializing(); - try { - log("Initializing the inner runner: " + realRunner); + try { + log("Initializing the inner runner: " + realRunnerClass); - mRealRunner = realRunner.getConstructor(Class.class).newInstance(testClass); + mRealRunner = instantiateRealRunner(realRunnerClass, testClass); + mDescription = mRealRunner.getDescription(); - } catch (InstantiationException | IllegalAccessException - | InvocationTargetException | NoSuchMethodException e) { - throw logAndFail("Failed to instantiate " + realRunner, e); + } 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 @@ public class RavenwoodAwareTestRunner extends Runner implements Filterable, Orde log("onRunnerInitializing"); - RavenwoodAwareTestRunnerHook.onRunnerInitializing(this, mTestClsas); + RavenwoodAwareTestRunnerHook.onRunnerInitializing(this, mTestClass); // Hook point to allow more customization. runAnnotatedMethodsOnRavenwood(RavenwoodTestRunnerInitializing.class, null); @@ -230,7 +271,7 @@ public class RavenwoodAwareTestRunner extends Runner implements Filterable, Orde @Override public Description getDescription() { - return mRealRunner.getDescription(); + return mDescription; } @Override @@ -241,6 +282,10 @@ public class RavenwoodAwareTestRunner extends Runner implements Filterable, Orde return; } + if (maybeReportExceptionFromConstructor(notifier)) { + return; + } + sCurrentRunner.set(this); try { runWithHooks(getDescription(), Scope.Runner, Order.First, @@ -250,6 +295,18 @@ public class RavenwoodAwareTestRunner extends Runner implements Filterable, Orde } } + /** 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 000000000000..478503b699a0 --- /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 fb9b823ee93b..05f3fdffdaa7 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 000000000000..be15b711b980 --- /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 000000000000..b6cf5b857682 --- /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 d8366c58c50d..34239b826c67 100644 --- a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt +++ b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt @@ -46,6 +46,7 @@ android.util.EmptyArray 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.ColorSpace 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 3a7fab39e4ac..0dcd271562d1 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 e92ef7216e25..a38512ec9f2d 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 @@ data class RavenizerStats( /** 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 @@ data class RavenizerStats( RavenizerStats{ totalTime=$totalTime, loadStructureTime=$loadStructureTime, + validationTime=$validationTime, totalProcessTime=$totalProcessTime, totalConversionTime=$totalConversionTime, totalCopyTime=$totalCopyTime, @@ -84,16 +88,44 @@ data class RavenizerStats( 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 e85e3be31b77..e8341e5ceb06 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 @@ class RavenizerOptions( /** 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 @@ class RavenizerOptions( "--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 @@ class RavenizerOptions( 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 e026e7ab3679..1aa70c08c254 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 @@ data class TypeHolder( 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 ruleAnotType = TypeHolder(org.junit.Rule::class.java) 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 @@ fun String.shouldByBypassed(): Boolean { 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 000000000000..27092d28ae5e --- /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 25cad0213b72..eaef2cf6a956 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.RavenizerInternalException 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 @@ class RunnerRewritingAdapter private constructor( av.visit("value", ravenwoodTestRunnerType.type) av.visitEnd() } - log.d("Processed ${classInternalName.toHumanReadableClassName()}") + log.i("Update the @RunWith: ${classInternalName.toHumanReadableClassName()}") } /* @@ -435,7 +436,19 @@ class RunnerRewritingAdapter private constructor( 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 7cbb97e56b01..f1a8b5a96080 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.IAccessibilityInteractionConnectionCallback; 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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub 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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub 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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } @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 83f57b2bf31c..950246ff20ac 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 final class AccessibilityCheckerManager { 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 @@ public final class AccessibilityCheckerManager { 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 eb24b027dc3a..a739304fc269 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 @@ public class AccessibilityCheckerUtils { 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 Set.of(); + 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/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerService.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerService.java index 954651d9ef0a..a2d467c2085c 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 @@ public class AppFunctionManagerService extends SystemService { @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 53885fc5799e..32b8d6b476c3 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.IExecuteAppFunctionCallback; 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 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { 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 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { 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 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { } ); } 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 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { @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 000000000000..c01fe311e9ca --- /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 4678a165b83f..5fd282df97ec 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 @@ public class ObservableUuidStore { } 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 47203fbf2ef0..fbe593fd3df1 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_REBOOT; 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.text.TextUtils; 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 android.util.Xml; 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.IOException; 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 @@ public class PackageWatchdog { /** 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 bba97fad0fc9..cadceb52dedd 100644 --- a/services/core/java/com/android/server/RescueParty.java +++ b/services/core/java/com/android/server/RescueParty.java @@ -18,7 +18,7 @@ package com.android.server; 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 @@ public class RescueParty { 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 @@ public class RescueParty { 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 @@ public class RescueParty { 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 168ec052e67d..4e24cf38fe73 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -790,6 +790,7 @@ public class AudioService extends IAudioService.Stub private final BroadcastReceiver mReceiver = new AudioServiceBroadcastReceiver(); private final Executor mAudioServerLifecycleExecutor; + private long mSysPropListenerNativeHandle; private final List<Future> mScheduledPermissionTasks = new ArrayList(); private IMediaProjectionManager mProjectionService; // to validate projection token @@ -10640,7 +10641,7 @@ public class AudioService extends IAudioService.Stub }, UPDATE_DELAY_MS, TimeUnit.MILLISECONDS)); } }; - mAudioSystem.listenForSystemPropertyChange( + mSysPropListenerNativeHandle = mAudioSystem.listenForSystemPropertyChange( PermissionManager.CACHE_KEY_PACKAGE_INFO, task); } else { @@ -14713,6 +14714,7 @@ public class AudioService extends IAudioService.Stub @Override /** @see AudioManager#permissionUpdateBarrier() */ public void permissionUpdateBarrier() { + mAudioSystem.triggerSystemPropertyUpdate(mSysPropListenerNativeHandle); List<Future> snapshot; synchronized (mScheduledPermissionTasks) { snapshot = List.copyOf(mScheduledPermissionTasks); diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java index d083c68c4c2c..5cabddea9c17 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 @@ public class AudioSystemAdapter implements AudioSystem.RoutingUpdateCallback, 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 f5af5ea3eab4..bc58501f5b59 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 class BiometricFrameworkStatsLogger { 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 class BiometricFrameworkStatsLogger { 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 000000000000..3eb3380a57a0 --- /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 dc611fc1d25b..334dda079e7a 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 @@ public final class DisplayBrightnessState { private final boolean mIsUserInitiatedChange; + private @BrightnessInfo.BrightnessMaxReason int mBrightnessMaxReason; + private DisplayBrightnessState(Builder builder) { mBrightness = builder.getBrightness(); mHdrBrightness = builder.getHdrBrightness(); @@ -64,6 +67,7 @@ public final class DisplayBrightnessState { mBrightnessEvent = builder.getBrightnessEvent(); mBrightnessAdjustmentFlag = builder.getBrightnessAdjustmentFlag(); mIsUserInitiatedChange = builder.isUserInitiatedChange(); + mBrightnessMaxReason = builder.getBrightnessMaxReason(); } /** @@ -159,6 +163,13 @@ public final class DisplayBrightnessState { 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 @@ public final class DisplayBrightnessState { .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 @@ public final class DisplayBrightnessState { == otherState.shouldUpdateScreenBrightnessSetting() && Objects.equals(mBrightnessEvent, otherState.getBrightnessEvent()) && mBrightnessAdjustmentFlag == otherState.getBrightnessAdjustmentFlag() - && mIsUserInitiatedChange == otherState.isUserInitiatedChange(); + && mIsUserInitiatedChange == otherState.isUserInitiatedChange() + && mBrightnessMaxReason == otherState.getBrightnessMaxReason(); } @Override @@ -221,7 +235,7 @@ public final class DisplayBrightnessState { mShouldUseAutoBrightness, mIsSlowChange, mMaxBrightness, mMinBrightness, mCustomAnimationRate, mShouldUpdateScreenBrightnessSetting, mBrightnessEvent, mBrightnessAdjustmentFlag, - mIsUserInitiatedChange); + mIsUserInitiatedChange, mBrightnessMaxReason); } /** @@ -245,12 +259,11 @@ public final class DisplayBrightnessState { 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 @@ public final class DisplayBrightnessState { builder.setBrightnessEvent(state.getBrightnessEvent()); builder.setBrightnessAdjustmentFlag(state.getBrightnessAdjustmentFlag()); builder.setIsUserInitiatedChange(state.isUserInitiatedChange()); + builder.setBrightnessMaxReason(state.getBrightnessMaxReason()); return builder; } @@ -506,5 +520,21 @@ public final class DisplayBrightnessState { 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 a887f6d241e6..047eb29efcc0 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 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // 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 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call 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 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call 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 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mBrightnessRangeController.getTransitionPoint()); changed |= mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.brightnessMaxReason, - mBrightnessClamperController.getBrightnessMaxReason()); + maxReason); return changed; } } @@ -2926,7 +2929,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call 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 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call 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 59fffe7ee393..8ee708562565 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 @@ public class BrightnessClamperController { 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 @@ public class BrightnessClamperController { 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 bbe7b2b038c9..3c7b9d37e4c6 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 @@ public class HdmiControlService extends SystemService { 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 e3061a7c39ae..41351613331d 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 @@ public class BackgroundUserSoundNotifier { 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 2124ff6b07e0..ff9c3e5f467b 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.ApplicationExitInfo; 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 @@ public class PackageManagerService implements PackageSender, TestUtilityService // 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 @@ public class PackageManagerService implements PackageSender, TestUtilityService 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 a683a8c54849..13901c1f2c23 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 @@ public class UserManagerService extends IUserManager.Stub { // 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/permission/AccessCheckDelegate.java b/services/core/java/com/android/server/pm/permission/AccessCheckDelegate.java index f518769bdb35..e9cb279439a6 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 @@ public interface AccessCheckDelegate extends CheckPermissionDelegate, CheckOpsDe @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); + } + + 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); } - return superImpl.apply(code, attributionSource, shouldCollectAsyncNotedOp, - message, shouldCollectMessage, skiProxyOperation); } @Override diff --git a/services/core/java/com/android/server/policy/ModifierShortcutManager.java b/services/core/java/com/android/server/policy/ModifierShortcutManager.java index 7ed89728a005..027e69cbc09b 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.text.TextUtils; 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 @@ public class ModifierShortcutManager { 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 @@ public class ModifierShortcutManager { 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 @@ public class ModifierShortcutManager { 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 @@ public class ModifierShortcutManager { // 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 @@ public class ModifierShortcutManager { 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 (shortcutIntent == null) { + // Next try the primary character on that key. + shortcutChar = Character.toLowerCase(kcm.getDisplayLabel(keyCode)); + if (shortcutChar == 0) { + return null; + } + shortcutIntent = shortcutMap.get(shortcutChar); } - } - 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 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 @@ public class ModifierShortcutManager { 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 (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 { + 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) { - mShiftComponentShortcuts.put(shortcutChar, componentName); + mShiftRoleShortcuts.put(shortcutChar, roleName); } else { - mComponentShortcuts.put(shortcutChar, componentName); + 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 @@ public class ModifierShortcutManager { @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 class ModifierShortcutManager { */ 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); + if (modifierShortcutManagerRefactor()) { + for (Bookmark b : mBookmarks.values()) { + KeyboardShortcutInfo info = shortcutInfoFromIntent( + b.getShortcutChar(), b.getIntent(mContext), b.isShift()); + 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); + } 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 < mShiftRoleShortcuts.size(); i++) { - String role = mShiftRoleShortcuts.valueAt(i); - KeyboardShortcutInfo info = shortcutInfoFromIntent( - (char) (mShiftRoleShortcuts.keyAt(i)), getRoleLaunchIntent(role), true); - if (info != null) { - shortcuts.add(info); + for (int i = 0; i < mShiftCategoryShortcuts.size(); i++) { + KeyboardShortcutInfo info = shortcutInfoFromIntent( + (char) (mShiftCategoryShortcuts.keyAt(i)), + mShiftCategoryShortcuts.valueAt(i), + true); + if (info != null) { + shortcuts.add(info); + } } - } - if (modifierShortcutManagerMultiuser()) { - for (int i = 0; i < mComponentShortcuts.size(); i++) { - ComponentName component = mComponentShortcuts.valueAt(i); + for (int i = 0; i < mRoleShortcuts.size(); i++) { + String role = mRoleShortcuts.valueAt(i); KeyboardShortcutInfo info = shortcutInfoFromIntent( - (char) (mComponentShortcuts.keyAt(i)), - resolveComponentNameIntent(component), + (char) (mRoleShortcuts.keyAt(i)), + getRoleLaunchIntent(role), 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 < mShiftRoleShortcuts.size(); i++) { + String role = mShiftRoleShortcuts.valueAt(i); KeyboardShortcutInfo info = shortcutInfoFromIntent( - (char) (mShiftComponentShortcuts.keyAt(i)), - resolveComponentNameIntent(component), + (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 @@ public class ModifierShortcutManager { void dump(String prefix, PrintWriter pw) { IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ", prefix); ipw.println("ModifierShortcutManager shortcuts:"); + if (modifierShortcutManagerRefactor()) { + ipw.increaseIndent(); + 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 < 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); + + } - 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); + 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; } - 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); + public char getShortcutChar() { + return mShortcutChar; } - 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); + public boolean isShift() { + return mShift; } - for (int i = 0; i < mShiftShortcuts.size(); i++) { - char shortcutChar = (char) mShiftShortcuts.keyAt(i); - Intent intent = mShiftShortcuts.valueAt(i); - ipw.println("SHIFT+" + shortcutChar + " " + intent); + public abstract Intent getIntent(Context context); + public void clearIntent() { + mIntent = null; } - 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); + } + + 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; } - 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); + 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 1c786e668c7a..68026ea9094a 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 @@ package com.android.server.rollback; 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.PowerManager; 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 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve 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 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve } 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/tunerresourcemanager/CasResource.java b/services/core/java/com/android/server/tv/tunerresourcemanager/CasResource.java index 440d2514537c..eb5361c84b89 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 @@ import java.util.Set; * @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 @@ public class CasResource { 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 class CasResource { */ 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 31149f3590b8..5cef729627f0 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 @@ public final class CiCamResource extends CasResource { * 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 0afb049d31c7..9229f7f016bc 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 @@ public class TunerResourceManagerService extends SystemService implements IBinde @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 @@ public class TunerResourceManagerService extends SystemService implements IBinde 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 @@ public class TunerResourceManagerService extends SystemService implements IBinde 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 @@ public class TunerResourceManagerService extends SystemService implements IBinde 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 @@ public class TunerResourceManagerService extends SystemService implements IBinde 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 @@ public class TunerResourceManagerService extends SystemService implements IBinde 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 @@ public class TunerResourceManagerService extends SystemService implements IBinde @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 @@ public class TunerResourceManagerService extends SystemService implements IBinde 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 @@ public class TunerResourceManagerService extends SystemService implements IBinde 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; - } + // Grant frontend when there is unused resource. + if (grantingFrontend != null) { + updateFrontendClientMappingOnNewGrant(grantingFrontend.getHandle(), + request.clientId); + frontendHandle[0] = grantingFrontend.getHandle(); + 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; + // 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; } - frontendHandle[0] = inUseLowestPriorityFrHandle; - updateFrontendClientMappingOnNewGrant( - inUseLowestPriorityFrHandle, request.clientId); - return true; } return false; @@ -1192,165 +1171,257 @@ public class TunerResourceManagerService extends SystemService implements IBinde } @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 + ")"); } + int[] reclaimOwnerId = new int[1]; + if (!claimLnb(request, lnbHandle, reclaimOwnerId)) { + return false; + } + if (lnbHandle[0] == TunerResourceManager.INVALID_RESOURCE_HANDLE) { + return false; + } + if (reclaimOwnerId[0] != INVALID_CLIENT_ID) { + if (!reclaimResource(reclaimOwnerId[0], + TunerResourceManager.TUNER_RESOURCE_TYPE_LNB)) { + return false; + } + 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; - 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()); + 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 (grantingLnbHandle > -1) { - lnbHandle[0] = grantingLnbHandle; - updateLnbClientMappingOnNewGrant(grantingLnbHandle, request.clientId); - return true; - } + // 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 (inUseLowestPriorityLnbHandle > TunerResourceManager.INVALID_RESOURCE_HANDLE - && ((requestClient.getPriority() > currentLowestPriority) || ( - (requestClient.getPriority() == currentLowestPriority) && isRequestFromSameProcess))) { - if (!reclaimResource(getLnbResource(inUseLowestPriorityLnbHandle).getOwnerClientId(), - TunerResourceManager.TUNER_RESOURCE_TYPE_LNB)) { - return false; + // 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; } - lnbHandle[0] = inUseLowestPriorityLnbHandle; - updateLnbClientMappingOnNewGrant(inUseLowestPriorityLnbHandle, request.clientId); - 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()); + if (reclaimOwnerId[0] != INVALID_CLIENT_ID) { + if (!reclaimResource(reclaimOwnerId[0], + TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION)) { + return false; + } + synchronized (mLock) { + if (getCasResource(request.casSystemId).isFullyUsed()) { + Slog.e(TAG, "Reclaimed cas still fully used"); + return false; + } + updateCasClientMappingOnNewGrant(request.casSystemId, request.clientId); } } + return true; + } - // 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, - TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION)) { - return false; + 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; } - casSessionHandle[0] = generateResourceHandle( - TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION, cas.getSystemId()); - updateCasClientMappingOnNewGrant(request.casSystemId, request.clientId); - 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 @@ public class TunerResourceManagerService extends SystemService implements IBinde } @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 @@ public class TunerResourceManagerService extends SystemService implements IBinde } @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; + int[] reclaimOwnerId = new int[1]; + if (!claimDemux(request, demuxHandle, reclaimOwnerId)) { + return false; } - - demuxHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE; - ClientProfile requestClient = getClientProfile(request.clientId); - - if (requestClient == null) { + 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(); - - // 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) { - int numOfSupportedCaps = dr.getNumOfCaps(); - boolean shouldUpdate = false; - // update lowest priority - if (currentLowestPriority > priority) { - currentLowestPriority = priority; - isRequestFromSameProcess = (requestClient.getProcessId() - == (getClientProfile(dr.getOwnerClientId())).getProcessId()); + 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); + } + + // 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; + } - // reset the smallest caps when lower priority resource is found - smallestNumOfSupportedCapsInUse = numOfSupportedCaps; + 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(); - 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) { + // look for the demux with minimum caps supporting the desired caps + if (smallestNumOfSupportedCaps > numOfSupportedCaps) { + smallestNumOfSupportedCaps = numOfSupportedCaps; + grantingDemux = dr; + } + } 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; } - } - if (shouldUpdate) { - inUseLowestPriorityDrHandle = dr.getHandle(); } } } } - } - // Grant demux when there is unused resource. - if (grantingDemuxHandle != TunerResourceManager.INVALID_RESOURCE_HANDLE) { - demuxHandle[0] = grantingDemuxHandle; - updateDemuxClientMappingOnNewGrant(grantingDemuxHandle, request.clientId); - return true; - } + // Grant demux when there is unused resource. + if (grantingDemux != null) { + updateDemuxClientMappingOnNewGrant(grantingDemux.getHandle(), request.clientId); + demuxHandle[0] = grantingDemux.getHandle(); + 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; + // 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; } - demuxHandle[0] = inUseLowestPriorityDrHandle; - updateDemuxClientMappingOnNewGrant( - inUseLowestPriorityDrHandle, request.clientId); - return true; } return false; @@ -1792,7 +1918,9 @@ public class TunerResourceManagerService extends SystemService implements IBinde return; } - mListeners.put(clientId, record); + synchronized (mLock) { + mListeners.put(clientId, record); + } } @VisibleForTesting @@ -1808,33 +1936,44 @@ public class TunerResourceManagerService extends SystemService implements IBinde // 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; + synchronized (mLock) { + ClientProfile profile = getClientProfile(reclaimingClientId); + if (profile == null) { + return true; + } + shareFeClientIds = profile.getShareFeClientIds(); } - Set<Integer> shareFeClientIds = profile.getShareFeClientIds(); + ResourcesReclaimListenerRecord listenerRecord = null; 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; + 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); + } } - clearAllResourcesAndClientMapping(getClientProfile(clientId)); } 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); + } + if (listenerRecord != null) { + try { + listenerRecord.getListener().onReclaimResources(); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to reclaim resources on client " + reclaimingClientId, e); + } } - clearAllResourcesAndClientMapping(profile); + return true; } @@ -2258,6 +2397,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde 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 @@ public class TunerResourceManagerService extends SystemService implements IBinde 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 @@ public class TunerResourceManagerService extends SystemService implements IBinde 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 000000000000..b4d3862431f1 --- /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 fe0cf5909970..ea4bd0182ea3 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 @@ final class HalVibration extends Vibration { @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 @@ final class HalVibration extends Vibration { // 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 @@ final class HalVibration extends Vibration { 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 @@ final class HalVibration extends Vibration { ? 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 4e58b9a35243..83e05f4eff9f 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.util.SparseArray; 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 @@ final class InputDeviceDelegate implements InputManager.InputDeviceListener { * * @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 aa4b9f3e9d7d..9a0479337471 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.os.vibrator.VibrationEffectSegment; 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 @@ abstract class Vibration { 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 final int mProtoEnumValue; - - Status(int value) { - mProtoEnumValue = value; - } - - public int getProtoEnumValue() { - return mProtoEnumValue; - } - } + private VibrationSession.Status mStatus; - 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; } - /** Return true if vibration is a repeating vibration. */ - abstract boolean isRepeating(); + VibrationSession.Status getStatus() { + return mStatus; + } + + /** Return true is current status is different from {@link VibrationSession.Status#RUNNING}. */ + boolean hasEnded() { + return mStatus != VibrationSession.Status.RUNNING; + } /** - * 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. + * Set the {@link VibrationSession} of this vibration and reports the current system time as + * this vibration end time, for debugging purposes. * - * 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}. + * <p>This method will only accept given value if the current status is {@link + * VibrationSession.Status#RUNNING}. */ - static 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; + 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); + } - @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); - } + /** Return true if vibration is a repeating vibration. */ + abstract boolean isRepeating(); - @Override - public int hashCode() { - return Objects.hash(attrs, uid, deviceId, opPkg, reason); - } + /** Return {@link VibrationSession.DebugInfo} with read-only debug data about this vibration. */ + abstract VibrationSession.DebugInfo getDebugInfo(); - @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 @@ abstract class Vibration { * 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 @@ abstract class Vibration { 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 @@ abstract class Vibration { } @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 @@ abstract class Vibration { + ", 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 @@ abstract class Vibration { 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 @@ abstract class Vibration { 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 000000000000..5640b49b28d8 --- /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 0d6778c18759..69cdcf47680d 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.GuardedBy; 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 @@ final class VibrationSettings { /** * 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 @@ final class VibrationSettings { * * @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 @@ final class VibrationSettings { // 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 @@ final class VibrationSettings { * {@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 8179d6aea9ca..fc0c6e7bf05e 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 @@ final class VibrationStats { * @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 @@ final class VibrationStats { * <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 @@ final class VibrationStats { 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 7152844cc772..5137d1938332 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.Slog; 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 @@ final class VibrationStepConductor implements IBinder.DeathRecipient { } /** - * 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 @@ final class VibrationStepConductor implements IBinder.DeathRecipient { } // 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 @@ final class VibrationStepConductor implements IBinder.DeathRecipient { 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 @@ final class VibrationStepConductor implements IBinder.DeathRecipient { 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 cfb4c74fcbfc..ab4a4d8fc08d 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 android.util.Slog; 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 @@ final class VibrationThread extends Thread { runCurrentVibrationWithWakeLockAndDeathLink(); } finally { clientVibrationCompleteIfNotAlready( - new Vibration.EndInfo(Vibration.Status.FINISHED_UNEXPECTED)); + new Vibration.EndInfo(Status.FINISHED_UNEXPECTED)); } } finally { mWakeLock.release(); @@ -259,7 +260,7 @@ final class VibrationThread extends Thread { } 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 de5e662f2a71..3a814cd80836 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 @@ final class VibratorControlService extends IVibratorControlService.Stub { } /** - * 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 c143bebdd4a5..799934af54c0 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.GuardedBy; 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 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { @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 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { // 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 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { 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 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } 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 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { 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 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { // 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 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { 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 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { 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 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { 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 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { 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 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { 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 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { 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 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } if (mCurrentExternalVibration != null && shouldCancelVibration( - mCurrentExternalVibration.externalVibration.getVibrationAttributes(), + mCurrentExternalVibration.getCallerInfo().attrs, usageFilter)) { - mCurrentExternalVibration.mute(); + mCurrentExternalVibration.notifyEnded(); endExternalVibrateLocked( cancelledByUserInfo, /* continueExternalControl= */ false); } @@ -860,7 +857,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { : 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 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { // 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 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { 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 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } @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 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } } - @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 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { 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 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { 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 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { */ @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 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { 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 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } @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 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { * {@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 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { /** 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 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { * 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 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { */ 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 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } } - /** 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 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { 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 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } /** - * 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 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { 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 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { 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 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { @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 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { 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 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { // 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 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { 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 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { 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 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { 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 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } 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/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 235a2115a964..ebdf52cc9037 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A /** 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } + /** 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A 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 197bd5a308cd..ab02d49e5f96 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.Retention; 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 @@ public class AppTransitionController { 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 @@ public class AppTransitionController { } /** + * 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/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java index 20c5f02aaee2..a5cea34bf79d 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 @@ public class BackgroundActivityStartController { (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 4a870a3a5b6e..5f5365dca1e9 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.SystemClock; 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 @@ class BackgroundLaunchProcessController { 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/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java index f8665c70c61d..432089ff2fcf 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 com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; 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 @@ class RemoteAnimationController implements DeathRecipient { } 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 e4a31760e5ed..5aa34d22f00f 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.ArrayMap; 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 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr 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 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } @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 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } } + /** + * 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 6bfa32a97ddb..7f6dc8472813 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 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { * 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 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { 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 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { } // 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 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { 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 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { 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/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 979b3a5697cf..e3ceb3348de7 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 @@ public class WindowManagerService extends IWindowManager.Stub } boolean allWindowsDrawn = false; synchronized (mGlobalLock) { - if (displayId == DEFAULT_DISPLAY + if ((displayId == DEFAULT_DISPLAY || 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 4abbdee65acb..886ae7ad7e50 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { 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) { @@ -17900,15 +17914,28 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { }); } + 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; } @@ -21749,8 +21776,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { */ @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; @@ -21762,7 +21787,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } 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/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index c5c371ff85d5..13c436d1216d 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.ApplicationErrorReport; 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 @@ public final class SystemServer implements Dumpable { 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 000000000000..e5f41472df7b --- /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 a4ef629492e7..7ffd0eca9b96 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.NonNull; 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 com.android.server.SystemService; 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 @@ public class SupervisionService extends ISupervisionManager.Stub { } @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 000000000000..3aba24a3d4a5 --- /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 f690b1bbfccf..2d4a29b3ed6f 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 @@ package com.android.server.display; 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 @@ public class DisplayBrightnessStateTest { .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 0ce9233b4b6f..0a03702d4688 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 static org.mockito.Mockito.when; 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 @@ public class BrightnessClamperControllerTest { } @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 000000000000..6f38fcae182c --- /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 c8cbbb5957ec..2e6c93cb92aa 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.WindowManager; 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.ArgumentMatchers; 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 @@ public class AccessibilityManagerServiceTest { @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 @@ public class AccessibilityManagerServiceTest { .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 4ec2fb9ffe6e..cdaeade41159 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 @@ public class AccessibilityCheckerUtilsTest { 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 @@ public class AccessibilityCheckerUtilsTest { 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/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java index b4cc3434e013..698bda335f83 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 com.android.server.wm.ActivityTaskManagerInternal; import java.io.File; import java.io.IOException; +import java.util.List; import java.util.Map; /** @@ -325,6 +326,11 @@ public class DevicePolicyManagerServiceTestable extends DevicePolicyManagerServi } @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 b7483d67563c..cb4269a205e4 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.PasswordMetrics; 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 @@ public class DevicePolicyManagerTest extends DpmTestBase { } @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 76aa40c04e3d..2e200a9268f5 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 class MockSystemServices { 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 @@ public class MockSystemServices { 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 class MockSystemServices { } } + 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/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java index 963b27e010fa..bf58443194e5 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.TunerFrontendInfo; 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 @@ public class TunerResourceManagerServiceTest { 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 @@ public class TunerResourceManagerServiceTest { } @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 @@ public class TunerResourceManagerServiceTest { 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 @@ public class TunerResourceManagerServiceTest { 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 @@ public class TunerResourceManagerServiceTest { 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 @@ public class TunerResourceManagerServiceTest { .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 @@ public class TunerResourceManagerServiceTest { 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 @@ public class TunerResourceManagerServiceTest { 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 @@ public class TunerResourceManagerServiceTest { 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 @@ public class TunerResourceManagerServiceTest { 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 @@ public class TunerResourceManagerServiceTest { .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 @@ public class TunerResourceManagerServiceTest { .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 @@ public class TunerResourceManagerServiceTest { .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 @@ public class TunerResourceManagerServiceTest { 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 @@ public class TunerResourceManagerServiceTest { .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 @@ public class TunerResourceManagerServiceTest { 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 @@ public class TunerResourceManagerServiceTest { .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 @@ public class TunerResourceManagerServiceTest { 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 @@ public class TunerResourceManagerServiceTest { 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 @@ public class TunerResourceManagerServiceTest { 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 @@ public class TunerResourceManagerServiceTest { 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 @@ public class TunerResourceManagerServiceTest { } @Test - public void shareFrontendTest_FrontendWithExclusiveGroupReadyToShare() { + public void shareFrontendTest_FrontendWithExclusiveGroupReadyToShare() throws RemoteException { /**** Register Clients and Set Priority ****/ + 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); - // 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); - - 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 @@ public class TunerResourceManagerServiceTest { // 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 @@ public class TunerResourceManagerServiceTest { .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 @@ public class TunerResourceManagerServiceTest { // 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 @@ public class TunerResourceManagerServiceTest { .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 @@ public class TunerResourceManagerServiceTest { /**** 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 @@ public class TunerResourceManagerServiceTest { // Predefined second frontend request request = tunerFrontendRequest( - ownerClientId1[0] /*clientId*/, + ownerClient1.getId() /*clientId*/, FrontendSettings.TYPE_DVBT); // Second request call @@ -1170,43 +1076,35 @@ public class TunerResourceManagerServiceTest { // 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 @@ public class TunerResourceManagerServiceTest { 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 @@ public class TunerResourceManagerServiceTest { // 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 @@ public class TunerResourceManagerServiceTest { .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 @@ public class TunerResourceManagerServiceTest { 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 f3ecfcc933b4..66788b6d2444 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 android.view.InputDevice; 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 class InputDeviceDelegateTest { 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 @@ public class InputDeviceDelegateTest { 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 index 84f8412fb4b5..f69d1c42eb14 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 org.junit.Test; 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 38cd49daf99d..c7a136ad0f9f 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.FakeSettingsProvider; 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 @@ public class VibrationSettingsTest { if (expectedAllowedVibrations.contains(usage)) { assertVibrationNotIgnoredForUsage(usage); } else { - assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_BACKGROUND); + assertVibrationIgnoredForUsage(usage, Status.IGNORED_BACKGROUND); } } } @@ -350,7 +352,7 @@ public class VibrationSettingsTest { 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 @@ public class VibrationSettingsTest { 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 @@ public class VibrationSettingsTest { 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 @@ public class VibrationSettingsTest { if (expectedAllowedVibrations.contains(usage)) { assertVibrationNotIgnoredForUsage(usage); } else { - assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_FOR_POWER); + assertVibrationIgnoredForUsage(usage, Status.IGNORED_FOR_POWER); } } } @@ -426,7 +428,7 @@ public class VibrationSettingsTest { 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 @@ public class VibrationSettingsTest { 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 @@ public class VibrationSettingsTest { 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 @@ public class VibrationSettingsTest { 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 @@ public class VibrationSettingsTest { 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 @@ public class VibrationSettingsTest { 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 @@ public class VibrationSettingsTest { 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 @@ public class VibrationSettingsTest { 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 @@ public class VibrationSettingsTest { 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 @@ public class VibrationSettingsTest { 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 @@ public class VibrationSettingsTest { 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 @@ public class VibrationSettingsTest { // 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 @@ public class VibrationSettingsTest { } 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 @@ public class VibrationSettingsTest { 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 @@ public class VibrationSettingsTest { } 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 @@ public class VibrationSettingsTest { 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 bfdaa785a669..31cc50f18299 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 androidx.test.filters.LargeTest; 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 @@ public class VibrationThreadTest { waitForCompletion(); verify(mControllerCallbacks, never()).onComplete(anyInt(), eq(vibrationId)); - verifyCallbacksTriggered(vibrationId, Vibration.Status.IGNORED_UNSUPPORTED); + verifyCallbacksTriggered(vibrationId, Status.IGNORED_UNSUPPORTED); } @Test @@ -202,7 +204,7 @@ public class VibrationThreadTest { waitForCompletion(); verify(mControllerCallbacks, never()).onComplete(anyInt(), eq(vibrationId)); - verifyCallbacksTriggered(vibrationId, Vibration.Status.IGNORED_UNSUPPORTED); + verifyCallbacksTriggered(vibrationId, Status.IGNORED_UNSUPPORTED); } @Test @@ -216,7 +218,7 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { // 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 @@ public class VibrationThreadTest { // 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 @@ public class VibrationThreadTest { // 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { // 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 @@ public class VibrationThreadTest { // 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 @@ public class VibrationThreadTest { // 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 @@ public class VibrationThreadTest { // 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 @@ public class VibrationThreadTest { // 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { // 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 @@ public class VibrationThreadTest { // 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { // 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 @@ public class VibrationThreadTest { // 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 @@ public class VibrationThreadTest { .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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { .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 3466bbb26fc8..cd057b619000 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 static org.junit.Assert.assertTrue; 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 @@ public class VibratorFrameworkStatsLoggerTest { } 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 4afb56265563..4012575cda88 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.internal.util.test.FakeSettingsProviderRule; 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 @@ public class VibratorManagerServiceTest { 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 @@ public class VibratorManagerServiceTest { 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 @@ public class VibratorManagerServiceTest { 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 @@ public class VibratorManagerServiceTest { 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 @@ public class VibratorManagerServiceTest { } @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 @@ public class VibratorManagerServiceTest { } @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 @@ public class VibratorManagerServiceTest { } @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 @@ public class VibratorManagerServiceTest { 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 @@ public class VibratorManagerServiceTest { 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 @@ public class VibratorManagerServiceTest { 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 @@ public class VibratorManagerServiceTest { 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 @@ public class VibratorManagerServiceTest { 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 @@ public class VibratorManagerServiceTest { 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 @@ public class VibratorManagerServiceTest { 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 @@ public class VibratorManagerServiceTest { 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 d147325921bf..0575d98b65ec 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.content.res.XmlResourceParser; 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 androidx.test.filters.SmallTest; 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 @@ import java.util.Collections; */ @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 71f90a2560fd..43171f847818 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.app.role.RoleManager; 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 @@ public class ModifierShortcutTests extends ShortcutKeyTestBase { * 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 @@ public class ModifierShortcutTests extends ShortcutKeyTestBase { * 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 af4394acce67..0c1fbf3cb3d7 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.FLAG_SHOW_WALLPAPER; 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 android.view.WindowManager.TRANSIT_TO_FRONT; 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.Assert.fail; 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 @@ public class AppTransitionControllerTest extends WindowTestsBase { } @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 @@ public class AppTransitionControllerTest extends WindowTestsBase { 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 affaad6d9210..193390833789 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java @@ -17,6 +17,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; @@ -316,6 +317,50 @@ public class DisplayContentDeferredUpdateTests extends WindowTestsBase { verify(mScreenUnblocker).sendToTarget(); } + @Test + public void testWaitForAllWindowsDrawnForInvalidDisplay_usesTransitionToUnblock() { + mSetFlagsRule.enableFlags(Flags.FLAG_WAIT_FOR_TRANSITION_ON_DISPLAY_SWITCH); + + final WindowState defaultDisplayWindow = createWindow(/* parent= */ null, + TYPE_BASE_APPLICATION, mDisplayContent, "DefaultDisplayWindow"); + makeWindowVisibleAndNotDrawn(defaultDisplayWindow); + + mDisplayContent.mDisplayUpdater.onDisplaySwitching(/* switching= */ true); + + mWmInternal.waitForAllWindowsDrawn(mScreenUnblocker, + /* timeout= */ Integer.MAX_VALUE, INVALID_DISPLAY); + + // Perform display update + mUniqueId = "new_default_display_unique_id"; + mDisplayContent.requestDisplayUpdate(mock(Runnable.class)); + + when(mDisplayContent.mTransitionController.inTransition()).thenReturn(true); + + // Notify that transition started collecting + captureStartTransitionCollection().getAllValues().forEach((callback) -> + callback.onCollectStarted(/* deferred= */ true)); + + // Verify that screen is not unblocked yet + verify(mScreenUnblocker, never()).sendToTarget(); + + // Make all display windows drawn + defaultDisplayWindow.mWinAnimator.mDrawState = HAS_DRAWN; + mWm.mRoot.performSurfacePlacement(); + + // Verify that default display is still not unblocked yet + // (so it doesn't use old windows drawn path) + verify(mScreenUnblocker, never()).sendToTarget(); + + // Mark start transaction as presented + when(mDisplayContent.mTransitionController.inTransition()).thenReturn(false); + captureRequestedTransition().getAllValues().forEach( + this::makeTransitionTransactionCompleted); + + // Verify that the default screen unblocker is sent only after start transaction + // of the Shell transition is presented + verify(mScreenUnblocker).sendToTarget(); + } + private void prepareSecondaryDisplay() { mSecondaryDisplayContent = createNewDisplay(); when(mSecondaryScreenUnblocker.getTarget()).thenReturn(mWm.mH); 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 d013053a063d..a71b81e025d2 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.Bundle; 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 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { private IBinder mFragmentToken; private WindowContainerTransaction mTransaction; private WindowContainerToken mFragmentWindowToken; + private RemoteAnimationDefinition mDefinition; private IBinder mErrorToken; private Rect mTaskFragBounds; @@ -167,6 +169,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { 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 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { } @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 808a57589b47..1e1abf283af8 100644 --- a/telecomm/java/android/telecom/CallControl.java +++ b/telecomm/java/android/telecom/CallControl.java @@ -39,7 +39,10 @@ import java.util.concurrent.Executor; /** * 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 1df6cf78047c..ad7d9870ca98 100644 --- a/telecomm/java/android/telecom/Connection.java +++ b/telecomm/java/android/telecom/Connection.java @@ -2621,7 +2621,9 @@ public abstract class Connection extends Conferenceable { } /** - * 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 @@ public abstract class Connection extends Conferenceable { } /** - * 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 cba2eeae5c9f..13bd5eb67e44 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -10014,6 +10014,19 @@ public class CarrierConfigManager { 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 @@ public class CarrierConfigManager { 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 7481daa8e396..bc709abbe76e 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -132,6 +132,7 @@ import com.android.internal.telephony.OperatorInfo; 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 @@ public class TelephonyManager { 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 class TelephonyManager { 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 @@ public class TelephonyManager { 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 c5934a70610f..83fc0dcfe790 100644 --- a/telephony/java/android/telephony/satellite/SatelliteManager.java +++ b/telephony/java/android/telephony/satellite/SatelliteManager.java @@ -592,7 +592,7 @@ public final class SatelliteManager { () -> 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 @@ public final class SatelliteManager { } } }; - telephony.requestIsSatelliteEnabled(mSubId, receiver); + telephony.requestIsSatelliteEnabled(receiver); } else { loge("requestIsEnabled() invalid telephony"); executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError( @@ -707,7 +707,7 @@ public final class SatelliteManager { } } }; - telephony.requestIsDemoModeEnabled(mSubId, receiver); + telephony.requestIsDemoModeEnabled(receiver); } else { loge("requestIsDemoModeEnabled() invalid telephony"); executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError( @@ -764,7 +764,7 @@ public final class SatelliteManager { } } }; - telephony.requestIsEmergencyModeEnabled(mSubId, receiver); + telephony.requestIsEmergencyModeEnabled(receiver); } else { executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError( new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE)))); @@ -821,7 +821,7 @@ public final class SatelliteManager { } } }; - telephony.requestIsSatelliteSupported(mSubId, receiver); + telephony.requestIsSatelliteSupported(receiver); } else { loge("requestIsSupported() invalid telephony"); executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError( @@ -878,7 +878,7 @@ public final class SatelliteManager { } } }; - telephony.requestSatelliteCapabilities(mSubId, receiver); + telephony.requestSatelliteCapabilities(receiver); } else { loge("requestCapabilities() invalid telephony"); executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError( @@ -1204,8 +1204,7 @@ public final class SatelliteManager { } }; 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 @@ public final class SatelliteManager { () -> 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 @@ public final class SatelliteManager { () -> resultListener.accept(result))); } }; - cancelRemote = telephony.provisionSatelliteService(mSubId, token, provisionData, + cancelRemote = telephony.provisionSatelliteService(token, provisionData, errorCallback); } else { loge("provisionService() invalid telephony"); @@ -1364,7 +1362,7 @@ public final class SatelliteManager { () -> 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 @@ public final class SatelliteManager { } }; 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 @@ public final class SatelliteManager { 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 @@ public final class SatelliteManager { } } }; - telephony.requestIsSatelliteProvisioned(mSubId, receiver); + telephony.requestIsSatelliteProvisioned(receiver); } else { loge("requestIsProvisioned() invalid telephony"); executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError( @@ -1560,7 +1557,7 @@ public final class SatelliteManager { } }; 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 @@ public final class SatelliteManager { 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 @@ public final class SatelliteManager { } }; 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 @@ public final class SatelliteManager { 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 @@ public final class SatelliteManager { () -> resultListener.accept(result))); } }; - telephony.pollPendingDatagrams(mSubId, internalCallback); + telephony.pollPendingDatagrams(internalCallback); } else { loge("pollPendingDatagrams() invalid telephony"); executor.execute(() -> Binder.withCleanCallingIdentity( @@ -1790,7 +1787,7 @@ public final class SatelliteManager { () -> resultListener.accept(result))); } }; - telephony.sendDatagram(mSubId, datagramType, datagram, + telephony.sendDatagram(datagramType, datagram, needFullScreenPointingUI, internalCallback); } else { loge("sendDatagram() invalid telephony"); @@ -1908,7 +1905,7 @@ public final class SatelliteManager { } } }; - telephony.requestTimeForNextSatelliteVisibility(mSubId, receiver); + telephony.requestTimeForNextSatelliteVisibility(receiver); } else { loge("requestTimeForNextSatelliteVisibility() invalid telephony"); executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError( @@ -1939,7 +1936,7 @@ public final class SatelliteManager { 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 @@ public final class SatelliteManager { } } }; - telephony.requestNtnSignalStrength(mSubId, receiver); + telephony.requestNtnSignalStrength(receiver); } else { loge("requestNtnSignalStrength() invalid telephony"); executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError( @@ -2254,7 +2251,7 @@ public final class SatelliteManager { ntnSignalStrength))); } }; - telephony.registerForNtnSignalStrengthChanged(mSubId, internalCallback); + telephony.registerForNtnSignalStrengthChanged(internalCallback); sNtnSignalStrengthCallbackMap.put(callback, internalCallback); } else { throw new IllegalStateException("Telephony service is null."); @@ -2294,7 +2291,7 @@ public final class SatelliteManager { 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 @@ public final class SatelliteManager { } }; 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 @@ public final class SatelliteManager { 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 @@ public final class SatelliteManager { }; sSatelliteSupportedStateCallbackMap.put(callback, internalCallback); return telephony.registerForSatelliteSupportedStateChanged( - mSubId, internalCallback); + internalCallback); } else { throw new IllegalStateException("telephony service is null."); } @@ -2483,7 +2480,7 @@ public final class SatelliteManager { 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 0c5f30f96360..e852e6bbb756 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -2743,7 +2743,6 @@ interface ITelephony { /** * 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 @@ interface ITelephony { */ @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 @@ interface ITelephony { */ @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 @@ interface ITelephony { * {@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 @@ interface ITelephony { */ @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 @@ interface ITelephony { /** * 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 @@ interface ITelephony { */ @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 @@ interface ITelephony { /** * 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 @@ interface ITelephony { */ @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 @@ interface ITelephony { */ @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 @@ interface ITelephony { /** * 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 379b45cdf08e..c3e1a1fa2e18 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.FlickerBuilder 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 @@ class EnterSystemSplitTest(flicker: LegacyFlickerTest) : ActivityEmbeddingTestBa @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 adeba72c9c96..6e6a3275191f 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 @@ open class OpenAppFromLockscreenViaIntentTest(flicker: LegacyFlickerTest) : 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 Binary files differindex b2354f8f4799..297244f9d6d1 100644 --- a/tests/Input/assets/testPointerFillStyle.png +++ b/tests/Input/assets/testPointerFillStyle.png diff --git a/tools/hoststubgen/hoststubgen/invoketest/hoststubgen-invoke-test.sh b/tools/hoststubgen/hoststubgen/invoketest/hoststubgen-invoke-test.sh index d97dd7c18045..5c5421a9151a 100755 --- a/tools/hoststubgen/hoststubgen/invoketest/hoststubgen-invoke-test.sh +++ b/tools/hoststubgen/hoststubgen/invoketest/hoststubgen-invoke-test.sh @@ -213,9 +213,9 @@ com.unsupported.* " 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 6cf214300b43..7dd4fdd078a2 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 @@ fun isAnonymousInnerClass(cn: ClassNode): Boolean { } /** - * 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 47790b10782a..37048d9c7c60 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 @@ import com.android.hoststubgen.asm.getDirectOuterClassName */ 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 + } } } - 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 + 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 + } - return super.getPolicyForClass(className) + override fun getPolicyForClass(className: String): FilterPolicyWithReason { + // 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 bd9e85e17890..de4cb0c536c1 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 c2f593cf5ae3..845e1d08ce92 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,111 +394,13 @@ NestMembers: 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/TinyFrameworkCallerCheck$Impl.class - Compiled from "TinyFrameworkCallerCheck.java" -class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl - minor version: 0 - major version: 61 - flags: (0x0020) ACC_SUPER - this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl - super_class: #x // java/lang/Object - interfaces: 0, fields: 0, methods: 3, attributes: 3 - private com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl(); - descriptor: ()V - flags: (0x0002) ACC_PRIVATE - Code: - stack=1, locals=1, args_size=1 - x: aload_0 - x: invokespecial #x // Method java/lang/Object."<init>":()V - x: return - LineNumberTable: - LocalVariableTable: - Start Length Slot Name Signature - 0 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl; - - public static int getOneKeep(); - descriptor: ()I - flags: (0x0009) ACC_PUBLIC, ACC_STATIC - Code: - stack=1, locals=0, args_size=0 - x: iconst_1 - x: ireturn - LineNumberTable: - RuntimeInvisibleAnnotations: - x: #x() - android.hosttest.annotation.HostSideTestKeep - - public static int getOneStub(); - descriptor: ()I - flags: (0x0009) ACC_PUBLIC, ACC_STATIC - Code: - stack=1, locals=0, args_size=0 - x: iconst_1 - x: ireturn - LineNumberTable: - RuntimeInvisibleAnnotations: - x: #x() - android.hosttest.annotation.HostSideTestStub -} -SourceFile: "TinyFrameworkCallerCheck.java" -NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck -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/TinyFrameworkCallerCheck.class - Compiled from "TinyFrameworkCallerCheck.java" -public class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck - minor version: 0 - major version: 61 - flags: (0x0021) ACC_PUBLIC, ACC_SUPER - this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck - super_class: #x // java/lang/Object - interfaces: 0, fields: 0, methods: 3, attributes: 4 - public com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck(); - descriptor: ()V - flags: (0x0001) ACC_PUBLIC - Code: - stack=1, locals=1, args_size=1 - x: aload_0 - x: invokespecial #x // Method java/lang/Object."<init>":()V - x: return - LineNumberTable: - LocalVariableTable: - Start Length Slot Name Signature - 0 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck; - - public static int getOne_withCheck(); - descriptor: ()I - flags: (0x0009) ACC_PUBLIC, ACC_STATIC - Code: - stack=1, locals=0, args_size=0 - x: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.getOneKeep:()I - x: ireturn - LineNumberTable: - - public static int getOne_noCheck(); - descriptor: ()I - flags: (0x0009) ACC_PUBLIC, ACC_STATIC - Code: - stack=1, locals=0, args_size=0 - x: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.getOneStub:()I - x: ireturn - LineNumberTable: -} -SourceFile: "TinyFrameworkCallerCheck.java" -RuntimeInvisibleAnnotations: - x: #x() - android.hosttest.annotation.HostSideTestWholeClassStub -NestMembers: - 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 +## 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/TinyFrameworkClassAnnotations + 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; @@ -519,7 +421,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota descriptor: I flags: (0x0001) ACC_PUBLIC - public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnotations(); + public com.android.hoststubgen.test.tinyframework.TinyFrameworkAnnotations(); descriptor: ()V flags: (0x0001) ACC_PUBLIC Code: @@ -536,7 +438,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota LineNumberTable: LocalVariableTable: Start Length Slot Name Signature - 0 15 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations; + 0 15 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations; RuntimeInvisibleAnnotations: x: #x() android.hosttest.annotation.HostSideTestStub @@ -553,7 +455,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota LineNumberTable: LocalVariableTable: Start Length Slot Name Signature - 0 6 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations; + 0 6 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations; 0 6 1 value I RuntimeInvisibleAnnotations: x: #x() @@ -571,7 +473,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota LineNumberTable: LocalVariableTable: Start Length Slot Name Signature - 0 4 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations; + 0 4 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations; 0 4 1 value I RuntimeInvisibleAnnotations: x: #x() @@ -589,7 +491,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota LineNumberTable: LocalVariableTable: Start Length Slot Name Signature - 0 8 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations; + 0 8 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations; 0 8 1 foo Ljava/lang/String; RuntimeInvisibleAnnotations: x: #x() @@ -608,7 +510,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota LineNumberTable: LocalVariableTable: Start Length Slot Name Signature - 0 10 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations; + 0 10 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations; 0 10 1 value I RuntimeInvisibleAnnotations: x: #x() @@ -630,7 +532,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota LineNumberTable: LocalVariableTable: Start Length Slot Name Signature - 0 4 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations; + 0 4 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations; 0 4 1 value I public static native int nativeAddThree(int); @@ -668,7 +570,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota LineNumberTable: LocalVariableTable: Start Length Slot Name Signature - 0 3 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations; + 0 3 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations; RuntimeInvisibleAnnotations: x: #x() android.hosttest.annotation.HostSideTestThrow @@ -684,12 +586,12 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota LineNumberTable: LocalVariableTable: Start Length Slot Name Signature - 0 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations; + 0 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations; RuntimeInvisibleAnnotations: x: #x() android.hosttest.annotation.HostSideTestStub } -SourceFile: "TinyFrameworkClassAnnotations.java" +SourceFile: "TinyFrameworkAnnotations.java" RuntimeInvisibleAnnotations: x: #x() android.hosttest.annotation.HostSideTestStub @@ -697,183 +599,104 @@ RuntimeInvisibleAnnotations: 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 +## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class + Compiled from "TinyFrameworkCallerCheck.java" +class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl minor version: 0 major version: 61 - flags: (0x0021) ACC_PUBLIC, ACC_SUPER - this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations + flags: (0x0020) ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl 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(); + interfaces: 0, fields: 0, methods: 3, attributes: 3 + private com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl(); descriptor: ()V - flags: (0x0001) ACC_PUBLIC + flags: (0x0002) ACC_PRIVATE Code: - stack=2, locals=1, args_size=1 + stack=1, 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 + x: return LineNumberTable: LocalVariableTable: Start Length Slot Name Signature - 0 15 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations; + 0 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl; - public int addOne(int); - descriptor: (I)I - flags: (0x0001) ACC_PUBLIC + public static int getOneKeep(); + descriptor: ()I + flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: - stack=2, locals=2, args_size=2 - x: aload_0 - x: iload_1 - x: invokevirtual #x // Method addOneInner:(I)I + stack=1, locals=0, args_size=0 + x: iconst_1 x: ireturn LineNumberTable: - LocalVariableTable: - Start Length Slot Name Signature - 0 6 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations; - 0 6 1 value I + RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestKeep - public int addOneInner(int); - descriptor: (I)I - flags: (0x0001) ACC_PUBLIC + public static int getOneStub(); + descriptor: ()I + flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: - stack=2, locals=2, args_size=2 - x: iload_1 + stack=1, locals=0, args_size=0 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 +} +SourceFile: "TinyFrameworkCallerCheck.java" +NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck +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/TinyFrameworkCallerCheck.class + Compiled from "TinyFrameworkCallerCheck.java" +public class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck + super_class: #x // java/lang/Object + interfaces: 0, fields: 0, methods: 3, attributes: 4 + public com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck(); + descriptor: ()V flags: (0x0001) ACC_PUBLIC Code: - stack=2, locals=2, args_size=2 - x: iload_1 - x: iconst_2 - x: iadd - x: ireturn + stack=1, locals=1, args_size=1 + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return LineNumberTable: LocalVariableTable: Start Length Slot Name Signature - 0 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" - ) + 0 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck; - public static int nativeAddThree_host(int); - descriptor: (I)I + public static int getOne_withCheck(); + descriptor: ()I flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: - stack=2, locals=1, args_size=1 - x: iload_0 - x: iconst_3 - x: iadd + stack=1, locals=0, args_size=0 + x: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.getOneKeep:()I 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 + public static int getOne_noCheck(); + descriptor: ()I + flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: - stack=1, locals=1, args_size=1 - x: aload_0 - x: invokevirtual #x // Method unsupportedMethod:()Ljava/lang/String; - x: areturn + stack=1, locals=0, args_size=0 + x: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.getOneStub:()I + x: ireturn LineNumberTable: - LocalVariableTable: - Start Length Slot Name Signature - 0 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations; } -SourceFile: "TinyFrameworkClassClassWideAnnotations.java" +SourceFile: "TinyFrameworkCallerCheck.java" RuntimeInvisibleAnnotations: x: #x() android.hosttest.annotation.HostSideTestWholeClassStub +NestMembers: + 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/TinyFrameworkClassLoadHook.class Compiled from "TinyFrameworkClassLoadHook.java" public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook @@ -936,6 +759,118 @@ SourceFile: "TinyFrameworkClassLoadHook.java" 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 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass 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 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass <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 @@ NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedCl 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 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass 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 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass 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 @@ NestMembers: 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 @@ InnerClasses: 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 1b83d244c55c..86a9c65f59b4 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,136 +216,13 @@ RuntimeVisibleAnnotations: com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl NestMembers: com/android/hoststubgen/test/tinyframework/R$Nested -## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class - Compiled from "TinyFrameworkCallerCheck.java" -class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl - minor version: 0 - major version: 61 - flags: (0x0020) ACC_SUPER - this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl - super_class: #x // java/lang/Object - interfaces: 0, fields: 0, methods: 2, attributes: 4 - private com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl(); - descriptor: ()V - flags: (0x0002) ACC_PRIVATE - 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 static int getOneStub(); - descriptor: ()I - flags: (0x0009) ACC_PUBLIC, ACC_STATIC - Code: - stack=3, locals=0, args_size=0 - 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 -} -InnerClasses: - private static #x= #x of #x; // Impl=class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl of class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck -SourceFile: "TinyFrameworkCallerCheck.java" -RuntimeVisibleAnnotations: - x: #x() - com.android.hoststubgen.hosthelper.HostStubGenKeptInStub - x: #x() - com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl -NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck -## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck.class - Compiled from "TinyFrameworkCallerCheck.java" -public class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck +## 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/TinyFrameworkCallerCheck - super_class: #x // java/lang/Object - interfaces: 0, fields: 0, methods: 3, attributes: 5 - public com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck(); - 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 static int getOne_withCheck(); - descriptor: ()I - flags: (0x0009) ACC_PUBLIC, ACC_STATIC - Code: - stack=3, locals=0, args_size=0 - 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 getOne_noCheck(); - descriptor: ()I - flags: (0x0009) ACC_PUBLIC, ACC_STATIC - Code: - stack=3, locals=0, args_size=0 - 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: - private static #x= #x of #x; // Impl=class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl of class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck -SourceFile: "TinyFrameworkCallerCheck.java" -RuntimeVisibleAnnotations: - x: #x() - com.android.hoststubgen.hosthelper.HostStubGenKeptInStub - x: #x() - com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl -RuntimeInvisibleAnnotations: - x: #x() - 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 + 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; @@ -360,7 +237,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota x: #x() android.hosttest.annotation.HostSideTestStub - public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnotations(); + public com.android.hoststubgen.test.tinyframework.TinyFrameworkAnnotations(); descriptor: ()V flags: (0x0001) ACC_PUBLIC Code: @@ -449,7 +326,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota x: #x() android.hosttest.annotation.HostSideTestStub } -SourceFile: "TinyFrameworkClassAnnotations.java" +SourceFile: "TinyFrameworkAnnotations.java" RuntimeVisibleAnnotations: x: #x() com.android.hoststubgen.hosthelper.HostStubGenKeptInStub @@ -462,45 +339,18 @@ RuntimeInvisibleAnnotations: 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 +## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class + Compiled from "TinyFrameworkCallerCheck.java" +class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl minor version: 0 major version: 61 - flags: (0x0021) ACC_PUBLIC, ACC_SUPER - this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations + flags: (0x0020) ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl 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(); + interfaces: 0, fields: 0, methods: 2, attributes: 4 + private com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl(); descriptor: ()V - flags: (0x0001) ACC_PUBLIC + flags: (0x0002) ACC_PRIVATE Code: stack=3, locals=1, args_size=1 x: new #x // class java/lang/RuntimeException @@ -514,11 +364,11 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW x: #x() com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl - public int addOne(int); - descriptor: (I)I - flags: (0x0001) ACC_PUBLIC + public static int getOneStub(); + descriptor: ()I + flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: - stack=3, locals=2, args_size=2 + stack=3, locals=0, args_size=0 x: new #x // class java/lang/RuntimeException x: dup x: ldc #x // String Stub! @@ -529,12 +379,33 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW com.android.hoststubgen.hosthelper.HostStubGenKeptInStub x: #x() com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl - - public int addOneInner(int); - descriptor: (I)I + RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestStub +} +InnerClasses: + private static #x= #x of #x; // Impl=class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl of class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck +SourceFile: "TinyFrameworkCallerCheck.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl +NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck +## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck.class + Compiled from "TinyFrameworkCallerCheck.java" +public class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck + super_class: #x // java/lang/Object + interfaces: 0, fields: 0, methods: 3, attributes: 5 + public com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck(); + descriptor: ()V flags: (0x0001) ACC_PUBLIC Code: - stack=3, locals=2, args_size=2 + stack=3, locals=1, args_size=1 x: new #x // class java/lang/RuntimeException x: dup x: ldc #x // String Stub! @@ -546,11 +417,11 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW x: #x() com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl - public void toBeRemoved(java.lang.String); - descriptor: (Ljava/lang/String;)V - flags: (0x0001) ACC_PUBLIC + public static int getOne_withCheck(); + descriptor: ()I + flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: - stack=3, locals=2, args_size=2 + stack=3, locals=0, args_size=0 x: new #x // class java/lang/RuntimeException x: dup x: ldc #x // String Stub! @@ -562,11 +433,11 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW x: #x() com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl - public int addTwo(int); - descriptor: (I)I - flags: (0x0001) ACC_PUBLIC + public static int getOne_noCheck(); + descriptor: ()I + flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: - stack=3, locals=2, args_size=2 + stack=3, locals=0, args_size=0 x: new #x // class java/lang/RuntimeException x: dup x: ldc #x // String Stub! @@ -577,10 +448,42 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW com.android.hoststubgen.hosthelper.HostStubGenKeptInStub x: #x() com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl +} +InnerClasses: + private static #x= #x of #x; // Impl=class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl of class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck +SourceFile: "TinyFrameworkCallerCheck.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl +RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestWholeClassStub +NestMembers: + com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl +## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook.class + Compiled from "TinyFrameworkClassLoadHook.java" +public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook + super_class: #x // java/lang/Object + interfaces: 0, fields: 1, methods: 3, attributes: 3 + public static final java.util.Set<java.lang.Class<?>> sLoadedClasses; + descriptor: Ljava/util/Set; + flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL + Signature: #x // Ljava/util/Set<Ljava/lang/Class<*>;>; + 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 + private com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook(); + descriptor: ()V + flags: (0x0002) ACC_PRIVATE Code: stack=3, locals=1, args_size=1 x: new #x // class java/lang/RuntimeException @@ -594,9 +497,9 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW x: #x() com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl - public java.lang.String unsupportedMethod(); - descriptor: ()Ljava/lang/String; - flags: (0x0001) ACC_PUBLIC + public static void onClassLoaded(java.lang.Class<?>); + descriptor: (Ljava/lang/Class;)V + flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=1, args_size=1 x: new #x // class java/lang/RuntimeException @@ -604,17 +507,18 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW x: ldc #x // String Stub! x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V x: athrow + Signature: #x // (Ljava/lang/Class<*>;)V 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 + static {}; + descriptor: ()V + flags: (0x0008) ACC_STATIC Code: - stack=3, locals=1, args_size=1 + stack=3, locals=0, args_size=0 x: new #x // class java/lang/RuntimeException x: dup x: ldc #x // String Stub! @@ -626,7 +530,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW x: #x() com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl } -SourceFile: "TinyFrameworkClassClassWideAnnotations.java" +SourceFile: "TinyFrameworkClassLoadHook.java" RuntimeVisibleAnnotations: x: #x() com.android.hoststubgen.hosthelper.HostStubGenKeptInStub @@ -635,28 +539,27 @@ RuntimeVisibleAnnotations: 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 +## 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/TinyFrameworkClassLoadHook + this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations super_class: #x // java/lang/Object - interfaces: 0, fields: 1, methods: 3, attributes: 3 - public static final java.util.Set<java.lang.Class<?>> sLoadedClasses; - descriptor: Ljava/util/Set; - flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL - Signature: #x // Ljava/util/Set<Ljava/lang/Class<*>;>; + 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 - private com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook(); + public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWideAnnotations(); descriptor: ()V - flags: (0x0002) ACC_PRIVATE + flags: (0x0001) ACC_PUBLIC Code: stack=3, locals=1, args_size=1 x: new #x // class java/lang/RuntimeException @@ -670,28 +573,43 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHo x: #x() com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl - public static void onClassLoaded(java.lang.Class<?>); - descriptor: (Ljava/lang/Class;)V - flags: (0x0009) ACC_PUBLIC, ACC_STATIC + public int addOne(int); + descriptor: (I)I + flags: (0x0001) ACC_PUBLIC Code: - stack=3, locals=1, args_size=1 + 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 - Signature: #x // (Ljava/lang/Class<*>;)V RuntimeVisibleAnnotations: x: #x() com.android.hoststubgen.hosthelper.HostStubGenKeptInStub x: #x() com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl - static {}; - descriptor: ()V - flags: (0x0008) ACC_STATIC + public int addTwo(int); + descriptor: (I)I + flags: (0x0001) ACC_PUBLIC Code: - stack=3, locals=0, args_size=0 + 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! @@ -703,7 +621,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHo x: #x() com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl } -SourceFile: "TinyFrameworkClassLoadHook.java" +SourceFile: "TinyFrameworkClassWideAnnotations.java" RuntimeVisibleAnnotations: x: #x() com.android.hoststubgen.hosthelper.HostStubGenKeptInStub @@ -2153,7 +2071,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass 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 @@ RuntimeVisibleAnnotations: 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 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass 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 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass 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 @@ InnerClasses: 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 @@ NestMembers: 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 d23b450c4076..c6b9c7a9e4f1 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,155 +450,13 @@ RuntimeVisibleAnnotations: com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl NestMembers: com/android/hoststubgen/test/tinyframework/R$Nested -## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class - Compiled from "TinyFrameworkCallerCheck.java" -class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl - minor version: 0 - major version: 61 - flags: (0x0020) ACC_SUPER - this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl - super_class: #x // java/lang/Object - interfaces: 0, fields: 0, methods: 3, attributes: 4 - private com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl(); - descriptor: ()V - flags: (0x0002) ACC_PRIVATE - Code: - stack=1, locals=1, args_size=1 - x: aload_0 - x: invokespecial #x // Method java/lang/Object."<init>":()V - x: return - LineNumberTable: - LocalVariableTable: - Start Length Slot Name Signature - 0 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl; - RuntimeVisibleAnnotations: - x: #x() - com.android.hoststubgen.hosthelper.HostStubGenKeptInStub - x: #x() - com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl - - public static int getOneKeep(); - descriptor: ()I - flags: (0x0009) ACC_PUBLIC, ACC_STATIC - Code: - stack=4, locals=0, args_size=0 - x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl - x: ldc #x // String getOneKeep - x: ldc #x // String ()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: iconst_1 - x: ireturn - LineNumberTable: - RuntimeVisibleAnnotations: - x: #x() - com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl - RuntimeInvisibleAnnotations: - x: #x() - android.hosttest.annotation.HostSideTestKeep - - public static int getOneStub(); - descriptor: ()I - flags: (0x0009) ACC_PUBLIC, ACC_STATIC - Code: - stack=1, locals=0, args_size=0 - x: iconst_1 - x: ireturn - LineNumberTable: - RuntimeVisibleAnnotations: - x: #x() - com.android.hoststubgen.hosthelper.HostStubGenKeptInStub - x: #x() - com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl - RuntimeInvisibleAnnotations: - x: #x() - android.hosttest.annotation.HostSideTestStub -} -InnerClasses: - private static #x= #x of #x; // Impl=class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl of class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck -SourceFile: "TinyFrameworkCallerCheck.java" -RuntimeVisibleAnnotations: - x: #x() - com.android.hoststubgen.hosthelper.HostStubGenKeptInStub - x: #x() - com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl -NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck -## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck.class - Compiled from "TinyFrameworkCallerCheck.java" -public class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck +## 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/TinyFrameworkCallerCheck - super_class: #x // java/lang/Object - interfaces: 0, fields: 0, methods: 3, attributes: 5 - public com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck(); - descriptor: ()V - flags: (0x0001) ACC_PUBLIC - Code: - stack=1, locals=1, args_size=1 - x: aload_0 - x: invokespecial #x // Method java/lang/Object."<init>":()V - x: return - LineNumberTable: - LocalVariableTable: - Start Length Slot Name Signature - 0 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck; - RuntimeVisibleAnnotations: - x: #x() - com.android.hoststubgen.hosthelper.HostStubGenKeptInStub - x: #x() - com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl - - public static int getOne_withCheck(); - descriptor: ()I - flags: (0x0009) ACC_PUBLIC, ACC_STATIC - Code: - stack=1, locals=0, args_size=0 - x: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.getOneKeep:()I - x: ireturn - LineNumberTable: - RuntimeVisibleAnnotations: - x: #x() - com.android.hoststubgen.hosthelper.HostStubGenKeptInStub - x: #x() - com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl - - public static int getOne_noCheck(); - descriptor: ()I - flags: (0x0009) ACC_PUBLIC, ACC_STATIC - Code: - stack=1, locals=0, args_size=0 - x: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.getOneStub:()I - x: ireturn - LineNumberTable: - RuntimeVisibleAnnotations: - x: #x() - com.android.hoststubgen.hosthelper.HostStubGenKeptInStub - x: #x() - com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl -} -InnerClasses: - private static #x= #x of #x; // Impl=class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl of class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck -SourceFile: "TinyFrameworkCallerCheck.java" -RuntimeVisibleAnnotations: - x: #x() - com.android.hoststubgen.hosthelper.HostStubGenKeptInStub - x: #x() - com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl -RuntimeInvisibleAnnotations: - x: #x() - 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 + 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; @@ -628,12 +486,12 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota 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 // 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.TinyFrameworkClassAnnotations(); + public com.android.hoststubgen.test.tinyframework.TinyFrameworkAnnotations(); descriptor: ()V flags: (0x0001) ACC_PUBLIC Code: @@ -650,7 +508,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota LineNumberTable: LocalVariableTable: Start Length Slot Name Signature - 0 15 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations; + 0 15 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations; RuntimeVisibleAnnotations: x: #x() com.android.hoststubgen.hosthelper.HostStubGenKeptInStub @@ -672,7 +530,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota LineNumberTable: LocalVariableTable: Start Length Slot Name Signature - 0 6 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations; + 0 6 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations; 0 6 1 value I RuntimeVisibleAnnotations: x: #x() @@ -688,7 +546,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota 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 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; @@ -701,7 +559,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota LineNumberTable: LocalVariableTable: Start Length Slot Name Signature - 15 4 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations; + 15 4 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations; 15 4 1 value I RuntimeVisibleAnnotations: x: #x() @@ -722,7 +580,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota LineNumberTable: LocalVariableTable: Start Length Slot Name Signature - 0 4 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations; + 0 4 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations; 0 4 1 value I RuntimeVisibleAnnotations: x: #x() @@ -758,7 +616,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota 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 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; @@ -790,7 +648,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota LineNumberTable: LocalVariableTable: Start Length Slot Name Signature - 0 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations; + 0 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations; RuntimeVisibleAnnotations: x: #x() com.android.hoststubgen.hosthelper.HostStubGenKeptInStub @@ -800,7 +658,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota x: #x() android.hosttest.annotation.HostSideTestStub } -SourceFile: "TinyFrameworkClassAnnotations.java" +SourceFile: "TinyFrameworkAnnotations.java" RuntimeVisibleAnnotations: x: #x() com.android.hoststubgen.hosthelper.HostStubGenKeptInStub @@ -813,205 +671,138 @@ RuntimeInvisibleAnnotations: 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 +## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class + Compiled from "TinyFrameworkCallerCheck.java" +class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl minor version: 0 major version: 61 - flags: (0x0021) ACC_PUBLIC, ACC_SUPER - this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations + flags: (0x0020) ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl 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(); + interfaces: 0, fields: 0, methods: 3, attributes: 4 + private com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl(); descriptor: ()V - flags: (0x0001) ACC_PUBLIC + flags: (0x0002) ACC_PRIVATE Code: - stack=2, locals=1, args_size=1 + stack=1, 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 + x: return LineNumberTable: LocalVariableTable: Start Length Slot Name Signature - 0 15 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations; + 0 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl; 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 + public static int getOneKeep(); + descriptor: ()I + flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: - stack=2, locals=2, args_size=2 - x: aload_0 - x: iload_1 - x: invokevirtual #x // Method addOneInner:(I)I - x: ireturn + stack=4, locals=0, args_size=0 + x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl + x: ldc #x // String getOneKeep + x: ldc #x // String ()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: iconst_1 + 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 + RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestKeep - public int addOneInner(int); - descriptor: (I)I - flags: (0x0001) ACC_PUBLIC + public static int getOneStub(); + descriptor: ()I + flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: - stack=2, locals=2, args_size=2 - x: iload_1 + stack=1, locals=0, args_size=0 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 + RuntimeInvisibleAnnotations: x: #x() - com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl - - public int addTwo(int); - descriptor: (I)I + android.hosttest.annotation.HostSideTestStub +} +InnerClasses: + private static #x= #x of #x; // Impl=class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl of class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck +SourceFile: "TinyFrameworkCallerCheck.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl +NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck +## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck.class + Compiled from "TinyFrameworkCallerCheck.java" +public class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck + super_class: #x // java/lang/Object + interfaces: 0, fields: 0, methods: 3, attributes: 5 + public com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck(); + descriptor: ()V flags: (0x0001) ACC_PUBLIC Code: - stack=2, locals=2, args_size=2 - x: iload_1 - x: iconst_2 - x: iadd - x: ireturn + stack=1, locals=1, args_size=1 + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return LineNumberTable: LocalVariableTable: Start Length Slot Name Signature - 0 4 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations; - 0 4 1 value I + 0 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck; 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 + public static int getOne_withCheck(); + descriptor: ()I flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: - stack=2, locals=1, args_size=1 - x: iload_0 - x: iconst_3 - x: iadd + stack=1, locals=0, args_size=0 + x: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.getOneKeep:()I 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 + public static int getOne_noCheck(); + descriptor: ()I + flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: - stack=1, locals=1, args_size=1 - x: aload_0 - x: invokevirtual #x // Method unsupportedMethod:()Ljava/lang/String; - x: areturn + stack=1, locals=0, args_size=0 + x: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.getOneStub:()I + x: ireturn 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" +InnerClasses: + private static #x= #x of #x; // Impl=class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl of class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck +SourceFile: "TinyFrameworkCallerCheck.java" RuntimeVisibleAnnotations: x: #x() com.android.hoststubgen.hosthelper.HostStubGenKeptInStub @@ -1020,6 +811,8 @@ RuntimeVisibleAnnotations: RuntimeInvisibleAnnotations: x: #x() android.hosttest.annotation.HostSideTestWholeClassStub +NestMembers: + com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook.class Compiled from "TinyFrameworkClassLoadHook.java" public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook @@ -1107,6 +900,140 @@ RuntimeVisibleAnnotations: 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 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass 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 @@ RuntimeVisibleAnnotations: 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 @@ RuntimeVisibleAnnotations: 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 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass 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 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass 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 @@ InnerClasses: 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 @@ NestMembers: 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 1b83d244c55c..86a9c65f59b4 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,136 +216,13 @@ RuntimeVisibleAnnotations: com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl NestMembers: com/android/hoststubgen/test/tinyframework/R$Nested -## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class - Compiled from "TinyFrameworkCallerCheck.java" -class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl - minor version: 0 - major version: 61 - flags: (0x0020) ACC_SUPER - this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl - super_class: #x // java/lang/Object - interfaces: 0, fields: 0, methods: 2, attributes: 4 - private com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl(); - descriptor: ()V - flags: (0x0002) ACC_PRIVATE - 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 static int getOneStub(); - descriptor: ()I - flags: (0x0009) ACC_PUBLIC, ACC_STATIC - Code: - stack=3, locals=0, args_size=0 - 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 -} -InnerClasses: - private static #x= #x of #x; // Impl=class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl of class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck -SourceFile: "TinyFrameworkCallerCheck.java" -RuntimeVisibleAnnotations: - x: #x() - com.android.hoststubgen.hosthelper.HostStubGenKeptInStub - x: #x() - com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl -NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck -## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck.class - Compiled from "TinyFrameworkCallerCheck.java" -public class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck +## 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/TinyFrameworkCallerCheck - super_class: #x // java/lang/Object - interfaces: 0, fields: 0, methods: 3, attributes: 5 - public com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck(); - 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 static int getOne_withCheck(); - descriptor: ()I - flags: (0x0009) ACC_PUBLIC, ACC_STATIC - Code: - stack=3, locals=0, args_size=0 - 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 getOne_noCheck(); - descriptor: ()I - flags: (0x0009) ACC_PUBLIC, ACC_STATIC - Code: - stack=3, locals=0, args_size=0 - 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: - private static #x= #x of #x; // Impl=class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl of class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck -SourceFile: "TinyFrameworkCallerCheck.java" -RuntimeVisibleAnnotations: - x: #x() - com.android.hoststubgen.hosthelper.HostStubGenKeptInStub - x: #x() - com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl -RuntimeInvisibleAnnotations: - x: #x() - 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 + 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; @@ -360,7 +237,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota x: #x() android.hosttest.annotation.HostSideTestStub - public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnotations(); + public com.android.hoststubgen.test.tinyframework.TinyFrameworkAnnotations(); descriptor: ()V flags: (0x0001) ACC_PUBLIC Code: @@ -449,7 +326,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota x: #x() android.hosttest.annotation.HostSideTestStub } -SourceFile: "TinyFrameworkClassAnnotations.java" +SourceFile: "TinyFrameworkAnnotations.java" RuntimeVisibleAnnotations: x: #x() com.android.hoststubgen.hosthelper.HostStubGenKeptInStub @@ -462,45 +339,18 @@ RuntimeInvisibleAnnotations: 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 +## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class + Compiled from "TinyFrameworkCallerCheck.java" +class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl minor version: 0 major version: 61 - flags: (0x0021) ACC_PUBLIC, ACC_SUPER - this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations + flags: (0x0020) ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl 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(); + interfaces: 0, fields: 0, methods: 2, attributes: 4 + private com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl(); descriptor: ()V - flags: (0x0001) ACC_PUBLIC + flags: (0x0002) ACC_PRIVATE Code: stack=3, locals=1, args_size=1 x: new #x // class java/lang/RuntimeException @@ -514,11 +364,11 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW x: #x() com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl - public int addOne(int); - descriptor: (I)I - flags: (0x0001) ACC_PUBLIC + public static int getOneStub(); + descriptor: ()I + flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: - stack=3, locals=2, args_size=2 + stack=3, locals=0, args_size=0 x: new #x // class java/lang/RuntimeException x: dup x: ldc #x // String Stub! @@ -529,12 +379,33 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW com.android.hoststubgen.hosthelper.HostStubGenKeptInStub x: #x() com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl - - public int addOneInner(int); - descriptor: (I)I + RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestStub +} +InnerClasses: + private static #x= #x of #x; // Impl=class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl of class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck +SourceFile: "TinyFrameworkCallerCheck.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl +NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck +## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck.class + Compiled from "TinyFrameworkCallerCheck.java" +public class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck + super_class: #x // java/lang/Object + interfaces: 0, fields: 0, methods: 3, attributes: 5 + public com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck(); + descriptor: ()V flags: (0x0001) ACC_PUBLIC Code: - stack=3, locals=2, args_size=2 + stack=3, locals=1, args_size=1 x: new #x // class java/lang/RuntimeException x: dup x: ldc #x // String Stub! @@ -546,11 +417,11 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW x: #x() com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl - public void toBeRemoved(java.lang.String); - descriptor: (Ljava/lang/String;)V - flags: (0x0001) ACC_PUBLIC + public static int getOne_withCheck(); + descriptor: ()I + flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: - stack=3, locals=2, args_size=2 + stack=3, locals=0, args_size=0 x: new #x // class java/lang/RuntimeException x: dup x: ldc #x // String Stub! @@ -562,11 +433,11 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW x: #x() com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl - public int addTwo(int); - descriptor: (I)I - flags: (0x0001) ACC_PUBLIC + public static int getOne_noCheck(); + descriptor: ()I + flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: - stack=3, locals=2, args_size=2 + stack=3, locals=0, args_size=0 x: new #x // class java/lang/RuntimeException x: dup x: ldc #x // String Stub! @@ -577,10 +448,42 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW com.android.hoststubgen.hosthelper.HostStubGenKeptInStub x: #x() com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl +} +InnerClasses: + private static #x= #x of #x; // Impl=class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl of class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck +SourceFile: "TinyFrameworkCallerCheck.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl +RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestWholeClassStub +NestMembers: + com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl +## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook.class + Compiled from "TinyFrameworkClassLoadHook.java" +public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook + super_class: #x // java/lang/Object + interfaces: 0, fields: 1, methods: 3, attributes: 3 + public static final java.util.Set<java.lang.Class<?>> sLoadedClasses; + descriptor: Ljava/util/Set; + flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL + Signature: #x // Ljava/util/Set<Ljava/lang/Class<*>;>; + 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 + private com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook(); + descriptor: ()V + flags: (0x0002) ACC_PRIVATE Code: stack=3, locals=1, args_size=1 x: new #x // class java/lang/RuntimeException @@ -594,9 +497,9 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW x: #x() com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl - public java.lang.String unsupportedMethod(); - descriptor: ()Ljava/lang/String; - flags: (0x0001) ACC_PUBLIC + public static void onClassLoaded(java.lang.Class<?>); + descriptor: (Ljava/lang/Class;)V + flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=1, args_size=1 x: new #x // class java/lang/RuntimeException @@ -604,17 +507,18 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW x: ldc #x // String Stub! x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V x: athrow + Signature: #x // (Ljava/lang/Class<*>;)V 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 + static {}; + descriptor: ()V + flags: (0x0008) ACC_STATIC Code: - stack=3, locals=1, args_size=1 + stack=3, locals=0, args_size=0 x: new #x // class java/lang/RuntimeException x: dup x: ldc #x // String Stub! @@ -626,7 +530,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW x: #x() com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl } -SourceFile: "TinyFrameworkClassClassWideAnnotations.java" +SourceFile: "TinyFrameworkClassLoadHook.java" RuntimeVisibleAnnotations: x: #x() com.android.hoststubgen.hosthelper.HostStubGenKeptInStub @@ -635,28 +539,27 @@ RuntimeVisibleAnnotations: 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 +## 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/TinyFrameworkClassLoadHook + this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations super_class: #x // java/lang/Object - interfaces: 0, fields: 1, methods: 3, attributes: 3 - public static final java.util.Set<java.lang.Class<?>> sLoadedClasses; - descriptor: Ljava/util/Set; - flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL - Signature: #x // Ljava/util/Set<Ljava/lang/Class<*>;>; + 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 - private com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook(); + public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWideAnnotations(); descriptor: ()V - flags: (0x0002) ACC_PRIVATE + flags: (0x0001) ACC_PUBLIC Code: stack=3, locals=1, args_size=1 x: new #x // class java/lang/RuntimeException @@ -670,28 +573,43 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHo x: #x() com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl - public static void onClassLoaded(java.lang.Class<?>); - descriptor: (Ljava/lang/Class;)V - flags: (0x0009) ACC_PUBLIC, ACC_STATIC + public int addOne(int); + descriptor: (I)I + flags: (0x0001) ACC_PUBLIC Code: - stack=3, locals=1, args_size=1 + 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 - Signature: #x // (Ljava/lang/Class<*>;)V RuntimeVisibleAnnotations: x: #x() com.android.hoststubgen.hosthelper.HostStubGenKeptInStub x: #x() com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl - static {}; - descriptor: ()V - flags: (0x0008) ACC_STATIC + public int addTwo(int); + descriptor: (I)I + flags: (0x0001) ACC_PUBLIC Code: - stack=3, locals=0, args_size=0 + 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! @@ -703,7 +621,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHo x: #x() com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl } -SourceFile: "TinyFrameworkClassLoadHook.java" +SourceFile: "TinyFrameworkClassWideAnnotations.java" RuntimeVisibleAnnotations: x: #x() com.android.hoststubgen.hosthelper.HostStubGenKeptInStub @@ -2153,7 +2071,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass 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 @@ RuntimeVisibleAnnotations: 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 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass 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 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass 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 @@ InnerClasses: 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 @@ NestMembers: 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 d12a23d3d68f..da434a615c81 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,205 +611,13 @@ RuntimeVisibleAnnotations: com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl NestMembers: com/android/hoststubgen/test/tinyframework/R$Nested -## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class - Compiled from "TinyFrameworkCallerCheck.java" -class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl - minor version: 0 - major version: 61 - flags: (0x0020) ACC_SUPER - this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl - super_class: #x // java/lang/Object - interfaces: 0, fields: 0, methods: 4, attributes: 4 - private static {}; - descriptor: ()V - flags: (0x000a) ACC_PRIVATE, ACC_STATIC - Code: - stack=2, locals=0, args_size=0 - x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl - 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 - - private com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl(); - descriptor: ()V - flags: (0x0002) ACC_PRIVATE - Code: - stack=4, locals=1, args_size=1 - x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl - x: ldc #x // String <init> - x: ldc #x // String ()V - x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall - x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V - x: aload_0 - x: invokespecial #x // Method java/lang/Object."<init>":()V - x: return - LineNumberTable: - LocalVariableTable: - Start Length Slot Name Signature - 11 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl; - RuntimeVisibleAnnotations: - x: #x() - com.android.hoststubgen.hosthelper.HostStubGenKeptInStub - x: #x() - com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl - - public static int getOneKeep(); - descriptor: ()I - flags: (0x0009) ACC_PUBLIC, ACC_STATIC - Code: - stack=4, locals=0, args_size=0 - x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl - x: ldc #x // String getOneKeep - x: ldc #x // String ()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/TinyFrameworkCallerCheck$Impl - x: ldc #x // String getOneKeep - x: ldc #x // String ()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: iconst_1 - x: ireturn - LineNumberTable: - RuntimeVisibleAnnotations: - x: #x() - com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl - RuntimeInvisibleAnnotations: - x: #x() - android.hosttest.annotation.HostSideTestKeep - - public static int getOneStub(); - descriptor: ()I - flags: (0x0009) ACC_PUBLIC, ACC_STATIC - Code: - stack=4, locals=0, args_size=0 - x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl - x: ldc #x // String getOneStub - x: ldc #x // String ()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: iconst_1 - x: ireturn - LineNumberTable: - RuntimeVisibleAnnotations: - x: #x() - com.android.hoststubgen.hosthelper.HostStubGenKeptInStub - x: #x() - com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl - RuntimeInvisibleAnnotations: - x: #x() - android.hosttest.annotation.HostSideTestStub -} -InnerClasses: - private static #x= #x of #x; // Impl=class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl of class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck -SourceFile: "TinyFrameworkCallerCheck.java" -RuntimeVisibleAnnotations: - x: #x() - com.android.hoststubgen.hosthelper.HostStubGenKeptInStub - x: #x() - com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl -NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck -## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck.class - Compiled from "TinyFrameworkCallerCheck.java" -public class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck - minor version: 0 - major version: 61 - flags: (0x0021) ACC_PUBLIC, ACC_SUPER - this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck - super_class: #x // java/lang/Object - interfaces: 0, fields: 0, methods: 4, attributes: 5 - 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/TinyFrameworkCallerCheck - 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.TinyFrameworkCallerCheck(); - descriptor: ()V - flags: (0x0001) ACC_PUBLIC - Code: - stack=4, locals=1, args_size=1 - x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck - x: ldc #x // String <init> - x: ldc #x // String ()V - x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall - x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V - x: aload_0 - x: invokespecial #x // Method java/lang/Object."<init>":()V - x: return - LineNumberTable: - LocalVariableTable: - Start Length Slot Name Signature - 11 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck; - RuntimeVisibleAnnotations: - x: #x() - com.android.hoststubgen.hosthelper.HostStubGenKeptInStub - x: #x() - com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl - - public static int getOne_withCheck(); - descriptor: ()I - flags: (0x0009) ACC_PUBLIC, ACC_STATIC - Code: - stack=4, locals=0, args_size=0 - x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck - x: ldc #x // String getOne_withCheck - x: ldc #x // String ()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: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.getOneKeep:()I - x: ireturn - LineNumberTable: - RuntimeVisibleAnnotations: - x: #x() - com.android.hoststubgen.hosthelper.HostStubGenKeptInStub - x: #x() - com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl - - public static int getOne_noCheck(); - descriptor: ()I - flags: (0x0009) ACC_PUBLIC, ACC_STATIC - Code: - stack=4, locals=0, args_size=0 - x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck - x: ldc #x // String getOne_noCheck - x: ldc #x // String ()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: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.getOneStub:()I - x: ireturn - LineNumberTable: - RuntimeVisibleAnnotations: - x: #x() - com.android.hoststubgen.hosthelper.HostStubGenKeptInStub - x: #x() - com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl -} -InnerClasses: - private static #x= #x of #x; // Impl=class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl of class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck -SourceFile: "TinyFrameworkCallerCheck.java" -RuntimeVisibleAnnotations: - x: #x() - com.android.hoststubgen.hosthelper.HostStubGenKeptInStub - x: #x() - com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl -RuntimeInvisibleAnnotations: - x: #x() - 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 +## 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/TinyFrameworkClassAnnotations + 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; @@ -839,20 +647,20 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota 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 // 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/TinyFrameworkClassAnnotations + 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.TinyFrameworkClassAnnotations(); + 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/TinyFrameworkClassAnnotations + 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 @@ -869,7 +677,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota LineNumberTable: LocalVariableTable: Start Length Slot Name Signature - 11 15 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations; + 11 15 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations; RuntimeVisibleAnnotations: x: #x() com.android.hoststubgen.hosthelper.HostStubGenKeptInStub @@ -884,7 +692,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota 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 // 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 @@ -896,7 +704,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota LineNumberTable: LocalVariableTable: Start Length Slot Name Signature - 11 6 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations; + 11 6 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations; 11 6 1 value I RuntimeVisibleAnnotations: x: #x() @@ -912,12 +720,12 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota 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 // 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/TinyFrameworkClassAnnotations + 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; @@ -930,7 +738,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota LineNumberTable: LocalVariableTable: Start Length Slot Name Signature - 26 4 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations; + 26 4 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations; 26 4 1 value I RuntimeVisibleAnnotations: x: #x() @@ -944,7 +752,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota 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 // 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 @@ -956,7 +764,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota LineNumberTable: LocalVariableTable: Start Length Slot Name Signature - 11 4 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations; + 11 4 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations; 11 4 1 value I RuntimeVisibleAnnotations: x: #x() @@ -971,7 +779,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota 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 // 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 @@ -997,12 +805,12 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota 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 // 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/TinyFrameworkClassAnnotations + 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; @@ -1028,7 +836,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota 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 // 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 @@ -1039,7 +847,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota LineNumberTable: LocalVariableTable: Start Length Slot Name Signature - 11 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations; + 11 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations; RuntimeVisibleAnnotations: x: #x() com.android.hoststubgen.hosthelper.HostStubGenKeptInStub @@ -1049,7 +857,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota x: #x() android.hosttest.annotation.HostSideTestStub } -SourceFile: "TinyFrameworkClassAnnotations.java" +SourceFile: "TinyFrameworkAnnotations.java" RuntimeVisibleAnnotations: x: #x() com.android.hoststubgen.hosthelper.HostStubGenKeptInStub @@ -1062,255 +870,188 @@ RuntimeInvisibleAnnotations: 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 +## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class + Compiled from "TinyFrameworkCallerCheck.java" +class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl minor version: 0 major version: 61 - flags: (0x0021) ACC_PUBLIC, ACC_SUPER - this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations + flags: (0x0020) ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl 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 - + interfaces: 0, fields: 0, methods: 4, attributes: 4 private static {}; descriptor: ()V flags: (0x000a) ACC_PRIVATE, ACC_STATIC Code: stack=2, locals=0, args_size=0 - x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl 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(); + private com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl(); descriptor: ()V - flags: (0x0001) ACC_PUBLIC + flags: (0x0002) ACC_PRIVATE Code: stack=4, locals=1, args_size=1 - x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl 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; + 11 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl; 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 + public static int getOneKeep(); + descriptor: ()I + flags: (0x0009) ACC_PUBLIC, ACC_STATIC 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 + stack=4, locals=0, args_size=0 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl + x: ldc #x // String getOneKeep + x: ldc #x // String ()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: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl + x: ldc #x // String getOneKeep + x: ldc #x // String ()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: iconst_1 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 + RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestKeep - public int addOneInner(int); - descriptor: (I)I - flags: (0x0001) ACC_PUBLIC + public static int getOneStub(); + descriptor: ()I + flags: (0x0009) ACC_PUBLIC, ACC_STATIC 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 + stack=4, locals=0, args_size=0 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl + x: ldc #x // String getOneStub + x: ldc #x // String ()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 + RuntimeInvisibleAnnotations: x: #x() - com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl + android.hosttest.annotation.HostSideTestStub +} +InnerClasses: + private static #x= #x of #x; // Impl=class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl of class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck +SourceFile: "TinyFrameworkCallerCheck.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl +NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck +## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck.class + Compiled from "TinyFrameworkCallerCheck.java" +public class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck + super_class: #x // java/lang/Object + interfaces: 0, fields: 0, methods: 4, attributes: 5 + 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/TinyFrameworkCallerCheck + 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 int addTwo(int); - descriptor: (I)I + public com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck(); + descriptor: ()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 addTwo - x: ldc #x // String (I)I + stack=4, locals=1, args_size=1 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck + 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: iload_1 - x: iconst_2 - x: iadd - x: ireturn + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return LineNumberTable: LocalVariableTable: Start Length Slot Name Signature - 11 4 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations; - 11 4 1 value I + 11 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck; 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 + public static int getOne_withCheck(); + descriptor: ()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 + stack=4, locals=0, args_size=0 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck + x: ldc #x // String getOne_withCheck + x: ldc #x // String ()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: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.getOneKeep:()I 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 + public static int getOne_noCheck(); + descriptor: ()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 visibleButUsesUnsupportedMethod - x: ldc #x // String ()Ljava/lang/String; + stack=4, locals=0, args_size=0 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck + x: ldc #x // String getOne_noCheck + x: ldc #x // String ()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: invokevirtual #x // Method unsupportedMethod:()Ljava/lang/String; - x: areturn + x: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.getOneStub:()I + x: ireturn 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" +InnerClasses: + private static #x= #x of #x; // Impl=class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl of class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck +SourceFile: "TinyFrameworkCallerCheck.java" RuntimeVisibleAnnotations: x: #x() com.android.hoststubgen.hosthelper.HostStubGenKeptInStub @@ -1319,6 +1060,8 @@ RuntimeVisibleAnnotations: RuntimeInvisibleAnnotations: x: #x() android.hosttest.annotation.HostSideTestWholeClassStub +NestMembers: + com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook.class Compiled from "TinyFrameworkClassLoadHook.java" public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook @@ -1424,6 +1167,175 @@ RuntimeVisibleAnnotations: 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 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass 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 @@ RuntimeVisibleAnnotations: 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 @@ RuntimeVisibleAnnotations: 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 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass 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 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass 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 @@ InnerClasses: 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 @@ NestMembers: 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 index 6d8a48a37fea..30dfc80fc60b 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 @@ import android.hosttest.annotation.HostSideTestThrow; @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 index 145b65a98d7b..a626bc943018 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 @@ public class TinyFrameworkClassClassWideAnnotations { 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 e1c48bb85563..fec307a3db25 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 @@ public class TinyFrameworkNestedClasses { return 2; } }; + public Supplier<Integer> getSupplier() { return new Supplier<Integer>() { @Override @@ -52,12 +53,10 @@ public class TinyFrameworkNestedClasses { }; } - @HostSideTestWholeClassStub public class InnerClass { public int value = 5; } - @HostSideTestWholeClassStub public static class StaticNestedClass { public int value = 6; @@ -70,6 +69,10 @@ public class TinyFrameworkNestedClasses { } }; } + + 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 index 288c7162aa58..181902a41ec9 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.Rule; 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 @@ public class TinyFrameworkClassWithAnnotTest { @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 1692c6e89e8c..dda5a0529278 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 com.google.common.truth.Truth.assertThat; 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 @@ public class TinyFrameworkClassTest { } @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 @@ public class TinyFrameworkClassTest { } @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 000000000000..83753b5b1fb2 --- /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 6b46c84bd0bb..5b2795c4cff2 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_PUBLIC 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) { |