diff options
411 files changed, 8357 insertions, 3067 deletions
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/ByteBufferPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/ByteBufferPerfTest.java index 4cf46e5364ea..f01ac0247908 100644 --- a/apct-tests/perftests/core/src/android/libcore/regression/ByteBufferPerfTest.java +++ b/apct-tests/perftests/core/src/android/libcore/regression/ByteBufferPerfTest.java @@ -565,7 +565,6 @@ public class ByteBufferPerfTest { } @Test - @Parameters(method = "getData") public void time_new_byteArray() throws Exception { final BenchmarkState state = mBenchmarkRule.getState(); while (state.keepRunning()) { @@ -574,7 +573,6 @@ public class ByteBufferPerfTest { } @Test - @Parameters(method = "getData") public void time_ByteBuffer_allocate() throws Exception { final BenchmarkState state = mBenchmarkRule.getState(); while (state.keepRunning()) { diff --git a/apex/jobscheduler/framework/aconfig/job.aconfig b/apex/jobscheduler/framework/aconfig/job.aconfig index 80db264d0f44..5f5507587f72 100644 --- a/apex/jobscheduler/framework/aconfig/job.aconfig +++ b/apex/jobscheduler/framework/aconfig/job.aconfig @@ -23,3 +23,10 @@ flag { description: "Introduce a new RUN_BACKUP_JOBS permission and exemption logic allowing for longer running jobs for apps whose primary purpose is to backup or sync content." bug: "318731461" } + +flag { + name: "cleanup_empty_jobs" + namespace: "backstage_power" + description: "Enables automatic cancellation of jobs due to leaked JobParameters, reducing unnecessary battery drain and improving system efficiency. This includes logging and traces for better issue diagnosis." + bug: "349688611" +} diff --git a/apex/jobscheduler/framework/java/android/app/job/IJobCallback.aidl b/apex/jobscheduler/framework/java/android/app/job/IJobCallback.aidl index 96494ec28204..11d17ca749b7 100644 --- a/apex/jobscheduler/framework/java/android/app/job/IJobCallback.aidl +++ b/apex/jobscheduler/framework/java/android/app/job/IJobCallback.aidl @@ -85,6 +85,14 @@ interface IJobCallback { */ @UnsupportedAppUsage void jobFinished(int jobId, boolean reschedule); + + /* + * Inform JobScheduler to force finish this job because the client has lost + * the job handle. jobFinished can no longer be called from the client. + * @param jobId Unique integer used to identify this job + */ + void forceJobFinished(int jobId); + /* * Inform JobScheduler of a change in the estimated transfer payload. * diff --git a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java index e833bb95a302..52a761f8d486 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java @@ -34,15 +34,21 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.PersistableBundle; import android.os.RemoteException; +import android.system.SystemCleaner; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.ref.Cleaner; /** * Contains the parameters used to configure/identify your job. You do not create this object * yourself, instead it is handed in to your application by the System. */ public class JobParameters implements Parcelable { + private static final String TAG = "JobParameters"; /** @hide */ public static final int INTERNAL_STOP_REASON_UNKNOWN = -1; @@ -306,6 +312,10 @@ public class JobParameters implements Parcelable { private int mStopReason = STOP_REASON_UNDEFINED; private int mInternalStopReason = INTERNAL_STOP_REASON_UNKNOWN; private String debugStopReason; // Human readable stop reason for debugging. + @Nullable + private JobCleanupCallback mJobCleanupCallback; + @Nullable + private Cleaner.Cleanable mCleanable; /** @hide */ public JobParameters(IBinder callback, String namespace, int jobId, PersistableBundle extras, @@ -326,6 +336,8 @@ public class JobParameters implements Parcelable { this.mTriggeredContentAuthorities = triggeredContentAuthorities; this.mNetwork = network; this.mJobNamespace = namespace; + this.mJobCleanupCallback = null; + this.mCleanable = null; } /** @@ -597,6 +609,8 @@ public class JobParameters implements Parcelable { mStopReason = in.readInt(); mInternalStopReason = in.readInt(); debugStopReason = in.readString(); + mJobCleanupCallback = null; + mCleanable = null; } /** @hide */ @@ -612,6 +626,54 @@ public class JobParameters implements Parcelable { this.debugStopReason = debugStopReason; } + /** @hide */ + public void initCleaner(JobCleanupCallback jobCleanupCallback) { + mJobCleanupCallback = jobCleanupCallback; + mCleanable = SystemCleaner.cleaner().register(this, mJobCleanupCallback); + } + + /** + * Lazy initialize the cleaner and enable it + * + * @hide + */ + public void enableCleaner() { + if (mJobCleanupCallback == null) { + initCleaner(new JobCleanupCallback(IJobCallback.Stub.asInterface(callback), jobId)); + } + mJobCleanupCallback.enableCleaner(); + } + + /** + * Disable the cleaner from running and unregister it + * + * @hide + */ + public void disableCleaner() { + if (mJobCleanupCallback != null) { + mJobCleanupCallback.disableCleaner(); + if (mCleanable != null) { + mCleanable.clean(); + mCleanable = null; + } + mJobCleanupCallback = null; + } + } + + /** @hide */ + @VisibleForTesting + @Nullable + public Cleaner.Cleanable getCleanable() { + return mCleanable; + } + + /** @hide */ + @VisibleForTesting + @Nullable + public JobCleanupCallback getJobCleanupCallback() { + return mJobCleanupCallback; + } + @Override public int describeContents() { return 0; @@ -647,6 +709,67 @@ public class JobParameters implements Parcelable { dest.writeString(debugStopReason); } + /** + * JobCleanupCallback is used track JobParameters leak. If the job is started + * and jobFinish is not called at the time of garbage collection of JobParameters + * instance, it is considered a job leak. Force finish the job. + * + * @hide + */ + public static class JobCleanupCallback implements Runnable { + private final IJobCallback mCallback; + private final int mJobId; + private boolean mIsCleanerEnabled; + + public JobCleanupCallback( + IJobCallback callback, + int jobId) { + mCallback = callback; + mJobId = jobId; + mIsCleanerEnabled = false; + } + + /** + * Check if the cleaner is enabled + * + * @hide + */ + public boolean isCleanerEnabled() { + return mIsCleanerEnabled; + } + + /** + * Enable the cleaner to detect JobParameter leak + * + * @hide + */ + public void enableCleaner() { + mIsCleanerEnabled = true; + } + + /** + * Disable the cleaner from running. + * + * @hide + */ + public void disableCleaner() { + mIsCleanerEnabled = false; + } + + /** @hide */ + @Override + public void run() { + if (!isCleanerEnabled()) { + return; + } + try { + mCallback.forceJobFinished(mJobId); + } catch (Exception e) { + Log.wtf(TAG, "Could not destroy running job", e); + } + } + } + public static final @android.annotation.NonNull Creator<JobParameters> CREATOR = new Creator<JobParameters>() { @Override public JobParameters createFromParcel(Parcel in) { diff --git a/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java b/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java index 79d87edff9b2..5f80c52388b4 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java @@ -165,7 +165,13 @@ public abstract class JobServiceEngine { case MSG_EXECUTE_JOB: { final JobParameters params = (JobParameters) msg.obj; try { + if (Flags.cleanupEmptyJobs()) { + params.enableCleaner(); + } boolean workOngoing = JobServiceEngine.this.onStartJob(params); + if (Flags.cleanupEmptyJobs() && !workOngoing) { + params.disableCleaner(); + } ackStartMessage(params, workOngoing); } catch (Exception e) { Log.e(TAG, "Error while executing job: " + params.getJobId()); @@ -190,6 +196,9 @@ public abstract class JobServiceEngine { IJobCallback callback = params.getCallback(); if (callback != null) { try { + if (Flags.cleanupEmptyJobs()) { + params.disableCleaner(); + } callback.jobFinished(params.getJobId(), needsReschedule); } catch (RemoteException e) { Log.e(TAG, "Error reporting job finish to system: binder has gone" + 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 be8e304a8101..ee246d84997f 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java @@ -129,6 +129,8 @@ public final class JobServiceContext implements ServiceConnection { private static final String[] VERB_STRINGS = { "VERB_BINDING", "VERB_STARTING", "VERB_EXECUTING", "VERB_STOPPING", "VERB_FINISHED" }; + private static final String TRACE_JOB_FORCE_FINISHED_PREFIX = "forceJobFinished:"; + private static final String TRACE_JOB_FORCE_FINISHED_DELIMITER = "#"; // States that a job occupies while interacting with the client. static final int VERB_BINDING = 0; @@ -292,6 +294,11 @@ public final class JobServiceContext implements ServiceConnection { } @Override + public void forceJobFinished(int jobId) { + doForceJobFinished(this, jobId); + } + + @Override public void updateEstimatedNetworkBytes(int jobId, JobWorkItem item, long downloadBytes, long uploadBytes) { doUpdateEstimatedNetworkBytes(this, jobId, item, downloadBytes, uploadBytes); @@ -762,6 +769,35 @@ public final class JobServiceContext implements ServiceConnection { } } + /** + * This method just adds traces to evaluate jobs that leak jobparameters at the client. + * It does not stop the job. + */ + void doForceJobFinished(JobCallback cb, int jobId) { + final long ident = Binder.clearCallingIdentity(); + try { + final JobStatus executing; + synchronized (mLock) { + // not the current job, presumably it has finished in some way already + if (!verifyCallerLocked(cb)) { + return; + } + + executing = getRunningJobLocked(); + } + if (executing != null && jobId == executing.getJobId()) { + final StringBuilder stateSuffix = new StringBuilder(); + stateSuffix.append(TRACE_JOB_FORCE_FINISHED_PREFIX); + stateSuffix.append(executing.getBatteryName()); + stateSuffix.append(TRACE_JOB_FORCE_FINISHED_DELIMITER); + stateSuffix.append(executing.getJobId()); + Trace.instant(Trace.TRACE_TAG_POWER, stateSuffix.toString()); + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + private void doAcknowledgeGetTransferredDownloadBytesMessage(JobCallback cb, int jobId, int workId, @BytesLong long transferredBytes) { // TODO(255393346): Make sure apps call this appropriately and monitor for abuse diff --git a/api/api.go b/api/api.go index 5b7f534443fb..e9f1feebd899 100644 --- a/api/api.go +++ b/api/api.go @@ -15,7 +15,7 @@ package api import ( - "sort" + "slices" "github.com/google/blueprint/proptools" @@ -75,31 +75,25 @@ func registerBuildComponents(ctx android.RegistrationContext) { var PrepareForCombinedApisTest = android.FixtureRegisterWithContext(registerBuildComponents) -func (a *CombinedApis) bootclasspath(ctx android.ConfigAndErrorContext) []string { - return a.properties.Bootclasspath.GetOrDefault(a.ConfigurableEvaluator(ctx), nil) -} - -func (a *CombinedApis) systemServerClasspath(ctx android.ConfigAndErrorContext) []string { - return a.properties.System_server_classpath.GetOrDefault(a.ConfigurableEvaluator(ctx), nil) -} - func (a *CombinedApis) apiFingerprintStubDeps(ctx android.BottomUpMutatorContext) []string { - ret := []string{} + bootClasspath := a.properties.Bootclasspath.GetOrDefault(ctx, nil) + systemServerClasspath := a.properties.System_server_classpath.GetOrDefault(ctx, nil) + var ret []string ret = append( ret, - transformArray(a.bootclasspath(ctx), "", ".stubs")..., + transformArray(bootClasspath, "", ".stubs")..., ) ret = append( ret, - transformArray(a.bootclasspath(ctx), "", ".stubs.system")..., + transformArray(bootClasspath, "", ".stubs.system")..., ) ret = append( ret, - transformArray(a.bootclasspath(ctx), "", ".stubs.module_lib")..., + transformArray(bootClasspath, "", ".stubs.module_lib")..., ) ret = append( ret, - transformArray(a.systemServerClasspath(ctx), "", ".stubs.system_server")..., + transformArray(systemServerClasspath, "", ".stubs.system_server")..., ) return ret } @@ -129,7 +123,7 @@ type genruleProps struct { Cmd *string Dists []android.Dist Out []string - Srcs []string + Srcs proptools.Configurable[[]string] Tools []string Visibility []string } @@ -137,7 +131,7 @@ type genruleProps struct { type libraryProps struct { Name *string Sdk_version *string - Static_libs []string + Static_libs proptools.Configurable[[]string] Visibility []string Defaults []string Is_stubs_module *bool @@ -145,7 +139,7 @@ type libraryProps struct { type fgProps struct { Name *string - Srcs []string + Srcs proptools.Configurable[[]string] Visibility []string } @@ -166,7 +160,7 @@ type MergedTxtDefinition struct { // The module for the non-updatable / non-module part of the api. BaseTxt string // The list of modules that are relevant for this merged txt. - Modules []string + Modules proptools.Configurable[[]string] // The output tag for each module to use.e.g. {.public.api.txt} for current.txt ModuleTag string // public, system, module-lib or system-server @@ -190,7 +184,8 @@ func createMergedTxt(ctx android.LoadHookContext, txt MergedTxtDefinition, stubs props.Tools = []string{"metalava"} props.Out = []string{filename} props.Cmd = proptools.StringPtr(metalavaCmd + "$(in) --out $(out)") - props.Srcs = append([]string{txt.BaseTxt}, createSrcs(txt.Modules, txt.ModuleTag)...) + props.Srcs = proptools.NewSimpleConfigurable([]string{txt.BaseTxt}) + props.Srcs.Append(createSrcs(txt.Modules, txt.ModuleTag)) if doDist { props.Dists = []android.Dist{ { @@ -209,11 +204,11 @@ func createMergedTxt(ctx android.LoadHookContext, txt MergedTxtDefinition, stubs ctx.CreateModule(genrule.GenRuleFactory, &props) } -func createMergedAnnotationsFilegroups(ctx android.LoadHookContext, modules, system_server_modules []string) { +func createMergedAnnotationsFilegroups(ctx android.LoadHookContext, modules, system_server_modules proptools.Configurable[[]string]) { for _, i := range []struct { name string tag string - modules []string + modules proptools.Configurable[[]string] }{ { name: "all-modules-public-annotations", @@ -240,33 +235,39 @@ func createMergedAnnotationsFilegroups(ctx android.LoadHookContext, modules, sys } } -func createMergedPublicStubs(ctx android.LoadHookContext, modules []string) { +func createMergedPublicStubs(ctx android.LoadHookContext, modules proptools.Configurable[[]string]) { + modules = modules.Clone() + transformConfigurableArray(modules, "", ".stubs") props := libraryProps{} props.Name = proptools.StringPtr("all-modules-public-stubs") - props.Static_libs = transformArray(modules, "", ".stubs") + props.Static_libs = modules props.Sdk_version = proptools.StringPtr("module_current") props.Visibility = []string{"//frameworks/base"} props.Is_stubs_module = proptools.BoolPtr(true) ctx.CreateModule(java.LibraryFactory, &props) } -func createMergedPublicExportableStubs(ctx android.LoadHookContext, modules []string) { +func createMergedPublicExportableStubs(ctx android.LoadHookContext, modules proptools.Configurable[[]string]) { + modules = modules.Clone() + transformConfigurableArray(modules, "", ".stubs.exportable") props := libraryProps{} props.Name = proptools.StringPtr("all-modules-public-stubs-exportable") - props.Static_libs = transformArray(modules, "", ".stubs.exportable") + props.Static_libs = modules props.Sdk_version = proptools.StringPtr("module_current") props.Visibility = []string{"//frameworks/base"} props.Is_stubs_module = proptools.BoolPtr(true) ctx.CreateModule(java.LibraryFactory, &props) } -func createMergedSystemStubs(ctx android.LoadHookContext, modules []string) { +func createMergedSystemStubs(ctx android.LoadHookContext, modules proptools.Configurable[[]string]) { // First create the all-updatable-modules-system-stubs { - updatable_modules := removeAll(modules, non_updatable_modules) + updatable_modules := modules.Clone() + removeAll(updatable_modules, non_updatable_modules) + transformConfigurableArray(updatable_modules, "", ".stubs.system") props := libraryProps{} props.Name = proptools.StringPtr("all-updatable-modules-system-stubs") - props.Static_libs = transformArray(updatable_modules, "", ".stubs.system") + props.Static_libs = updatable_modules props.Sdk_version = proptools.StringPtr("module_current") props.Visibility = []string{"//frameworks/base"} props.Is_stubs_module = proptools.BoolPtr(true) @@ -275,10 +276,11 @@ func createMergedSystemStubs(ctx android.LoadHookContext, modules []string) { // Now merge all-updatable-modules-system-stubs and stubs from non-updatable modules // into all-modules-system-stubs. { + static_libs := transformArray(non_updatable_modules, "", ".stubs.system") + static_libs = append(static_libs, "all-updatable-modules-system-stubs") props := libraryProps{} props.Name = proptools.StringPtr("all-modules-system-stubs") - props.Static_libs = transformArray(non_updatable_modules, "", ".stubs.system") - props.Static_libs = append(props.Static_libs, "all-updatable-modules-system-stubs") + props.Static_libs = proptools.NewSimpleConfigurable(static_libs) props.Sdk_version = proptools.StringPtr("module_current") props.Visibility = []string{"//frameworks/base"} props.Is_stubs_module = proptools.BoolPtr(true) @@ -286,13 +288,15 @@ func createMergedSystemStubs(ctx android.LoadHookContext, modules []string) { } } -func createMergedSystemExportableStubs(ctx android.LoadHookContext, modules []string) { +func createMergedSystemExportableStubs(ctx android.LoadHookContext, modules proptools.Configurable[[]string]) { // First create the all-updatable-modules-system-stubs { - updatable_modules := removeAll(modules, non_updatable_modules) + updatable_modules := modules.Clone() + removeAll(updatable_modules, non_updatable_modules) + transformConfigurableArray(updatable_modules, "", ".stubs.exportable.system") props := libraryProps{} props.Name = proptools.StringPtr("all-updatable-modules-system-stubs-exportable") - props.Static_libs = transformArray(updatable_modules, "", ".stubs.exportable.system") + props.Static_libs = updatable_modules props.Sdk_version = proptools.StringPtr("module_current") props.Visibility = []string{"//frameworks/base"} props.Is_stubs_module = proptools.BoolPtr(true) @@ -301,10 +305,11 @@ func createMergedSystemExportableStubs(ctx android.LoadHookContext, modules []st // Now merge all-updatable-modules-system-stubs and stubs from non-updatable modules // into all-modules-system-stubs. { + static_libs := transformArray(non_updatable_modules, "", ".stubs.exportable.system") + static_libs = append(static_libs, "all-updatable-modules-system-stubs-exportable") props := libraryProps{} props.Name = proptools.StringPtr("all-modules-system-stubs-exportable") - props.Static_libs = transformArray(non_updatable_modules, "", ".stubs.exportable.system") - props.Static_libs = append(props.Static_libs, "all-updatable-modules-system-stubs-exportable") + props.Static_libs = proptools.NewSimpleConfigurable(static_libs) props.Sdk_version = proptools.StringPtr("module_current") props.Visibility = []string{"//frameworks/base"} props.Is_stubs_module = proptools.BoolPtr(true) @@ -315,7 +320,7 @@ func createMergedSystemExportableStubs(ctx android.LoadHookContext, modules []st func createMergedTestStubsForNonUpdatableModules(ctx android.LoadHookContext) { props := libraryProps{} props.Name = proptools.StringPtr("all-non-updatable-modules-test-stubs") - props.Static_libs = transformArray(non_updatable_modules, "", ".stubs.test") + props.Static_libs = proptools.NewSimpleConfigurable(transformArray(non_updatable_modules, "", ".stubs.test")) props.Sdk_version = proptools.StringPtr("module_current") props.Visibility = []string{"//frameworks/base"} props.Is_stubs_module = proptools.BoolPtr(true) @@ -325,25 +330,27 @@ func createMergedTestStubsForNonUpdatableModules(ctx android.LoadHookContext) { func createMergedTestExportableStubsForNonUpdatableModules(ctx android.LoadHookContext) { props := libraryProps{} props.Name = proptools.StringPtr("all-non-updatable-modules-test-stubs-exportable") - props.Static_libs = transformArray(non_updatable_modules, "", ".stubs.exportable.test") + props.Static_libs = proptools.NewSimpleConfigurable(transformArray(non_updatable_modules, "", ".stubs.exportable.test")) props.Sdk_version = proptools.StringPtr("module_current") props.Visibility = []string{"//frameworks/base"} props.Is_stubs_module = proptools.BoolPtr(true) ctx.CreateModule(java.LibraryFactory, &props) } -func createMergedFrameworkImpl(ctx android.LoadHookContext, modules []string) { +func createMergedFrameworkImpl(ctx android.LoadHookContext, modules proptools.Configurable[[]string]) { + modules = modules.Clone() // This module is for the "framework-all" module, which should not include the core libraries. - modules = removeAll(modules, core_libraries_modules) + removeAll(modules, core_libraries_modules) // Remove the modules that belong to non-updatable APEXes since those are allowed to compile // against unstable APIs. - modules = removeAll(modules, non_updatable_modules) + removeAll(modules, non_updatable_modules) // First create updatable-framework-module-impl, which contains all updatable modules. // This module compiles against module_lib SDK. { + transformConfigurableArray(modules, "", ".impl") props := libraryProps{} props.Name = proptools.StringPtr("updatable-framework-module-impl") - props.Static_libs = transformArray(modules, "", ".impl") + props.Static_libs = modules props.Sdk_version = proptools.StringPtr("module_current") props.Visibility = []string{"//frameworks/base"} ctx.CreateModule(java.LibraryFactory, &props) @@ -352,65 +359,74 @@ func createMergedFrameworkImpl(ctx android.LoadHookContext, modules []string) { // Now create all-framework-module-impl, which contains updatable-framework-module-impl // and all non-updatable modules. This module compiles against hidden APIs. { + static_libs := transformArray(non_updatable_modules, "", ".impl") + static_libs = append(static_libs, "updatable-framework-module-impl") props := libraryProps{} props.Name = proptools.StringPtr("all-framework-module-impl") - props.Static_libs = transformArray(non_updatable_modules, "", ".impl") - props.Static_libs = append(props.Static_libs, "updatable-framework-module-impl") + props.Static_libs = proptools.NewSimpleConfigurable(static_libs) props.Sdk_version = proptools.StringPtr("core_platform") props.Visibility = []string{"//frameworks/base"} ctx.CreateModule(java.LibraryFactory, &props) } } -func createMergedFrameworkModuleLibExportableStubs(ctx android.LoadHookContext, modules []string) { +func createMergedFrameworkModuleLibExportableStubs(ctx android.LoadHookContext, modules proptools.Configurable[[]string]) { + modules = modules.Clone() // The user of this module compiles against the "core" SDK and against non-updatable modules, // so remove to avoid dupes. - modules = removeAll(modules, core_libraries_modules) - modules = removeAll(modules, non_updatable_modules) + removeAll(modules, core_libraries_modules) + removeAll(modules, non_updatable_modules) + transformConfigurableArray(modules, "", ".stubs.exportable.module_lib") props := libraryProps{} props.Name = proptools.StringPtr("framework-updatable-stubs-module_libs_api-exportable") - props.Static_libs = transformArray(modules, "", ".stubs.exportable.module_lib") + props.Static_libs = modules props.Sdk_version = proptools.StringPtr("module_current") props.Visibility = []string{"//frameworks/base"} props.Is_stubs_module = proptools.BoolPtr(true) ctx.CreateModule(java.LibraryFactory, &props) } -func createMergedFrameworkModuleLibStubs(ctx android.LoadHookContext, modules []string) { +func createMergedFrameworkModuleLibStubs(ctx android.LoadHookContext, modules proptools.Configurable[[]string]) { + modules = modules.Clone() // The user of this module compiles against the "core" SDK and against non-updatable modules, // so remove to avoid dupes. - modules = removeAll(modules, core_libraries_modules) - modules = removeAll(modules, non_updatable_modules) + removeAll(modules, core_libraries_modules) + removeAll(modules, non_updatable_modules) + transformConfigurableArray(modules, "", ".stubs.module_lib") props := libraryProps{} props.Name = proptools.StringPtr("framework-updatable-stubs-module_libs_api") - props.Static_libs = transformArray(modules, "", ".stubs.module_lib") + props.Static_libs = modules props.Sdk_version = proptools.StringPtr("module_current") props.Visibility = []string{"//frameworks/base"} props.Is_stubs_module = proptools.BoolPtr(true) ctx.CreateModule(java.LibraryFactory, &props) } -func createMergedFrameworkSystemServerExportableStubs(ctx android.LoadHookContext, bootclasspath, system_server_classpath []string) { +func createMergedFrameworkSystemServerExportableStubs(ctx android.LoadHookContext, bootclasspath, system_server_classpath proptools.Configurable[[]string]) { // The user of this module compiles against the "core" SDK and against non-updatable bootclasspathModules, // so remove to avoid dupes. - bootclasspathModules := removeAll(bootclasspath, core_libraries_modules) - bootclasspathModules = removeAll(bootclasspath, non_updatable_modules) - modules := append( - // Include all the module-lib APIs from the bootclasspath libraries. - transformArray(bootclasspathModules, "", ".stubs.exportable.module_lib"), - // Then add all the system-server APIs from the service-* libraries. - transformArray(system_server_classpath, "", ".stubs.exportable.system_server")..., - ) + bootclasspathModules := bootclasspath.Clone() + removeAll(bootclasspathModules, core_libraries_modules) + removeAll(bootclasspathModules, non_updatable_modules) + transformConfigurableArray(bootclasspathModules, "", ".stubs.exportable.module_lib") + + system_server_classpath = system_server_classpath.Clone() + transformConfigurableArray(system_server_classpath, "", ".stubs.exportable.system_server") + + // Include all the module-lib APIs from the bootclasspath libraries. + // Then add all the system-server APIs from the service-* libraries. + bootclasspathModules.Append(system_server_classpath) + props := libraryProps{} props.Name = proptools.StringPtr("framework-updatable-stubs-system_server_api-exportable") - props.Static_libs = modules + props.Static_libs = bootclasspathModules props.Sdk_version = proptools.StringPtr("system_server_current") props.Visibility = []string{"//frameworks/base"} props.Is_stubs_module = proptools.BoolPtr(true) ctx.CreateModule(java.LibraryFactory, &props) } -func createPublicStubsSourceFilegroup(ctx android.LoadHookContext, modules []string) { +func createPublicStubsSourceFilegroup(ctx android.LoadHookContext, modules proptools.Configurable[[]string]) { props := fgProps{} props.Name = proptools.StringPtr("all-modules-public-stubs-source") props.Srcs = createSrcs(modules, "{.public.stubs.source}") @@ -418,7 +434,14 @@ func createPublicStubsSourceFilegroup(ctx android.LoadHookContext, modules []str ctx.CreateModule(android.FileGroupFactory, &props) } -func createMergedTxts(ctx android.LoadHookContext, bootclasspath, system_server_classpath []string, baseTxtModulePrefix, stubsTypeSuffix string, doDist bool) { +func createMergedTxts( + ctx android.LoadHookContext, + bootclasspath proptools.Configurable[[]string], + system_server_classpath proptools.Configurable[[]string], + baseTxtModulePrefix string, + stubsTypeSuffix string, + doDist bool, +) { var textFiles []MergedTxtDefinition tagSuffix := []string{".api.txt}", ".removed-api.txt}"} @@ -463,11 +486,10 @@ func createMergedTxts(ctx android.LoadHookContext, bootclasspath, system_server_ } func (a *CombinedApis) createInternalModules(ctx android.LoadHookContext) { - bootclasspath := a.bootclasspath(ctx) - system_server_classpath := a.systemServerClasspath(ctx) + bootclasspath := a.properties.Bootclasspath.Clone() + system_server_classpath := a.properties.System_server_classpath.Clone() if ctx.Config().VendorConfig("ANDROID").Bool("include_nonpublic_framework_api") { - bootclasspath = append(bootclasspath, a.properties.Conditional_bootclasspath...) - sort.Strings(bootclasspath) + bootclasspath.AppendSimpleValue(a.properties.Conditional_bootclasspath) } createMergedTxts(ctx, bootclasspath, system_server_classpath, "non-updatable-", "-", false) createMergedTxts(ctx, bootclasspath, system_server_classpath, "non-updatable-exportable-", "-exportable-", true) @@ -500,8 +522,10 @@ func combinedApisModuleFactory() android.Module { // Various utility methods below. // Creates an array of ":<m><tag>" for each m in <modules>. -func createSrcs(modules []string, tag string) []string { - return transformArray(modules, ":", tag) +func createSrcs(modules proptools.Configurable[[]string], tag string) proptools.Configurable[[]string] { + result := modules.Clone() + transformConfigurableArray(result, ":", tag) + return result } // Creates an array of "<prefix><m><suffix>", for each m in <modules>. @@ -513,11 +537,23 @@ func transformArray(modules []string, prefix, suffix string) []string { return a } -func removeAll(s []string, vs []string) []string { - for _, v := range vs { - s = remove(s, v) - } - return s +// Creates an array of "<prefix><m><suffix>", for each m in <modules>. +func transformConfigurableArray(modules proptools.Configurable[[]string], prefix, suffix string) { + modules.AddPostProcessor(func(s []string) []string { + return transformArray(s, prefix, suffix) + }) +} + +func removeAll(s proptools.Configurable[[]string], vs []string) { + s.AddPostProcessor(func(s []string) []string { + a := make([]string, 0, len(s)) + for _, module := range s { + if !slices.Contains(vs, module) { + a = append(a, module) + } + } + return a + }) } func remove(s []string, v string) []string { diff --git a/cmds/uinput/examples/test-touchpad.evemu b/cmds/uinput/examples/test-touchpad.evemu new file mode 100644 index 000000000000..34ee5721b630 --- /dev/null +++ b/cmds/uinput/examples/test-touchpad.evemu @@ -0,0 +1,44 @@ +# EVEMU 1.2 +# This is an evemu "recording" of an Apple Magic Trackpad (1st generation), but +# that doesn't actually make any movements. It just runs for a very long time, +# to make Android think a touchpad is connected. This is useful for testing +# things like the settings in System > Touchpad, which only appear when one is +# connected. +# +# It can be played by piping it to the uinput command over ADB: +# $ adb shell uinput - < test-touchpad.evemu +N: Fake touchpad +I: 0005 05ac 030e 0160 +P: 05 00 00 00 00 00 00 00 +B: 00 0b 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 01 00 00 00 00 00 +B: 01 20 e5 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 02 00 00 00 00 00 00 00 00 +B: 03 03 00 00 00 00 80 73 02 +B: 04 10 00 00 00 00 00 00 00 +B: 05 00 00 00 00 00 00 00 00 +B: 11 00 00 00 00 00 00 00 00 +B: 12 00 00 00 00 00 00 00 00 +A: 00 -2909 3167 4 0 46 +A: 01 -2456 2565 4 0 45 +A: 2f 0 15 0 0 0 +A: 30 0 1020 4 0 0 +A: 31 0 1020 4 0 0 +A: 34 -31 32 1 0 0 +A: 35 -2909 3167 4 0 46 +A: 36 -2456 2565 4 0 45 +A: 39 0 65535 0 0 0 +E: 0.000001 0004 0005 1234 +E: 0.000001 0000 0000 0000 +E: 1000000000.000000 0004 0005 1235 +E: 1000000000.000000 0000 0000 0000 diff --git a/core/api/current.txt b/core/api/current.txt index 56852212ab1e..5e8febebee0e 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -145,12 +145,12 @@ package android { field public static final String MANAGE_DEVICE_POLICY_AUDIO_OUTPUT = "android.permission.MANAGE_DEVICE_POLICY_AUDIO_OUTPUT"; field public static final String MANAGE_DEVICE_POLICY_AUTOFILL = "android.permission.MANAGE_DEVICE_POLICY_AUTOFILL"; field public static final String MANAGE_DEVICE_POLICY_BACKUP_SERVICE = "android.permission.MANAGE_DEVICE_POLICY_BACKUP_SERVICE"; - field @FlaggedApi("android.app.admin.flags.dedicated_device_control_api_enabled") public static final String MANAGE_DEVICE_POLICY_BLOCK_UNINSTALL = "android.permission.MANAGE_DEVICE_POLICY_BLOCK_UNINSTALL"; + field public static final String MANAGE_DEVICE_POLICY_BLOCK_UNINSTALL = "android.permission.MANAGE_DEVICE_POLICY_BLOCK_UNINSTALL"; field public static final String MANAGE_DEVICE_POLICY_BLUETOOTH = "android.permission.MANAGE_DEVICE_POLICY_BLUETOOTH"; field public static final String MANAGE_DEVICE_POLICY_BUGREPORT = "android.permission.MANAGE_DEVICE_POLICY_BUGREPORT"; field public static final String MANAGE_DEVICE_POLICY_CALLS = "android.permission.MANAGE_DEVICE_POLICY_CALLS"; field public static final String MANAGE_DEVICE_POLICY_CAMERA = "android.permission.MANAGE_DEVICE_POLICY_CAMERA"; - field @FlaggedApi("android.app.admin.flags.dedicated_device_control_api_enabled") public static final String MANAGE_DEVICE_POLICY_CAMERA_TOGGLE = "android.permission.MANAGE_DEVICE_POLICY_CAMERA_TOGGLE"; + field public static final String MANAGE_DEVICE_POLICY_CAMERA_TOGGLE = "android.permission.MANAGE_DEVICE_POLICY_CAMERA_TOGGLE"; field public static final String MANAGE_DEVICE_POLICY_CERTIFICATES = "android.permission.MANAGE_DEVICE_POLICY_CERTIFICATES"; field public static final String MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE = "android.permission.MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE"; field @FlaggedApi("android.view.contentprotection.flags.manage_device_policy_enabled") public static final String MANAGE_DEVICE_POLICY_CONTENT_PROTECTION = "android.permission.MANAGE_DEVICE_POLICY_CONTENT_PROTECTION"; @@ -172,7 +172,7 @@ package android { field public static final String MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS = "android.permission.MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS"; field public static final String MANAGE_DEVICE_POLICY_METERED_DATA = "android.permission.MANAGE_DEVICE_POLICY_METERED_DATA"; field public static final String MANAGE_DEVICE_POLICY_MICROPHONE = "android.permission.MANAGE_DEVICE_POLICY_MICROPHONE"; - field @FlaggedApi("android.app.admin.flags.dedicated_device_control_api_enabled") public static final String MANAGE_DEVICE_POLICY_MICROPHONE_TOGGLE = "android.permission.MANAGE_DEVICE_POLICY_MICROPHONE_TOGGLE"; + field public static final String MANAGE_DEVICE_POLICY_MICROPHONE_TOGGLE = "android.permission.MANAGE_DEVICE_POLICY_MICROPHONE_TOGGLE"; field public static final String MANAGE_DEVICE_POLICY_MOBILE_NETWORK = "android.permission.MANAGE_DEVICE_POLICY_MOBILE_NETWORK"; field public static final String MANAGE_DEVICE_POLICY_MODIFY_USERS = "android.permission.MANAGE_DEVICE_POLICY_MODIFY_USERS"; field public static final String MANAGE_DEVICE_POLICY_MTE = "android.permission.MANAGE_DEVICE_POLICY_MTE"; @@ -8121,7 +8121,7 @@ package android.app.admin { method public boolean isLogoutEnabled(); method public boolean isManagedProfile(@NonNull android.content.ComponentName); method public boolean isMasterVolumeMuted(@NonNull android.content.ComponentName); - method @FlaggedApi("android.app.admin.flags.is_mte_policy_enforced") public static boolean isMtePolicyEnforced(); + method public static boolean isMtePolicyEnforced(); method public boolean isNetworkLoggingEnabled(@Nullable android.content.ComponentName); method public boolean isOrganizationOwnedDeviceWithManagedProfile(); method public boolean isOverrideApnEnabled(@NonNull android.content.ComponentName); @@ -8597,7 +8597,7 @@ package android.app.admin { field public static final int TAG_ADB_SHELL_CMD = 210002; // 0x33452 field public static final int TAG_ADB_SHELL_INTERACTIVE = 210001; // 0x33451 field public static final int TAG_APP_PROCESS_START = 210005; // 0x33455 - field @FlaggedApi("android.app.admin.flags.backup_service_security_log_event_enabled") public static final int TAG_BACKUP_SERVICE_TOGGLED = 210044; // 0x3347c + field public static final int TAG_BACKUP_SERVICE_TOGGLED = 210044; // 0x3347c field public static final int TAG_BLUETOOTH_CONNECTION = 210039; // 0x33477 field public static final int TAG_BLUETOOTH_DISCONNECTION = 210040; // 0x33478 field public static final int TAG_CAMERA_POLICY_SET = 210034; // 0x33472 diff --git a/core/api/system-current.txt b/core/api/system-current.txt index ba160372f51d..60dc52b655d6 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -1322,7 +1322,7 @@ package android.app.admin { method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public android.os.UserHandle getDeviceOwnerUser(); method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public android.app.admin.DevicePolicyState getDevicePolicyState(); method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public String getFinancedDeviceKioskRoleHolder(); - method @FlaggedApi("android.app.admin.flags.device_policy_size_tracking_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public int getMaxPolicyStorageLimit(); + method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public int getMaxPolicyStorageLimit(); method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_ADMIN_POLICY}) public java.util.List<java.lang.String> getPermittedAccessibilityServices(int); method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_ADMIN_POLICY}) public java.util.List<java.lang.String> getPermittedInputMethodsForCurrentUser(); method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public java.util.List<android.os.UserHandle> getPolicyManagedProfiles(@NonNull android.os.UserHandle); @@ -1349,7 +1349,7 @@ package android.app.admin { method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void setAuditLogEventCallback(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.List<android.app.admin.SecurityLog.SecurityEvent>>); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setDeviceProvisioningConfigApplied(); method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setDpcDownloaded(boolean); - method @FlaggedApi("android.app.admin.flags.device_policy_size_tracking_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setMaxPolicyStorageLimit(int); + method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setMaxPolicyStorageLimit(int); method @Deprecated @RequiresPermission(value=android.Manifest.permission.GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS, conditional=true) public void setProfileOwnerCanAccessDeviceIds(@NonNull android.content.ComponentName); method public void setSecondaryLockscreenEnabled(@NonNull android.content.ComponentName, boolean); method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setUserProvisioningState(int, @NonNull android.os.UserHandle); diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 42f761570849..4fc70769a3b1 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -2003,6 +2003,7 @@ package android.media { method public void setRampingRingerEnabled(boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public void setRs2Value(float); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setTestDeviceConnectionState(@NonNull android.media.AudioDeviceAttributes, boolean); + method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public void setVolumeControllerLongPressTimeoutEnabled(boolean); method @FlaggedApi("android.media.audio.focus_exclusive_with_recording") @RequiresPermission(android.Manifest.permission.QUERY_AUDIO_STATE) public boolean shouldNotificationSoundPlay(@NonNull android.media.AudioAttributes); } diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 8fd332621599..f27dc322a2b7 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -79,7 +79,6 @@ import android.permission.flags.Flags; import android.provider.DeviceConfig; import android.util.ArrayMap; import android.util.ArraySet; -import android.util.Log; import android.util.LongSparseArray; import android.util.LongSparseLongArray; import android.util.Pools; @@ -3156,12 +3155,6 @@ public class AppOpsManager { /** @hide */ public static final String KEY_HISTORICAL_OPS = "historical_ops"; - /** System properties for debug logging of noteOp call sites */ - private static final String DEBUG_LOGGING_ENABLE_PROP = "appops.logging_enabled"; - private static final String DEBUG_LOGGING_PACKAGES_PROP = "appops.logging_packages"; - private static final String DEBUG_LOGGING_OPS_PROP = "appops.logging_ops"; - private static final String DEBUG_LOGGING_TAG = "AppOpsManager"; - /** * Retrieve the op switch that controls the given operation. * @hide @@ -8066,14 +8059,6 @@ public class AppOpsManager { @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES) public void setUidMode(int code, int uid, @Mode int mode) { try { - // TODO(b/302609140): Remove extra logging after this issue is diagnosed. - if (code == OP_BLUETOOTH_CONNECT) { - Log.i(DEBUG_LOGGING_TAG, - "setUidMode called for OP_BLUETOOTH_CONNECT with mode: " + mode - + " for uid: " + uid + " calling uid: " + Binder.getCallingUid() - + " trace: " - + Arrays.toString(Thread.currentThread().getStackTrace())); - } mService.setUidMode(code, uid, mode); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -8094,15 +8079,6 @@ public class AppOpsManager { @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES) public void setUidMode(@NonNull String appOp, int uid, @Mode int mode) { try { - // TODO(b/302609140): Remove extra logging after this issue is diagnosed. - if (appOp.equals(OPSTR_BLUETOOTH_CONNECT)) { - Log.i(DEBUG_LOGGING_TAG, - "setUidMode called for OPSTR_BLUETOOTH_CONNECT with mode: " + mode - + " for uid: " + uid + " calling uid: " + Binder.getCallingUid() - + " trace: " - + Arrays.toString(Thread.currentThread().getStackTrace())); - } - mService.setUidMode(AppOpsManager.strOpToOp(appOp), uid, mode); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -8143,14 +8119,6 @@ public class AppOpsManager { @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES) public void setMode(int code, int uid, String packageName, @Mode int mode) { try { - // TODO(b/302609140): Remove extra logging after this issue is diagnosed. - if (code == OP_BLUETOOTH_CONNECT) { - Log.i(DEBUG_LOGGING_TAG, - "setMode called for OPSTR_BLUETOOTH_CONNECT with mode: " + mode - + " for uid: " + uid + " calling uid: " + Binder.getCallingUid() - + " trace: " - + Arrays.toString(Thread.currentThread().getStackTrace())); - } mService.setMode(code, uid, packageName, mode); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -8173,14 +8141,6 @@ public class AppOpsManager { public void setMode(@NonNull String op, int uid, @Nullable String packageName, @Mode int mode) { try { - // TODO(b/302609140): Remove extra logging after this issue is diagnosed. - if (op.equals(OPSTR_BLUETOOTH_CONNECT)) { - Log.i(DEBUG_LOGGING_TAG, - "setMode called for OPSTR_BLUETOOTH_CONNECT with mode: " + mode - + " for uid: " + uid + " calling uid: " + Binder.getCallingUid() - + " trace: " - + Arrays.toString(Thread.currentThread().getStackTrace())); - } mService.setMode(strOpToOp(op), uid, packageName, mode); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); diff --git a/core/java/android/app/activity_manager.aconfig b/core/java/android/app/activity_manager.aconfig index 8f91158c499d..c0c81df465e2 100644 --- a/core/java/android/app/activity_manager.aconfig +++ b/core/java/android/app/activity_manager.aconfig @@ -106,6 +106,17 @@ flag { flag { namespace: "backstage_power" + name: "use_app_info_not_launched" + description: "Use the notLaunched state from ApplicationInfo instead of current value" + is_fixed_read_only: true + bug: "362516211" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + namespace: "backstage_power" name: "cache_get_current_user_id" description: "Add caching for getCurrentUserId" is_fixed_read_only: true diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 0f54cb7bc35e..daa15f05d942 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -55,10 +55,8 @@ import static android.Manifest.permission.SET_TIME; import static android.Manifest.permission.SET_TIME_ZONE; import static android.app.admin.DeviceAdminInfo.HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED; import static android.app.admin.flags.Flags.FLAG_DEVICE_THEFT_API_ENABLED; -import static android.app.admin.flags.Flags.FLAG_DEVICE_POLICY_SIZE_TRACKING_ENABLED; import static android.app.admin.flags.Flags.onboardingBugreportV2Enabled; import static android.app.admin.flags.Flags.onboardingConsentlessBugreports; -import static android.app.admin.flags.Flags.FLAG_IS_MTE_POLICY_ENFORCED; import static android.content.Intent.LOCAL_FLAG_FROM_SYSTEM; import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1; import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; @@ -4232,7 +4230,6 @@ public class DevicePolicyManager { * * @return whether MTE is currently enabled on the device. */ - @FlaggedApi(FLAG_IS_MTE_POLICY_ENFORCED) public static boolean isMtePolicyEnforced() { return Zygote.nativeSupportsMemoryTagging(); } @@ -8664,6 +8661,7 @@ public class DevicePolicyManager { * {@link DeviceAdminInfo#USES_POLICY_DISABLE_CAMERA}. */ @RequiresPermission(value = MANAGE_DEVICE_POLICY_CAMERA, conditional = true) + @SupportsCoexistence public void setCameraDisabled(@Nullable ComponentName admin, boolean disabled) { if (mService != null) { try { @@ -10249,6 +10247,7 @@ public class DevicePolicyManager { * permission {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_LOCK_TASK}. */ @RequiresPermission(value = MANAGE_DEVICE_POLICY_LOCK_TASK, conditional = true) + @SupportsCoexistence public void clearPackagePersistentPreferredActivities(@Nullable ComponentName admin, String packageName) { throwIfParentInstance("clearPackagePersistentPreferredActivities"); @@ -11940,6 +11939,7 @@ public class DevicePolicyManager { * @throws SecurityException if {@code admin} is not a device or profile owner and if the caller * has not been granted the permission to set the given user restriction. */ + @SupportsCoexistence public void addUserRestriction(@NonNull ComponentName admin, @UserManager.UserRestrictionKey String key) { if (mService != null) { @@ -12021,6 +12021,7 @@ public class DevicePolicyManager { * @throws IllegalStateException if caller is not targeting Android * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or above. */ + @SupportsCoexistence public void addUserRestrictionGlobally(@NonNull @UserManager.UserRestrictionKey String key) { throwIfParentInstance("addUserRestrictionGlobally"); if (mService != null) { @@ -12076,6 +12077,7 @@ public class DevicePolicyManager { * @throws SecurityException if {@code admin} is not a device or profile owner and if the * caller has not been granted the permission to set the given user restriction. */ + @SupportsCoexistence public void clearUserRestriction(@NonNull ComponentName admin, @UserManager.UserRestrictionKey String key) { if (mService != null) { @@ -12312,6 +12314,7 @@ public class DevicePolicyManager { * @see #DELEGATION_PACKAGE_ACCESS */ @RequiresPermission(value = MANAGE_DEVICE_POLICY_PACKAGE_STATE, conditional = true) + @SupportsCoexistence public boolean setApplicationHidden(@Nullable ComponentName admin, String packageName, boolean hidden) { if (mService != null) { @@ -12492,6 +12495,7 @@ public class DevicePolicyManager { * @throws SecurityException if {@code admin} is not a device or profile owner. */ @RequiresPermission(value = MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT, conditional = true) + @SupportsCoexistence public void setAccountManagementDisabled(@Nullable ComponentName admin, String accountType, boolean disabled) { if (mService != null) { @@ -12575,10 +12579,24 @@ public class DevicePolicyManager { **/ @SystemApi public void setSecondaryLockscreenEnabled(@NonNull ComponentName admin, boolean enabled) { + setSecondaryLockscreenEnabled(admin, enabled, null); + } + + /** + * Called by the system supervision app to set whether a secondary lockscreen needs to be shown. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Null if the + * caller is not a device admin. + * @param enabled Whether or not the lockscreen needs to be shown. + * @param options A {@link PersistableBundle} to supply options to the lock screen. + * @hide + */ + public void setSecondaryLockscreenEnabled(@Nullable ComponentName admin, boolean enabled, + @Nullable PersistableBundle options) { throwIfParentInstance("setSecondaryLockscreenEnabled"); if (mService != null) { try { - mService.setSecondaryLockscreenEnabled(admin, enabled); + mService.setSecondaryLockscreenEnabled(admin, enabled, options); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -14276,6 +14294,7 @@ public class DevicePolicyManager { * @see #retrieveSecurityLogs */ @RequiresPermission(value = MANAGE_DEVICE_POLICY_SECURITY_LOGGING, conditional = true) + @SupportsCoexistence public void setSecurityLoggingEnabled(@Nullable ComponentName admin, boolean enabled) { throwIfParentInstance("setSecurityLoggingEnabled"); try { @@ -17181,6 +17200,7 @@ public class DevicePolicyManager { * if USB data signaling fails to be enabled/disabled. */ @RequiresPermission(value = MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING, conditional = true) + @SupportsCoexistence public void setUsbDataSignalingEnabled(boolean enabled) { throwIfParentInstance("setUsbDataSignalingEnabled"); if (mService != null) { @@ -17746,7 +17766,6 @@ public class DevicePolicyManager { */ @SystemApi @RequiresPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) - @FlaggedApi(FLAG_DEVICE_POLICY_SIZE_TRACKING_ENABLED) public void setMaxPolicyStorageLimit(int storageLimit) { if (mService != null) { try { @@ -17766,7 +17785,6 @@ public class DevicePolicyManager { */ @SystemApi @RequiresPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) - @FlaggedApi(FLAG_DEVICE_POLICY_SIZE_TRACKING_ENABLED) public int getMaxPolicyStorageLimit() { if (mService != null) { try { diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index d4e5c9960c2a..a4e2b8f62a23 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -303,7 +303,7 @@ interface IDevicePolicyManager { String[] getAccountTypesWithManagementDisabled(String callerPackageName); String[] getAccountTypesWithManagementDisabledAsUser(int userId, String callerPackageName, in boolean parent); - void setSecondaryLockscreenEnabled(in ComponentName who, boolean enabled); + void setSecondaryLockscreenEnabled(in ComponentName who, boolean enabled, in PersistableBundle options); boolean isSecondaryLockscreenEnabled(in UserHandle userHandle); void setPreferentialNetworkServiceConfigs( diff --git a/core/java/android/app/admin/SecurityLog.java b/core/java/android/app/admin/SecurityLog.java index 477f2e007b33..beb93fd079d9 100644 --- a/core/java/android/app/admin/SecurityLog.java +++ b/core/java/android/app/admin/SecurityLog.java @@ -16,10 +16,7 @@ package android.app.admin; -import static android.app.admin.flags.Flags.FLAG_BACKUP_SERVICE_SECURITY_LOG_EVENT_ENABLED; - import android.Manifest; -import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -611,7 +608,6 @@ public class SecurityLog { * <li> [2] backup service state ({@code Integer}, 1 for enabled, 0 for disabled) * @see DevicePolicyManager#setBackupServiceEnabled(ComponentName, boolean) */ - @FlaggedApi(FLAG_BACKUP_SERVICE_SECURITY_LOG_EVENT_ENABLED) public static final int TAG_BACKUP_SERVICE_TOGGLED = SecurityLogTags.SECURITY_BACKUP_SERVICE_TOGGLED; /** diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig index 9ed5aa61b3e1..8e08a95dad70 100644 --- a/core/java/android/app/admin/flags/flags.aconfig +++ b/core/java/android/app/admin/flags/flags.aconfig @@ -13,6 +13,7 @@ flag { bug: "289520697" } +# Fully rolled out and must not be used. flag { name: "device_policy_size_tracking_enabled" is_exported: true @@ -22,13 +23,6 @@ flag { } flag { - name: "device_policy_size_tracking_internal_enabled" - namespace: "enterprise" - description: "Add feature to track the total policy size and have a max threshold - internal changes" - bug: "281543351" -} - -flag { name: "onboarding_bugreport_v2_enabled" is_exported: true namespace: "enterprise" @@ -44,13 +38,7 @@ flag { is_fixed_read_only: true } -flag { - name: "dedicated_device_control_enabled" - namespace: "enterprise" - description: "Allow the device management role holder to control which platform features are available on dedicated devices." - bug: "281964214" -} - +# Fully rolled out and must not be used. flag { name: "dedicated_device_control_api_enabled" is_exported: true @@ -170,6 +158,7 @@ flag { bug: "293441361" } +# Fully rolled out and must not be used. flag { name: "assist_content_user_restriction_enabled" is_exported: true @@ -188,6 +177,7 @@ flag { } } +# Fully rolled out and must not be used. flag { name: "backup_service_security_log_event_enabled" is_exported: true @@ -196,6 +186,7 @@ flag { bug: "304999634" } +# Fully rolled out and must not be used. flag { name: "esim_management_enabled" is_exported: true @@ -213,6 +204,7 @@ flag { bug: "289515470" } +# Fully rolled out and must not be used. flag { name: "is_mte_policy_enforced" is_exported: true diff --git a/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java b/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java index c4e8b4157752..c4bfae98e33d 100644 --- a/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java +++ b/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java @@ -72,14 +72,32 @@ public class AppFunctionRuntimeMetadata extends GenericDocument { * 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. + * with {@link android.Manifest.permission#EXECUTE_APP_FUNCTIONS} or {@link + * android.Manifest.permission#EXECUTE_APP_FUNCTIONS_TRUSTED} 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)) + return getAppFunctionRuntimeSchemaBuilder(getRuntimeSchemaNameForPackage(packageName)) + .addParentType(RUNTIME_SCHEMA_TYPE) + .build(); + } + + /** + * Creates a parent schema for all app function runtime schemas. + * + * <p>This schema should be set visible to the owner itself and for callers with {@link + * android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED} or {@link + * android.permission.EXECUTE_APP_FUNCTIONS} permissions. + */ + public static AppSearchSchema createParentAppFunctionRuntimeSchema() { + return getAppFunctionRuntimeSchemaBuilder(RUNTIME_SCHEMA_TYPE).build(); + } + + private static AppSearchSchema.Builder getAppFunctionRuntimeSchemaBuilder( + @NonNull String schemaType) { + return new AppSearchSchema.Builder(schemaType) .addProperty( new AppSearchSchema.StringPropertyConfig.Builder(PROPERTY_FUNCTION_ID) .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) @@ -111,9 +129,7 @@ public class AppFunctionRuntimeMetadata extends GenericDocument { .setJoinableValueType( AppSearchSchema.StringPropertyConfig .JOINABLE_VALUE_TYPE_QUALIFIED_ID) - .build()) - .addParentType(RUNTIME_SCHEMA_TYPE) - .build(); + .build()); } /** Returns the function id. This might look like "com.example.message#send_message". */ diff --git a/core/java/android/app/jank/OWNERS b/core/java/android/app/jank/OWNERS new file mode 100644 index 000000000000..806de574b071 --- /dev/null +++ b/core/java/android/app/jank/OWNERS @@ -0,0 +1,4 @@ +steventerrell@google.com +carmenjackson@google.com +jjaggi@google.com +pmuetschard@google.com
\ No newline at end of file diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig index 297fe8a9e691..748260bc8d5f 100644 --- a/core/java/android/companion/virtual/flags/flags.aconfig +++ b/core/java/android/companion/virtual/flags/flags.aconfig @@ -98,11 +98,11 @@ flag { } flag { - name: "camera_multiple_input_streams" - is_exported: true - namespace: "virtual_devices" - description: "Expose multiple surface for the virtual camera owner for different stream resolution" - bug: "341083465" + name: "camera_multiple_input_streams" + is_exported: true + namespace: "virtual_devices" + description: "Expose multiple surface for the virtual camera owner for different stream resolution" + bug: "341083465" } flag { @@ -113,8 +113,16 @@ flag { } flag { - name: "status_bar_and_insets" - namespace: "virtual_devices" - description: "Allow for status bar and insets on virtual devices" - bug: "350007866" + namespace: "virtual_devices" + name: "display_power_manager_apis" + description: "Make relevant PowerManager APIs display aware by default" + bug: "365042486" + is_fixed_read_only: true +} + +flag { + name: "status_bar_and_insets" + namespace: "virtual_devices" + description: "Allow for status bar and insets on virtual devices" + bug: "350007866" } diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index da3cc1bda3be..031380dc1962 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -86,6 +86,7 @@ import android.util.Log; import android.util.proto.ProtoOutputStream; import com.android.internal.util.XmlUtils; +import com.android.modules.expresslog.Counter; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -12805,6 +12806,8 @@ public class Intent implements Parcelable, Cloneable { new ClipData.Item(text, htmlText, null, stream)); setClipData(clipData); if (stream != null) { + logCounterIfFlagsMissing(FLAG_GRANT_READ_URI_PERMISSION, + "intents.value_explicit_uri_grant_for_send_action"); addFlags(FLAG_GRANT_READ_URI_PERMISSION); } return true; @@ -12846,6 +12849,8 @@ public class Intent implements Parcelable, Cloneable { setClipData(clipData); if (streams != null) { + logCounterIfFlagsMissing(FLAG_GRANT_READ_URI_PERMISSION, + "intents.value_explicit_uri_grant_for_send_multiple_action"); addFlags(FLAG_GRANT_READ_URI_PERMISSION); } return true; @@ -12865,6 +12870,10 @@ public class Intent implements Parcelable, Cloneable { putExtra(MediaStore.EXTRA_OUTPUT, output); setClipData(ClipData.newRawUri("", output)); + + logCounterIfFlagsMissing( + FLAG_GRANT_WRITE_URI_PERMISSION | FLAG_GRANT_READ_URI_PERMISSION, + "intents.value_explicit_uri_grant_for_image_capture_action"); addFlags(FLAG_GRANT_WRITE_URI_PERMISSION|FLAG_GRANT_READ_URI_PERMISSION); return true; } @@ -12873,6 +12882,12 @@ public class Intent implements Parcelable, Cloneable { return false; } + private void logCounterIfFlagsMissing(int requiredFlags, String metricId) { + if ((getFlags() & requiredFlags) != requiredFlags) { + Counter.logIncrement(metricId); + } + } + @android.ravenwood.annotation.RavenwoodThrow private Uri maybeConvertFileToContentUri(Context context, Uri uri) { if (ContentResolver.SCHEME_FILE.equals(uri.getScheme()) diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index 495ae6026805..34bea1a4df6f 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -849,6 +849,12 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { */ public static final int PRIVATE_FLAG_EXT_CPU_OVERRIDE = 1 << 5; + /** + * Whether the app has been previously not launched + * @hide + */ + public static final int PRIVATE_FLAG_EXT_NOT_LAUNCHED = 1 << 6; + /** @hide */ @IntDef(flag = true, prefix = { "PRIVATE_FLAG_EXT_" }, value = { PRIVATE_FLAG_EXT_PROFILEABLE, @@ -857,6 +863,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK, PRIVATE_FLAG_EXT_ALLOWLISTED_FOR_HIDDEN_APIS, PRIVATE_FLAG_EXT_CPU_OVERRIDE, + PRIVATE_FLAG_EXT_NOT_LAUNCHED, }) @Retention(RetentionPolicy.SOURCE) public @interface ApplicationInfoPrivateFlagsExt {} @@ -2664,6 +2671,22 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { } /** + * Returns whether the app in the STOPPED state. + * @hide + */ + public boolean isStopped() { + return (flags & ApplicationInfo.FLAG_STOPPED) != 0; + } + + /** + * Returns whether the app was never launched (any process started) before. + * @hide + */ + public boolean isNotLaunched() { + return (privateFlagsExt & ApplicationInfo.PRIVATE_FLAG_EXT_NOT_LAUNCHED) != 0; + } + + /** * Checks if a changeId is enabled for the current user * @param changeId The changeId to verify * @return True of the changeId is enabled diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig index 28534ad4516e..9eec7a4e8f71 100644 --- a/core/java/android/content/pm/multiuser.aconfig +++ b/core/java/android/content/pm/multiuser.aconfig @@ -426,4 +426,13 @@ flag { metadata { purpose: PURPOSE_BUGFIX } -}
\ No newline at end of file +} + + +flag { + name: "caching_development_improvements" + namespace: "multiuser" + description: "System API to simplify caching implamentations" + bug: "364947162" + is_fixed_read_only: true +} diff --git a/core/java/android/database/CursorWindow.java b/core/java/android/database/CursorWindow.java index 65148726224e..ef59e0af3a27 100644 --- a/core/java/android/database/CursorWindow.java +++ b/core/java/android/database/CursorWindow.java @@ -26,6 +26,10 @@ import android.database.sqlite.SQLiteClosable; import android.database.sqlite.SQLiteException; import android.os.Parcel; import android.os.Parcelable; +import android.ravenwood.annotation.RavenwoodKeepWholeClass; +import android.ravenwood.annotation.RavenwoodRedirect; +import android.ravenwood.annotation.RavenwoodRedirectionClass; +import android.ravenwood.annotation.RavenwoodThrow; import dalvik.annotation.optimization.FastNative; import dalvik.system.CloseGuard; @@ -40,9 +44,8 @@ import dalvik.system.CloseGuard; * consumer for reading. * </p> */ -@android.ravenwood.annotation.RavenwoodKeepWholeClass -@android.ravenwood.annotation.RavenwoodNativeSubstitutionClass( - "com.android.platform.test.ravenwood.nativesubstitution.CursorWindow_host") +@RavenwoodKeepWholeClass +@RavenwoodRedirectionClass("CursorWindow_host") public class CursorWindow extends SQLiteClosable implements Parcelable { private static final String STATS_TAG = "CursorWindowStats"; @@ -63,48 +66,69 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { private final CloseGuard mCloseGuard; // May throw CursorWindowAllocationException + @RavenwoodRedirect private static native long nativeCreate(String name, int cursorWindowSize); // May throw CursorWindowAllocationException + @RavenwoodRedirect private static native long nativeCreateFromParcel(Parcel parcel); + @RavenwoodRedirect private static native void nativeDispose(long windowPtr); + @RavenwoodRedirect private static native void nativeWriteToParcel(long windowPtr, Parcel parcel); + @RavenwoodRedirect private static native String nativeGetName(long windowPtr); + @RavenwoodRedirect private static native byte[] nativeGetBlob(long windowPtr, int row, int column); + @RavenwoodRedirect private static native String nativeGetString(long windowPtr, int row, int column); + @RavenwoodThrow private static native void nativeCopyStringToBuffer(long windowPtr, int row, int column, CharArrayBuffer buffer); + @RavenwoodRedirect private static native boolean nativePutBlob(long windowPtr, byte[] value, int row, int column); + @RavenwoodRedirect private static native boolean nativePutString(long windowPtr, String value, int row, int column); // Below native methods don't do unconstrained work, so are FastNative for performance @FastNative + @RavenwoodThrow private static native void nativeClear(long windowPtr); @FastNative + @RavenwoodRedirect private static native int nativeGetNumRows(long windowPtr); @FastNative + @RavenwoodRedirect private static native boolean nativeSetNumColumns(long windowPtr, int columnNum); @FastNative + @RavenwoodRedirect private static native boolean nativeAllocRow(long windowPtr); @FastNative + @RavenwoodThrow private static native void nativeFreeLastRow(long windowPtr); @FastNative + @RavenwoodRedirect private static native int nativeGetType(long windowPtr, int row, int column); @FastNative + @RavenwoodRedirect private static native long nativeGetLong(long windowPtr, int row, int column); @FastNative + @RavenwoodRedirect private static native double nativeGetDouble(long windowPtr, int row, int column); @FastNative + @RavenwoodRedirect private static native boolean nativePutLong(long windowPtr, long value, int row, int column); @FastNative + @RavenwoodRedirect private static native boolean nativePutDouble(long windowPtr, double value, int row, int column); @FastNative + @RavenwoodThrow private static native boolean nativePutNull(long windowPtr, int row, int column); diff --git a/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java b/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java index da2eec9cbb28..b2d926044869 100644 --- a/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java +++ b/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java @@ -19,9 +19,9 @@ package android.os; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.TestApi; -import android.os.Handler; -import android.os.Looper; -import android.os.Trace; +import android.ravenwood.annotation.RavenwoodKeepWholeClass; +import android.ravenwood.annotation.RavenwoodRedirect; +import android.ravenwood.annotation.RavenwoodRedirectionClass; import android.util.Log; import android.util.Printer; import android.util.SparseArray; @@ -51,9 +51,8 @@ import java.util.concurrent.locks.ReentrantLock; * <p>You can retrieve the MessageQueue for the current thread with * {@link Looper#myQueue() Looper.myQueue()}. */ -@android.ravenwood.annotation.RavenwoodKeepWholeClass -@android.ravenwood.annotation.RavenwoodNativeSubstitutionClass( - "com.android.platform.test.ravenwood.nativesubstitution.MessageQueue_host") +@RavenwoodKeepWholeClass +@RavenwoodRedirectionClass("MessageQueue_host") public final class MessageQueue { private static final String TAG = "ConcurrentMessageQueue"; private static final boolean DEBUG = false; @@ -345,11 +344,17 @@ public final class MessageQueue { // Barriers are indicated by messages with a null target whose arg1 field carries the token. private final AtomicInteger mNextBarrierToken = new AtomicInteger(1); + @RavenwoodRedirect private static native long nativeInit(); + @RavenwoodRedirect private static native void nativeDestroy(long ptr); + @RavenwoodRedirect private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/ + @RavenwoodRedirect private static native void nativeWake(long ptr); + @RavenwoodRedirect private static native boolean nativeIsPolling(long ptr); + @RavenwoodRedirect private static native void nativeSetFileDescriptorEvents(long ptr, int fd, int events); MessageQueue(boolean quitAllowed) { diff --git a/core/java/android/os/LegacyMessageQueue/MessageQueue.java b/core/java/android/os/LegacyMessageQueue/MessageQueue.java index 6b9b3496d1c0..4474e7e91fdc 100644 --- a/core/java/android/os/LegacyMessageQueue/MessageQueue.java +++ b/core/java/android/os/LegacyMessageQueue/MessageQueue.java @@ -20,9 +20,9 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; -import android.os.Handler; -import android.os.Process; -import android.os.Trace; +import android.ravenwood.annotation.RavenwoodKeepWholeClass; +import android.ravenwood.annotation.RavenwoodRedirect; +import android.ravenwood.annotation.RavenwoodRedirectionClass; import android.util.Log; import android.util.Printer; import android.util.SparseArray; @@ -42,9 +42,8 @@ import java.util.concurrent.atomic.AtomicLong; * <p>You can retrieve the MessageQueue for the current thread with * {@link Looper#myQueue() Looper.myQueue()}. */ -@android.ravenwood.annotation.RavenwoodKeepWholeClass -@android.ravenwood.annotation.RavenwoodNativeSubstitutionClass( - "com.android.platform.test.ravenwood.nativesubstitution.MessageQueue_host") +@RavenwoodKeepWholeClass +@RavenwoodRedirectionClass("MessageQueue_host") public final class MessageQueue { private static final String TAG = "MessageQueue"; private static final boolean DEBUG = false; @@ -79,12 +78,18 @@ public final class MessageQueue { @UnsupportedAppUsage private int mNextBarrierToken; + @RavenwoodRedirect private native static long nativeInit(); + @RavenwoodRedirect private native static void nativeDestroy(long ptr); @UnsupportedAppUsage + @RavenwoodRedirect private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/ + @RavenwoodRedirect private native static void nativeWake(long ptr); + @RavenwoodRedirect private native static boolean nativeIsPolling(long ptr); + @RavenwoodRedirect private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events); MessageQueue(boolean quitAllowed) { diff --git a/core/java/android/os/LockedMessageQueue/MessageQueue.java b/core/java/android/os/LockedMessageQueue/MessageQueue.java index b24e14b0419e..f1affce58a5c 100644 --- a/core/java/android/os/LockedMessageQueue/MessageQueue.java +++ b/core/java/android/os/LockedMessageQueue/MessageQueue.java @@ -20,8 +20,9 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; -import android.os.Handler; -import android.os.Trace; +import android.ravenwood.annotation.RavenwoodKeepWholeClass; +import android.ravenwood.annotation.RavenwoodRedirect; +import android.ravenwood.annotation.RavenwoodRedirectionClass; import android.util.Log; import android.util.Printer; import android.util.SparseArray; @@ -44,9 +45,8 @@ import java.util.concurrent.atomic.AtomicLong; * <p>You can retrieve the MessageQueue for the current thread with * {@link Looper#myQueue() Looper.myQueue()}. */ -@android.ravenwood.annotation.RavenwoodKeepWholeClass -@android.ravenwood.annotation.RavenwoodNativeSubstitutionClass( - "com.android.platform.test.ravenwood.nativesubstitution.MessageQueue_host") +@RavenwoodKeepWholeClass +@RavenwoodRedirectionClass("MessageQueue_host") public final class MessageQueue { private static final String TAG = "LockedMessageQueue"; private static final boolean DEBUG = false; @@ -389,12 +389,18 @@ public final class MessageQueue { @UnsupportedAppUsage private int mNextBarrierToken; + @RavenwoodRedirect private native static long nativeInit(); + @RavenwoodRedirect private native static void nativeDestroy(long ptr); @UnsupportedAppUsage + @RavenwoodRedirect private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/ + @RavenwoodRedirect private native static void nativeWake(long ptr); + @RavenwoodRedirect private native static boolean nativeIsPolling(long ptr); + @RavenwoodRedirect private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events); MessageQueue(boolean quitAllowed) { diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index 47096dbbac61..2ac2ae916e58 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -28,7 +28,8 @@ import android.annotation.TestApi; import android.app.AppOpsManager; import android.compat.annotation.UnsupportedAppUsage; import android.ravenwood.annotation.RavenwoodKeepWholeClass; -import android.ravenwood.annotation.RavenwoodNativeSubstitutionClass; +import android.ravenwood.annotation.RavenwoodRedirect; +import android.ravenwood.annotation.RavenwoodRedirectionClass; import android.ravenwood.annotation.RavenwoodReplace; import android.ravenwood.annotation.RavenwoodThrow; import android.text.TextUtils; @@ -233,8 +234,7 @@ import java.util.function.IntFunction; * {@link #readSparseArray(ClassLoader, Class)}. */ @RavenwoodKeepWholeClass -@RavenwoodNativeSubstitutionClass( - "com.android.platform.test.ravenwood.nativesubstitution.Parcel_host") +@RavenwoodRedirectionClass("Parcel_host") public final class Parcel { private static final boolean DEBUG_RECYCLE = false; @@ -387,6 +387,7 @@ public final class Parcel { private static final int SIZE_COMPLEX_TYPE = 1; @CriticalNative + @RavenwoodRedirect private static native void nativeMarkSensitive(long nativePtr); @FastNative @RavenwoodThrow @@ -395,86 +396,126 @@ public final class Parcel { @RavenwoodThrow private static native boolean nativeIsForRpc(long nativePtr); @CriticalNative + @RavenwoodRedirect private static native int nativeDataSize(long nativePtr); @CriticalNative + @RavenwoodRedirect private static native int nativeDataAvail(long nativePtr); @CriticalNative + @RavenwoodRedirect private static native int nativeDataPosition(long nativePtr); @CriticalNative + @RavenwoodRedirect private static native int nativeDataCapacity(long nativePtr); @FastNative + @RavenwoodRedirect private static native void nativeSetDataSize(long nativePtr, int size); @CriticalNative + @RavenwoodRedirect private static native void nativeSetDataPosition(long nativePtr, int pos); @FastNative + @RavenwoodRedirect private static native void nativeSetDataCapacity(long nativePtr, int size); @CriticalNative + @RavenwoodRedirect private static native boolean nativePushAllowFds(long nativePtr, boolean allowFds); @CriticalNative + @RavenwoodRedirect private static native void nativeRestoreAllowFds(long nativePtr, boolean lastValue); + @RavenwoodRedirect private static native void nativeWriteByteArray(long nativePtr, byte[] b, int offset, int len); + @RavenwoodRedirect private static native void nativeWriteBlob(long nativePtr, byte[] b, int offset, int len); @CriticalNative + @RavenwoodRedirect private static native int nativeWriteInt(long nativePtr, int val); @CriticalNative + @RavenwoodRedirect private static native int nativeWriteLong(long nativePtr, long val); @CriticalNative + @RavenwoodRedirect private static native int nativeWriteFloat(long nativePtr, float val); @CriticalNative + @RavenwoodRedirect private static native int nativeWriteDouble(long nativePtr, double val); @RavenwoodThrow private static native void nativeSignalExceptionForError(int error); @FastNative + @RavenwoodRedirect private static native void nativeWriteString8(long nativePtr, String val); @FastNative + @RavenwoodRedirect private static native void nativeWriteString16(long nativePtr, String val); @FastNative @RavenwoodThrow private static native void nativeWriteStrongBinder(long nativePtr, IBinder val); @FastNative + @RavenwoodRedirect private static native void nativeWriteFileDescriptor(long nativePtr, FileDescriptor val); + @RavenwoodRedirect private static native byte[] nativeCreateByteArray(long nativePtr); + @RavenwoodRedirect private static native boolean nativeReadByteArray(long nativePtr, byte[] dest, int destLen); + @RavenwoodRedirect private static native byte[] nativeReadBlob(long nativePtr); @CriticalNative + @RavenwoodRedirect private static native int nativeReadInt(long nativePtr); @CriticalNative + @RavenwoodRedirect private static native long nativeReadLong(long nativePtr); @CriticalNative + @RavenwoodRedirect private static native float nativeReadFloat(long nativePtr); @CriticalNative + @RavenwoodRedirect private static native double nativeReadDouble(long nativePtr); @FastNative + @RavenwoodRedirect private static native String nativeReadString8(long nativePtr); @FastNative + @RavenwoodRedirect private static native String nativeReadString16(long nativePtr); @FastNative @RavenwoodThrow private static native IBinder nativeReadStrongBinder(long nativePtr); @FastNative + @RavenwoodRedirect private static native FileDescriptor nativeReadFileDescriptor(long nativePtr); + @RavenwoodRedirect private static native long nativeCreate(); + @RavenwoodRedirect private static native void nativeFreeBuffer(long nativePtr); + @RavenwoodRedirect private static native void nativeDestroy(long nativePtr); + @RavenwoodRedirect private static native byte[] nativeMarshall(long nativePtr); + @RavenwoodRedirect private static native void nativeUnmarshall( long nativePtr, byte[] data, int offset, int length); + @RavenwoodRedirect private static native int nativeCompareData(long thisNativePtr, long otherNativePtr); + @RavenwoodRedirect private static native boolean nativeCompareDataInRange( long ptrA, int offsetA, long ptrB, int offsetB, int length); + @RavenwoodRedirect private static native void nativeAppendFrom( long thisNativePtr, long otherNativePtr, int offset, int length); @CriticalNative + @RavenwoodRedirect private static native boolean nativeHasFileDescriptors(long nativePtr); + @RavenwoodRedirect private static native boolean nativeHasFileDescriptorsInRange( long nativePtr, int offset, int length); + @RavenwoodRedirect private static native boolean nativeHasBinders(long nativePtr); + @RavenwoodRedirect private static native boolean nativeHasBindersInRange( long nativePtr, int offset, int length); @RavenwoodThrow diff --git a/core/java/android/os/SemiConcurrentMessageQueue/MessageQueue.java b/core/java/android/os/SemiConcurrentMessageQueue/MessageQueue.java index 79f229acbccb..80c24a9003e8 100644 --- a/core/java/android/os/SemiConcurrentMessageQueue/MessageQueue.java +++ b/core/java/android/os/SemiConcurrentMessageQueue/MessageQueue.java @@ -19,8 +19,9 @@ package android.os; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.TestApi; -import android.os.Handler; -import android.os.Trace; +import android.ravenwood.annotation.RavenwoodKeepWholeClass; +import android.ravenwood.annotation.RavenwoodRedirect; +import android.ravenwood.annotation.RavenwoodRedirectionClass; import android.util.Log; import android.util.Printer; import android.util.SparseArray; @@ -37,8 +38,6 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.PriorityQueue; -import java.util.PriorityQueue; -import java.util.PriorityQueue; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; @@ -50,9 +49,8 @@ import java.util.concurrent.atomic.AtomicLong; * <p>You can retrieve the MessageQueue for the current thread with * {@link Looper#myQueue() Looper.myQueue()}. */ -@android.ravenwood.annotation.RavenwoodKeepWholeClass -@android.ravenwood.annotation.RavenwoodNativeSubstitutionClass( - "com.android.platform.test.ravenwood.nativesubstitution.MessageQueue_host") +@RavenwoodKeepWholeClass +@RavenwoodRedirectionClass("MessageQueue_host") public final class MessageQueue { private static final String TAG = "SemiConcurrentMessageQueue"; private static final boolean DEBUG = false; @@ -338,11 +336,17 @@ public final class MessageQueue { // Barriers are indicated by messages with a null target whose arg1 field carries the token. private final AtomicInteger mNextBarrierToken = new AtomicInteger(1); + @RavenwoodRedirect private static native long nativeInit(); + @RavenwoodRedirect private static native void nativeDestroy(long ptr); + @RavenwoodRedirect private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/ + @RavenwoodRedirect private static native void nativeWake(long ptr); + @RavenwoodRedirect private static native boolean nativeIsPolling(long ptr); + @RavenwoodRedirect private static native void nativeSetFileDescriptorEvents(long ptr, int fd, int events); MessageQueue(boolean quitAllowed) { diff --git a/core/java/android/os/SystemProperties.java b/core/java/android/os/SystemProperties.java index 0a386913de59..e53873b5622e 100644 --- a/core/java/android/os/SystemProperties.java +++ b/core/java/android/os/SystemProperties.java @@ -21,11 +21,13 @@ import android.annotation.Nullable; import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; import android.ravenwood.annotation.RavenwoodKeepWholeClass; -import android.ravenwood.annotation.RavenwoodNativeSubstitutionClass; +import android.ravenwood.annotation.RavenwoodRedirect; +import android.ravenwood.annotation.RavenwoodRedirectionClass; import android.util.Log; import android.util.MutableInt; import com.android.internal.annotations.GuardedBy; +import com.android.internal.ravenwood.RavenwoodEnvironment; import dalvik.annotation.optimization.CriticalNative; import dalvik.annotation.optimization.FastNative; @@ -56,8 +58,7 @@ import java.util.function.Predicate; */ @SystemApi @RavenwoodKeepWholeClass -@RavenwoodNativeSubstitutionClass( - "com.android.platform.test.ravenwood.nativesubstitution.SystemProperties_host") +@RavenwoodRedirectionClass("SystemProperties_host") public class SystemProperties { private static final String TAG = "SystemProperties"; private static final boolean TRACK_KEY_ACCESS = false; @@ -75,7 +76,7 @@ public class SystemProperties { @UnsupportedAppUsage @GuardedBy("sChangeCallbacks") - private static final ArrayList<Runnable> sChangeCallbacks = new ArrayList<Runnable>(); + static final ArrayList<Runnable> sChangeCallbacks = new ArrayList<Runnable>(); @GuardedBy("sRoReads") private static final HashMap<String, MutableInt> sRoReads = @@ -102,30 +103,18 @@ public class SystemProperties { } /** @hide */ + @RavenwoodRedirect public static void init$ravenwood(Map<String, String> values, Predicate<String> keyReadablePredicate, Predicate<String> keyWritablePredicate) { - native_init$ravenwood(values, keyReadablePredicate, keyWritablePredicate, - SystemProperties::callChangeCallbacks); - synchronized (sChangeCallbacks) { - sChangeCallbacks.clear(); - } + throw RavenwoodEnvironment.notSupportedOnDevice(); } /** @hide */ + @RavenwoodRedirect public static void reset$ravenwood() { - native_reset$ravenwood(); - synchronized (sChangeCallbacks) { - sChangeCallbacks.clear(); - } + throw RavenwoodEnvironment.notSupportedOnDevice(); } - // These native methods are currently only implemented by Ravenwood, as it's the only - // mechanism we have to jump to our RavenwoodNativeSubstitutionClass - private static native void native_init$ravenwood(Map<String, String> values, - Predicate<String> keyReadablePredicate, Predicate<String> keyWritablePredicate, - Runnable changeCallback); - private static native void native_reset$ravenwood(); - // The one-argument version of native_get used to be a regular native function. Nowadays, // we use the two-argument form of native_get all the time, but we can't just delete the // one-argument overload: apps use it via reflection, as the UnsupportedAppUsage annotation @@ -137,34 +126,46 @@ public class SystemProperties { @FastNative @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + @RavenwoodRedirect private static native String native_get(String key, String def); @FastNative @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + @RavenwoodRedirect private static native int native_get_int(String key, int def); @FastNative @UnsupportedAppUsage + @RavenwoodRedirect private static native long native_get_long(String key, long def); @FastNative @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + @RavenwoodRedirect private static native boolean native_get_boolean(String key, boolean def); @FastNative + @RavenwoodRedirect private static native long native_find(String name); @FastNative + @RavenwoodRedirect private static native String native_get(long handle); @CriticalNative + @RavenwoodRedirect private static native int native_get_int(long handle, int def); @CriticalNative + @RavenwoodRedirect private static native long native_get_long(long handle, long def); @CriticalNative + @RavenwoodRedirect private static native boolean native_get_boolean(long handle, boolean def); // _NOT_ FastNative: native_set performs IPC and can block @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + @RavenwoodRedirect private static native void native_set(String key, String def); @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + @RavenwoodRedirect private static native void native_add_change_callback(); + @RavenwoodRedirect private static native void native_report_sysprop_change(); /** @@ -300,7 +301,7 @@ public class SystemProperties { } @SuppressWarnings("unused") // Called from native code. - private static void callChangeCallbacks() { + static void callChangeCallbacks() { ArrayList<Runnable> callbacks = null; synchronized (sChangeCallbacks) { //Log.i("foo", "Calling " + sChangeCallbacks.size() + " change callbacks!"); diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 98904fe246f8..0a05f704f523 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -5150,13 +5150,19 @@ public final class Settings { public static final String SCREEN_OFF_TIMEOUT = "screen_off_timeout"; /** - * The screen backlight brightness between 0 and 255. + * The screen backlight brightness between 1 (minimum) and 255 (maximum). + * + * Use {@link android.view.WindowManager.LayoutParams#screenBrightness} to set the screen + * brightness instead. */ @Readable public static final String SCREEN_BRIGHTNESS = "screen_brightness"; /** - * Control whether to enable automatic brightness mode. + * Controls whether to enable automatic brightness mode. Value can be set to + * {@link #SCREEN_BRIGHTNESS_MODE_MANUAL} or {@link #SCREEN_BRIGHTNESS_MODE_AUTOMATIC}. + * If {@link #SCREEN_BRIGHTNESS_MODE_AUTOMATIC} is set, the system may change + * {@link #SCREEN_BRIGHTNESS} automatically. */ @Readable public static final String SCREEN_BRIGHTNESS_MODE = "screen_brightness_mode"; diff --git a/core/java/android/text/ClientFlags.java b/core/java/android/text/ClientFlags.java index 5d84d17bdb6e..bbe9cdbbce9f 100644 --- a/core/java/android/text/ClientFlags.java +++ b/core/java/android/text/ClientFlags.java @@ -35,20 +35,6 @@ public class ClientFlags { } /** - * @see Flags#phraseStrictFallback() - */ - public static boolean phraseStrictFallback() { - return TextFlags.isFeatureEnabled(Flags.FLAG_PHRASE_STRICT_FALLBACK); - } - - /** - * @see Flags#useBoundsForWidth() - */ - public static boolean useBoundsForWidth() { - return TextFlags.isFeatureEnabled(Flags.FLAG_USE_BOUNDS_FOR_WIDTH); - } - - /** * @see Flags#fixLineHeightForLocale() */ public static boolean fixLineHeightForLocale() { diff --git a/core/java/android/text/TextFlags.java b/core/java/android/text/TextFlags.java index 9e02460d2637..0f1b031a8fad 100644 --- a/core/java/android/text/TextFlags.java +++ b/core/java/android/text/TextFlags.java @@ -56,8 +56,6 @@ public final class TextFlags { */ public static final String[] TEXT_ACONFIGS_FLAGS = { Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN, - Flags.FLAG_PHRASE_STRICT_FALLBACK, - Flags.FLAG_USE_BOUNDS_FOR_WIDTH, Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE, Flags.FLAG_ICU_BIDI_MIGRATION, Flags.FLAG_FIX_MISALIGNED_CONTEXT_MENU, @@ -70,8 +68,6 @@ public final class TextFlags { */ public static final boolean[] TEXT_ACONFIG_DEFAULT_VALUE = { Flags.noBreakNoHyphenationSpan(), - Flags.phraseStrictFallback(), - Flags.useBoundsForWidth(), Flags.fixLineHeightForLocale(), Flags.icuBidiMigration(), Flags.fixMisalignedContextMenu(), diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig index d33c95e06677..b2be1a7ef351 100644 --- a/core/java/android/text/flags/flags.aconfig +++ b/core/java/android/text/flags/flags.aconfig @@ -2,14 +2,6 @@ package: "com.android.text.flags" container: "system" flag { - name: "vendor_custom_locale_fallback" - namespace: "text" - description: "A feature flag that adds custom locale fallback to the vendor customization XML. This enables vendors to add their locale specific fonts, e.g. Japanese font." - is_fixed_read_only: true - bug: "278768958" -} - -flag { name: "new_fonts_fallback_xml" is_exported: true namespace: "text" @@ -20,13 +12,6 @@ flag { } flag { - name: "fix_double_underline" - namespace: "text" - description: "Feature flag for fixing double underline because of the multiple font used in the single line." - bug: "297336724" -} - -flag { name: "fix_line_height_for_locale" is_exported: true namespace: "text" @@ -66,13 +51,6 @@ flag { } flag { - name: "phrase_strict_fallback" - namespace: "text" - description: "Feature flag for automatic fallback from phrase based line break to strict line break." - bug: "281970875" -} - -flag { name: "use_bounds_for_width" is_exported: true namespace: "text" diff --git a/core/java/android/util/EventLog.java b/core/java/android/util/EventLog.java index 0a73fd1689c3..00545da2baf2 100644 --- a/core/java/android/util/EventLog.java +++ b/core/java/android/util/EventLog.java @@ -21,6 +21,10 @@ import android.annotation.Nullable; import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; +import android.ravenwood.annotation.RavenwoodKeepWholeClass; +import android.ravenwood.annotation.RavenwoodRedirect; +import android.ravenwood.annotation.RavenwoodRedirectionClass; +import android.ravenwood.annotation.RavenwoodThrow; import java.io.BufferedReader; import java.io.FileReader; @@ -48,9 +52,8 @@ import java.util.regex.Pattern; * They carry a payload of one or more int, long, or String values. The * event-log-tags file defines the payload contents for each type code. */ -@android.ravenwood.annotation.RavenwoodKeepWholeClass -@android.ravenwood.annotation.RavenwoodNativeSubstitutionClass( - "com.android.platform.test.ravenwood.nativesubstitution.EventLog_host") +@RavenwoodKeepWholeClass +@RavenwoodRedirectionClass("EventLog_host") public class EventLog { /** @hide */ public EventLog() {} @@ -339,6 +342,7 @@ public class EventLog { * @param value A value to log * @return The number of bytes written */ + @RavenwoodRedirect public static native int writeEvent(int tag, int value); /** @@ -347,6 +351,7 @@ public class EventLog { * @param value A value to log * @return The number of bytes written */ + @RavenwoodRedirect public static native int writeEvent(int tag, long value); /** @@ -355,6 +360,7 @@ public class EventLog { * @param value A value to log * @return The number of bytes written */ + @RavenwoodRedirect public static native int writeEvent(int tag, float value); /** @@ -363,6 +369,7 @@ public class EventLog { * @param str A value to log * @return The number of bytes written */ + @RavenwoodRedirect public static native int writeEvent(int tag, String str); /** @@ -371,6 +378,7 @@ public class EventLog { * @param list A list of values to log * @return The number of bytes written */ + @RavenwoodRedirect public static native int writeEvent(int tag, Object... list); /** @@ -379,6 +387,7 @@ public class EventLog { * @param output container to add events into * @throws IOException if something goes wrong reading events */ + @RavenwoodThrow public static native void readEvents(int[] tags, Collection<Event> output) throws IOException; @@ -391,6 +400,7 @@ public class EventLog { * @hide */ @SystemApi + @RavenwoodThrow public static native void readEventsOnWrapping(int[] tags, long timestamp, Collection<Event> output) throws IOException; diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index f021bdfe478f..e10cc28d0745 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -4345,6 +4345,7 @@ public final class ViewRootImpl implements ViewParent, handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mHasPendingTransactions, mPendingTransaction, "view not visible"); + mHasPendingTransactions = false; } else if (cancelAndRedraw) { if (!mWasLastDrawCanceled) { logAndTrace("Canceling draw." @@ -4372,6 +4373,7 @@ public final class ViewRootImpl implements ViewParent, if (!performDraw(mActiveSurfaceSyncGroup)) { handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mHasPendingTransactions, mPendingTransaction, mLastPerformDrawSkippedReason); + mHasPendingTransactions = false; } } mWasLastDrawCanceled = cancelAndRedraw; @@ -4388,7 +4390,14 @@ public final class ViewRootImpl implements ViewParent, mReportNextDraw = false; mLastReportNextDrawReason = null; mActiveSurfaceSyncGroup = null; - mHasPendingTransactions = false; + if (mHasPendingTransactions) { + // TODO: We shouldn't ever actually hit this, it means mPendingTransaction wasn't + // merged with a sync group or BLASTBufferQueue before making it to this point + // But better a one or two frame flicker than steady-state broken from dropping + // whatever is in this transaction + mPendingTransaction.apply(); + mHasPendingTransactions = false; + } mSyncBuffer = false; if (isInWMSRequestedSync()) { mWmsRequestSyncGroup.markSyncReady(); @@ -5305,6 +5314,7 @@ public final class ViewRootImpl implements ViewParent, private void registerCallbackForPendingTransactions() { Transaction t = new Transaction(); t.merge(mPendingTransaction); + mHasPendingTransactions = false; registerRtFrameCallback(new FrameDrawingCallback() { @Override @@ -5384,6 +5394,7 @@ public final class ViewRootImpl implements ViewParent, if (!usingAsyncReport && mHasPendingTransactions) { pendingTransaction = new Transaction(); pendingTransaction.merge(mPendingTransaction); + mHasPendingTransactions = false; } else { pendingTransaction = null; } @@ -9942,6 +9953,7 @@ public final class ViewRootImpl implements ViewParent, } handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mHasPendingTransactions, mPendingTransaction, "shutting down VRI"); + mHasPendingTransactions = false; WindowManagerGlobal.getInstance().doRemoveView(this); } @@ -12601,6 +12613,7 @@ public final class ViewRootImpl implements ViewParent, if (mHasPendingTransactions) { t = new Transaction(); t.merge(mPendingTransaction); + mHasPendingTransactions = false; } else { t = null; } diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index eb3581717637..a395c1a05744 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -1447,6 +1447,11 @@ public class RemoteViews implements Parcelable, Filter { } @Override + public void onNullBinding(ComponentName name) { + context.unbindService(this); + } + + @Override public void onServiceDisconnected(ComponentName componentName) { } }); diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java index 2f28a8704cd3..118edc29f378 100644 --- a/core/java/android/widget/RemoteViewsAdapter.java +++ b/core/java/android/widget/RemoteViewsAdapter.java @@ -241,6 +241,11 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback } @Override + public void onNullBinding(ComponentName name) { + enqueueDeferredUnbindServiceMessage(); + } + + @Override public void handleMessage(Message msg) { RemoteViewsAdapter adapter = mAdapter.get(); diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index a4b28adae4a1..ef941da0e32d 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -1659,11 +1659,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } if (!hasUseBoundForWidthValue) { - if (CompatChanges.isChangeEnabled(USE_BOUNDS_FOR_WIDTH)) { - mUseBoundsForWidth = Flags.useBoundsForWidth(); - } else { - mUseBoundsForWidth = false; - } + mUseBoundsForWidth = CompatChanges.isChangeEnabled(USE_BOUNDS_FOR_WIDTH); } // TODO(b/179693024): Use a ChangeId instead. diff --git a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java index 07fa679a428a..dfb2884044f5 100644 --- a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java +++ b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java @@ -18,6 +18,10 @@ package com.android.internal.os; import android.os.Parcel; import android.os.Parcelable; +import android.ravenwood.annotation.RavenwoodKeepWholeClass; +import android.ravenwood.annotation.RavenwoodRedirect; +import android.ravenwood.annotation.RavenwoodRedirectionClass; +import android.ravenwood.annotation.RavenwoodReplace; import com.android.internal.util.Preconditions; @@ -55,18 +59,15 @@ import java.util.concurrent.atomic.AtomicReference; * * @hide */ -@android.ravenwood.annotation.RavenwoodKeepWholeClass -@android.ravenwood.annotation.RavenwoodNativeSubstitutionClass( - "com.android.platform.test.ravenwood.nativesubstitution.LongArrayMultiStateCounter_host") +@RavenwoodKeepWholeClass +@RavenwoodRedirectionClass("LongArrayMultiStateCounter_host") public final class LongArrayMultiStateCounter implements Parcelable { /** * Container for a native equivalent of a long[]. */ - @android.ravenwood.annotation.RavenwoodKeepWholeClass - @android.ravenwood.annotation.RavenwoodNativeSubstitutionClass( - "com.android.platform.test.ravenwood.nativesubstitution" - + ".LongArrayMultiStateCounter_host$LongArrayContainer_host") + @RavenwoodKeepWholeClass + @RavenwoodRedirectionClass("LongArrayContainer_host") public static class LongArrayContainer { private static NativeAllocationRegistry sRegistry; @@ -81,7 +82,7 @@ public final class LongArrayMultiStateCounter implements Parcelable { registerNativeAllocation(); } - @android.ravenwood.annotation.RavenwoodReplace + @RavenwoodReplace private void registerNativeAllocation() { if (sRegistry == null) { synchronized (LongArrayMultiStateCounter.class) { @@ -140,18 +141,23 @@ public final class LongArrayMultiStateCounter implements Parcelable { } @CriticalNative + @RavenwoodRedirect private static native long native_init(int length); @CriticalNative + @RavenwoodRedirect private static native long native_getReleaseFunc(); @FastNative + @RavenwoodRedirect private static native void native_setValues(long nativeObject, long[] array); @FastNative + @RavenwoodRedirect private static native void native_getValues(long nativeObject, long[] array); @FastNative + @RavenwoodRedirect private static native boolean native_combineValues(long nativeObject, long[] array, int[] indexMap); } @@ -175,7 +181,7 @@ public final class LongArrayMultiStateCounter implements Parcelable { registerNativeAllocation(); } - @android.ravenwood.annotation.RavenwoodReplace + @RavenwoodReplace private void registerNativeAllocation() { if (sRegistry == null) { synchronized (LongArrayMultiStateCounter.class) { @@ -374,57 +380,73 @@ public final class LongArrayMultiStateCounter implements Parcelable { @CriticalNative + @RavenwoodRedirect private static native long native_init(int stateCount, int arrayLength); @CriticalNative + @RavenwoodRedirect private static native long native_getReleaseFunc(); @CriticalNative + @RavenwoodRedirect private static native void native_setEnabled(long nativeObject, boolean enabled, long timestampMs); @CriticalNative + @RavenwoodRedirect private static native void native_setState(long nativeObject, int state, long timestampMs); @CriticalNative + @RavenwoodRedirect private static native void native_copyStatesFrom(long nativeObjectTarget, long nativeObjectSource); @CriticalNative + @RavenwoodRedirect private static native void native_setValues(long nativeObject, int state, long longArrayContainerNativeObject); @CriticalNative + @RavenwoodRedirect private static native void native_updateValues(long nativeObject, long longArrayContainerNativeObject, long timestampMs); @CriticalNative + @RavenwoodRedirect private static native void native_incrementValues(long nativeObject, long longArrayContainerNativeObject, long timestampMs); @CriticalNative + @RavenwoodRedirect private static native void native_addCounts(long nativeObject, long longArrayContainerNativeObject); @CriticalNative + @RavenwoodRedirect private static native void native_reset(long nativeObject); @CriticalNative + @RavenwoodRedirect private static native void native_getCounts(long nativeObject, long longArrayContainerNativeObject, int state); @FastNative + @RavenwoodRedirect private static native String native_toString(long nativeObject); @FastNative + @RavenwoodRedirect private static native void native_writeToParcel(long nativeObject, Parcel dest, int flags); @FastNative + @RavenwoodRedirect private static native long native_initFromParcel(Parcel parcel); @CriticalNative + @RavenwoodRedirect private static native int native_getStateCount(long nativeObject); @CriticalNative + @RavenwoodRedirect private static native int native_getArrayLength(long nativeObject); } diff --git a/core/java/com/android/internal/os/LongMultiStateCounter.java b/core/java/com/android/internal/os/LongMultiStateCounter.java index e5662c7d5145..c386a86f5906 100644 --- a/core/java/com/android/internal/os/LongMultiStateCounter.java +++ b/core/java/com/android/internal/os/LongMultiStateCounter.java @@ -18,6 +18,10 @@ package com.android.internal.os; import android.os.Parcel; import android.os.Parcelable; +import android.ravenwood.annotation.RavenwoodKeepWholeClass; +import android.ravenwood.annotation.RavenwoodRedirect; +import android.ravenwood.annotation.RavenwoodRedirectionClass; +import android.ravenwood.annotation.RavenwoodReplace; import com.android.internal.util.Preconditions; @@ -55,9 +59,8 @@ import libcore.util.NativeAllocationRegistry; * * @hide */ -@android.ravenwood.annotation.RavenwoodKeepWholeClass -@android.ravenwood.annotation.RavenwoodNativeSubstitutionClass( - "com.android.platform.test.ravenwood.nativesubstitution.LongMultiStateCounter_host") +@RavenwoodKeepWholeClass +@RavenwoodRedirectionClass("LongMultiStateCounter_host") public final class LongMultiStateCounter implements Parcelable { private static NativeAllocationRegistry sRegistry; @@ -82,7 +85,7 @@ public final class LongMultiStateCounter implements Parcelable { mStateCount = native_getStateCount(mNativeObject); } - @android.ravenwood.annotation.RavenwoodReplace + @RavenwoodReplace private void registerNativeAllocation() { if (sRegistry == null) { synchronized (LongMultiStateCounter.class) { @@ -210,43 +213,56 @@ public final class LongMultiStateCounter implements Parcelable { @CriticalNative + @RavenwoodRedirect private static native long native_init(int stateCount); @CriticalNative + @RavenwoodRedirect private static native long native_getReleaseFunc(); @CriticalNative + @RavenwoodRedirect private static native void native_setEnabled(long nativeObject, boolean enabled, long timestampMs); @CriticalNative + @RavenwoodRedirect private static native void native_setState(long nativeObject, int state, long timestampMs); @CriticalNative + @RavenwoodRedirect private static native long native_updateValue(long nativeObject, long value, long timestampMs); @CriticalNative + @RavenwoodRedirect private static native void native_incrementValue(long nativeObject, long increment, long timestampMs); @CriticalNative + @RavenwoodRedirect private static native void native_addCount(long nativeObject, long count); @CriticalNative + @RavenwoodRedirect private static native void native_reset(long nativeObject); @CriticalNative + @RavenwoodRedirect private static native long native_getCount(long nativeObject, int state); @FastNative + @RavenwoodRedirect private static native String native_toString(long nativeObject); @FastNative + @RavenwoodRedirect private static native void native_writeToParcel(long nativeObject, Parcel dest, int flags); @FastNative + @RavenwoodRedirect private static native long native_initFromParcel(Parcel parcel); @CriticalNative + @RavenwoodRedirect private static native int native_getStateCount(long nativeObject); } diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java index cdac09796137..1709ca78af4b 100644 --- a/core/java/com/android/internal/os/RuntimeInit.java +++ b/core/java/com/android/internal/os/RuntimeInit.java @@ -404,6 +404,17 @@ public class RuntimeInit { } public static void redirectLogStreams$ravenwood() { + if (sOut$ravenwood != null && sErr$ravenwood != null) { + return; // Already initialized. + } + + // Make sure the Log class is loaded and the JNI methods are hooked up, + // before redirecting System.out/err. + // Otherwise, because ClassLoadHook tries to write to System.out, this would cause + // a circular initialization problem and would cause a UnsatisfiedLinkError + // on the JNI methods. + Log.isLoggable("X", Log.VERBOSE); + if (sOut$ravenwood == null) { sOut$ravenwood = System.out; System.setOut(new AndroidPrintStream(Log.INFO, "System.out")); diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java index 58818f35de22..4708be8108c2 100644 --- a/core/java/com/android/internal/policy/DecorView.java +++ b/core/java/com/android/internal/policy/DecorView.java @@ -1144,7 +1144,8 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind mDrawLegacyNavigationBarBackground = ((requestedVisibleTypes | mLastForceConsumingTypes) & WindowInsets.Type.navigationBars()) != 0 - && (mWindow.getAttributes().flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0; + && (mWindow.getAttributes().flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0 + && navBarSize > 0; if (oldDrawLegacy != mDrawLegacyNavigationBarBackground) { mDrawLegacyNavigationBarBackgroundHandled = mWindow.onDrawLegacyNavigationBarBackgroundChanged( diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java index 1fd933f29789..e440dc9053fd 100644 --- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java +++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java @@ -34,7 +34,6 @@ import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Gro import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MESSAGES; import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.GROUP_ID; import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.LEVEL; -import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.LOCATION; import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.MESSAGE; import static android.internal.perfetto.protos.TracePacketOuterClass.TracePacket.INTERNED_DATA; import static android.internal.perfetto.protos.TracePacketOuterClass.TracePacket.PROTOLOG_MESSAGE; @@ -72,7 +71,6 @@ import com.android.internal.protolog.common.LogLevel; import java.io.FileInputStream; import java.io.FileNotFoundException; -import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.util.ArrayList; @@ -103,6 +101,7 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto private final ProtoLogDataSource mDataSource; @Nullable private final ProtoLogViewerConfigReader mViewerConfigReader; + @Deprecated @Nullable private final ViewerConfigInputStreamProvider mViewerConfigInputStreamProvider; @NonNull @@ -148,6 +147,7 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto cacheUpdater, groups); } + @Deprecated @VisibleForTesting public PerfettoProtoLogImpl( @Nullable ViewerConfigInputStreamProvider viewerConfigInputStreamProvider, @@ -160,6 +160,18 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto groups, dataSourceBuilder, configurationService); } + @VisibleForTesting + public PerfettoProtoLogImpl( + @Nullable String viewerConfigFilePath, + @Nullable ProtoLogViewerConfigReader viewerConfigReader, + @NonNull Runnable cacheUpdater, + @NonNull IProtoLogGroup[] groups, + @NonNull ProtoLogDataSourceBuilder dataSourceBuilder, + @NonNull ProtoLogConfigurationService configurationService) { + this(viewerConfigFilePath, null, viewerConfigReader, cacheUpdater, + groups, dataSourceBuilder, configurationService); + } + private PerfettoProtoLogImpl( @Nullable String viewerConfigFilePath, @Nullable ViewerConfigInputStreamProvider viewerConfigInputStreamProvider, @@ -449,6 +461,7 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto Log.d(LOG_TAG, "Finished onTracingFlush"); } + @Deprecated private void dumpViewerConfig() { if (mViewerConfigInputStreamProvider == null) { // No viewer config available @@ -457,103 +470,29 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto Log.d(LOG_TAG, "Dumping viewer config to trace"); - mDataSource.trace(ctx -> { - try { - ProtoInputStream pis = mViewerConfigInputStreamProvider.getInputStream(); - - final ProtoOutputStream os = ctx.newTracePacket(); - - os.write(TIMESTAMP, SystemClock.elapsedRealtimeNanos()); - - final long outProtologViewerConfigToken = os.start(PROTOLOG_VIEWER_CONFIG); - while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) { - if (pis.getFieldNumber() == (int) MESSAGES) { - writeViewerConfigMessage(pis, os); - } - - if (pis.getFieldNumber() == (int) GROUPS) { - writeViewerConfigGroup(pis, os); - } - } - - os.end(outProtologViewerConfigToken); - } catch (IOException e) { - Log.e(LOG_TAG, "Failed to read ProtoLog viewer config to dump on tracing end", e); - } - }); + Utils.dumpViewerConfig(mDataSource, mViewerConfigInputStreamProvider); Log.d(LOG_TAG, "Dumped viewer config to trace"); } - private static void writeViewerConfigGroup( - ProtoInputStream pis, ProtoOutputStream os) throws IOException { - final long inGroupToken = pis.start(GROUPS); - final long outGroupToken = os.start(GROUPS); - - while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) { - switch (pis.getFieldNumber()) { - case (int) ID: - int id = pis.readInt(ID); - os.write(ID, id); - break; - case (int) NAME: - String name = pis.readString(NAME); - os.write(NAME, name); - break; - case (int) TAG: - String tag = pis.readString(TAG); - os.write(TAG, tag); - break; - default: - throw new RuntimeException( - "Unexpected field id " + pis.getFieldNumber()); - } - } - - pis.end(inGroupToken); - os.end(outGroupToken); - } - - private static void writeViewerConfigMessage( - ProtoInputStream pis, ProtoOutputStream os) throws IOException { - final long inMessageToken = pis.start(MESSAGES); - final long outMessagesToken = os.start(MESSAGES); - - while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) { - switch (pis.getFieldNumber()) { - case (int) MessageData.MESSAGE_ID: - os.write(MessageData.MESSAGE_ID, - pis.readLong(MessageData.MESSAGE_ID)); - break; - case (int) MESSAGE: - os.write(MESSAGE, pis.readString(MESSAGE)); - break; - case (int) LEVEL: - os.write(LEVEL, pis.readInt(LEVEL)); - break; - case (int) GROUP_ID: - os.write(GROUP_ID, pis.readInt(GROUP_ID)); - break; - case (int) LOCATION: - os.write(LOCATION, pis.readString(LOCATION)); - break; - default: - throw new RuntimeException( - "Unexpected field id " + pis.getFieldNumber()); - } - } - - pis.end(inMessageToken); - os.end(outMessagesToken); - } - private void logToLogcat(String tag, LogLevel level, Message message, @Nullable Object[] args) { String messageString; if (mViewerConfigReader == null) { messageString = message.getMessage(); + + if (messageString == null) { + Log.e(LOG_TAG, "Failed to decode message for logcat. " + + "Message not available without ViewerConfig to decode the hash."); + } } else { messageString = message.getMessage(mViewerConfigReader); + + if (messageString == null) { + Log.e(LOG_TAG, "Failed to decode message for logcat. " + + "Message hash either not available in viewerConfig file or " + + "not loaded into memory from file before decoding."); + } } if (messageString == null) { @@ -760,7 +699,7 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto os.write(MessageData.MESSAGE_ID, messageHash); os.write(MESSAGE, message); - os.write(LEVEL, level.ordinal()); + os.write(LEVEL, level.id); os.write(GROUP_ID, logGroup.getId()); os.end(messageConfigToken); @@ -954,8 +893,7 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto private static class Message { @Nullable private final Long mMessageHash; - @Nullable - private final Integer mMessageMask; + private final int mMessageMask; @Nullable private final String mMessageString; @@ -972,8 +910,7 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto this.mMessageString = messageString; } - @Nullable - private Integer getMessageMask() { + private int getMessageMask() { return mMessageMask; } diff --git a/core/java/com/android/internal/protolog/ProtoLogConfigurationService.java b/core/java/com/android/internal/protolog/ProtoLogConfigurationService.java index d54a80b7c921..7031d694f09c 100644 --- a/core/java/com/android/internal/protolog/ProtoLogConfigurationService.java +++ b/core/java/com/android/internal/protolog/ProtoLogConfigurationService.java @@ -26,8 +26,6 @@ import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Mes import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.LOCATION; import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.MESSAGE; import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.MESSAGE_ID; -import static android.internal.perfetto.protos.TracePacketOuterClass.TracePacket.PROTOLOG_VIEWER_CONFIG; -import static android.internal.perfetto.protos.TracePacketOuterClass.TracePacket.TIMESTAMP; import android.annotation.NonNull; import android.annotation.Nullable; @@ -36,7 +34,6 @@ import android.content.Context; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ShellCallback; -import android.os.SystemClock; import android.tracing.perfetto.DataSourceParams; import android.tracing.perfetto.InitArguments; import android.tracing.perfetto.Producer; @@ -125,7 +122,8 @@ public final class ProtoLogConfigurationService extends IProtoLogConfigurationSe this(ProtoLogDataSource::new, tracer); } - private ProtoLogConfigurationService( + @VisibleForTesting + public ProtoLogConfigurationService( @NonNull ProtoLogDataSourceBuilder dataSourceBuilder, @NonNull ViewerConfigFileTracer tracer) { mDataSource = dataSourceBuilder.build( @@ -374,32 +372,13 @@ public final class ProtoLogConfigurationService extends IProtoLogConfigurationSe private static void dumpTransitionTraceConfig(@NonNull ProtoLogDataSource dataSource, @NonNull String viewerConfigFilePath) { - dataSource.trace(ctx -> { - final ProtoInputStream pis; + Utils.dumpViewerConfig(dataSource, () -> { try { - pis = new ProtoInputStream(new FileInputStream(viewerConfigFilePath)); + return new ProtoInputStream(new FileInputStream(viewerConfigFilePath)); } catch (FileNotFoundException e) { throw new RuntimeException( "Failed to load viewer config file " + viewerConfigFilePath, e); } - - try { - final ProtoOutputStream os = ctx.newTracePacket(); - - os.write(TIMESTAMP, SystemClock.elapsedRealtimeNanos()); - - final long outProtologViewerConfigToken = os.start(PROTOLOG_VIEWER_CONFIG); - while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) { - switch (pis.getFieldNumber()) { - case (int) MESSAGES -> writeViewerConfigMessage(pis, os); - case (int) GROUPS -> writeViewerConfigGroup(pis, os); - } - } - - os.end(outProtologViewerConfigToken); - } catch (IOException e) { - Log.e(LOG_TAG, "Failed to read ProtoLog viewer config to dump on tracing end", e); - } }); } diff --git a/core/java/com/android/internal/protolog/ProtoLogDataSource.java b/core/java/com/android/internal/protolog/ProtoLogDataSource.java index 1b2f5f7ccf2f..0afb135ac6d9 100644 --- a/core/java/com/android/internal/protolog/ProtoLogDataSource.java +++ b/core/java/com/android/internal/protolog/ProtoLogDataSource.java @@ -283,10 +283,24 @@ public class ProtoLogDataSource extends DataSource<ProtoLogDataSource.Instance, public static class Instance extends DataSourceInstance { public interface TracingInstanceStartCallback { + /** + * Execute the tracing instance's onStart callback. + * @param instanceIdx The index of the tracing instance we are executing the callback + * for. + * @param config The protolog configuration for the tracing instance we are executing + * the callback for. + */ void run(int instanceIdx, @NonNull ProtoLogConfig config); } public interface TracingInstanceStopCallback { + /** + * Execute the tracing instance's onStop callback. + * @param instanceIdx The index of the tracing instance we are executing the callback + * for. + * @param config The protolog configuration for the tracing instance we are executing + * the callback for. + */ void run(int instanceIdx, @NonNull ProtoLogConfig config); } diff --git a/core/java/com/android/internal/protolog/ProtoLogImpl.java b/core/java/com/android/internal/protolog/ProtoLogImpl.java index da6d8cff6890..7bdcf2d14b19 100644 --- a/core/java/com/android/internal/protolog/ProtoLogImpl.java +++ b/core/java/com/android/internal/protolog/ProtoLogImpl.java @@ -23,6 +23,7 @@ import static com.android.internal.protolog.common.ProtoLogToolInjected.Value.LO import static com.android.internal.protolog.common.ProtoLogToolInjected.Value.VIEWER_CONFIG_PATH; import android.annotation.Nullable; +import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.IProtoLog; @@ -37,6 +38,8 @@ import java.util.TreeMap; * A service for the ProtoLog logging system. */ public class ProtoLogImpl { + private static final String LOG_TAG = "ProtoLogImpl"; + private static IProtoLog sServiceInstance = null; @ProtoLogToolInjected(VIEWER_CONFIG_PATH) @@ -97,6 +100,9 @@ public class ProtoLogImpl { */ public static synchronized IProtoLog getSingleInstance() { if (sServiceInstance == null) { + Log.i(LOG_TAG, "Setting up " + ProtoLogImpl.class.getSimpleName() + " with " + + "viewerConfigPath = " + sViewerConfigPath); + final var groups = sLogGroups.values().toArray(new IProtoLogGroup[0]); if (android.tracing.Flags.perfettoProtologTracing()) { @@ -105,6 +111,9 @@ public class ProtoLogImpl { // TODO(b/353530422): Remove - temporary fix to unblock b/352290057 // In some tests the viewer config file might not exist in which we don't // want to provide config path to the user + Log.w(LOG_TAG, "Failed to find viewerConfigFile when setting up " + + ProtoLogImpl.class.getSimpleName() + ". " + + "Setting up without a viewer config instead..."); sServiceInstance = new PerfettoProtoLogImpl(sCacheUpdater, groups); } else { sServiceInstance = diff --git a/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java b/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java index 6c8996e610dc..0a80e006d5bc 100644 --- a/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java +++ b/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java @@ -43,6 +43,13 @@ public class ProtoLogViewerConfigReader { return mLogMessageMap.get(messageHash); } + /** + * Load the viewer configs for the target groups into memory. + * Only viewer configs loaded into memory can be required. So this must be called for all groups + * we want to query before we query their viewer config. + * + * @param groups Groups to load the viewer configs from file into memory. + */ public synchronized void loadViewerConfig(@NonNull String[] groups) { loadViewerConfig(groups, (message) -> {}); } diff --git a/core/java/com/android/internal/protolog/Utils.java b/core/java/com/android/internal/protolog/Utils.java new file mode 100644 index 000000000000..1e6ba309c046 --- /dev/null +++ b/core/java/com/android/internal/protolog/Utils.java @@ -0,0 +1,138 @@ +/* + * 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.internal.protolog; + +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.GROUPS; +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.ID; +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.NAME; +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.TAG; +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MESSAGES; +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.GROUP_ID; +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.LEVEL; +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.LOCATION; +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.MESSAGE; +import static android.internal.perfetto.protos.TracePacketOuterClass.TracePacket.PROTOLOG_VIEWER_CONFIG; +import static android.internal.perfetto.protos.TracePacketOuterClass.TracePacket.TIMESTAMP; + +import android.annotation.NonNull; +import android.internal.perfetto.protos.Protolog; +import android.os.SystemClock; +import android.util.Log; +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoOutputStream; + +import java.io.IOException; + +public class Utils { + private static final String LOG_TAG = "ProtoLogUtils"; + + /** + * Dump the viewer config provided by the input stream to the target datasource. + * @param dataSource The datasource to dump the ProtoLog viewer config to. + * @param viewerConfigInputStreamProvider The InputStream that provided the proto viewer config. + */ + public static void dumpViewerConfig(@NonNull ProtoLogDataSource dataSource, + @NonNull ViewerConfigInputStreamProvider viewerConfigInputStreamProvider) { + dataSource.trace(ctx -> { + try { + ProtoInputStream pis = viewerConfigInputStreamProvider.getInputStream(); + + final ProtoOutputStream os = ctx.newTracePacket(); + + os.write(TIMESTAMP, SystemClock.elapsedRealtimeNanos()); + + final long outProtologViewerConfigToken = os.start(PROTOLOG_VIEWER_CONFIG); + while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + if (pis.getFieldNumber() == (int) MESSAGES) { + writeViewerConfigMessage(pis, os); + } + + if (pis.getFieldNumber() == (int) GROUPS) { + writeViewerConfigGroup(pis, os); + } + } + + os.end(outProtologViewerConfigToken); + } catch (IOException e) { + Log.e(LOG_TAG, "Failed to read ProtoLog viewer config to dump to datasource", e); + } + }); + } + + private static void writeViewerConfigGroup( + @NonNull ProtoInputStream pis, @NonNull ProtoOutputStream os) throws IOException { + final long inGroupToken = pis.start(GROUPS); + final long outGroupToken = os.start(GROUPS); + + while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pis.getFieldNumber()) { + case (int) ID: + int id = pis.readInt(ID); + os.write(ID, id); + break; + case (int) NAME: + String name = pis.readString(NAME); + os.write(NAME, name); + break; + case (int) TAG: + String tag = pis.readString(TAG); + os.write(TAG, tag); + break; + default: + throw new RuntimeException( + "Unexpected field id " + pis.getFieldNumber()); + } + } + + pis.end(inGroupToken); + os.end(outGroupToken); + } + + private static void writeViewerConfigMessage( + ProtoInputStream pis, ProtoOutputStream os) throws IOException { + final long inMessageToken = pis.start(MESSAGES); + final long outMessagesToken = os.start(MESSAGES); + + while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pis.getFieldNumber()) { + case (int) Protolog.ProtoLogViewerConfig.MessageData.MESSAGE_ID: + os.write(Protolog.ProtoLogViewerConfig.MessageData.MESSAGE_ID, + pis.readLong(Protolog.ProtoLogViewerConfig.MessageData.MESSAGE_ID)); + break; + case (int) MESSAGE: + os.write(MESSAGE, pis.readString(MESSAGE)); + break; + case (int) LEVEL: + os.write(LEVEL, pis.readInt(LEVEL)); + break; + case (int) GROUP_ID: + os.write(GROUP_ID, pis.readInt(GROUP_ID)); + break; + case (int) LOCATION: + os.write(LOCATION, pis.readString(LOCATION)); + break; + default: + throw new RuntimeException( + "Unexpected field id " + pis.getFieldNumber()); + } + } + + pis.end(inMessageToken); + os.end(outMessagesToken); + } + +} diff --git a/core/java/com/android/internal/protolog/common/LogLevel.java b/core/java/com/android/internal/protolog/common/LogLevel.java index 16c34e1f333e..b5541ae81c2d 100644 --- a/core/java/com/android/internal/protolog/common/LogLevel.java +++ b/core/java/com/android/internal/protolog/common/LogLevel.java @@ -17,10 +17,18 @@ package com.android.internal.protolog.common; public enum LogLevel { - DEBUG("d"), VERBOSE("v"), INFO("i"), WARN("w"), ERROR("e"), WTF("wtf"); + DEBUG("d", 1), + VERBOSE("v", 2), + INFO("i", 3), + WARN("w", 4), + ERROR("e", 5), + WTF("wtf", 6); public final String shortCode; - LogLevel(String shortCode) { + public final int id; + + LogLevel(String shortCode, int id) { this.shortCode = shortCode; + this.id = id; } } diff --git a/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java b/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java index 319efe04da8c..30b160ab161b 100644 --- a/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java +++ b/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java @@ -16,15 +16,15 @@ package com.android.internal.ravenwood; import android.ravenwood.annotation.RavenwoodKeepWholeClass; -import android.ravenwood.annotation.RavenwoodNativeSubstitutionClass; +import android.ravenwood.annotation.RavenwoodRedirect; +import android.ravenwood.annotation.RavenwoodRedirectionClass; import android.ravenwood.annotation.RavenwoodReplace; /** * Class to interact with the Ravenwood environment. */ @RavenwoodKeepWholeClass -@RavenwoodNativeSubstitutionClass( - "com.android.platform.test.ravenwood.nativesubstitution.RavenwoodEnvironment_host") +@RavenwoodRedirectionClass("RavenwoodEnvironment_host") public final class RavenwoodEnvironment { public static final String TAG = "RavenwoodEnvironment"; @@ -40,7 +40,7 @@ public final class RavenwoodEnvironment { ensureRavenwoodInitialized(); } - private static RuntimeException notSupportedOnDevice() { + public static RuntimeException notSupportedOnDevice() { return new UnsupportedOperationException("This method can only be used on Ravenwood"); } @@ -56,14 +56,10 @@ public final class RavenwoodEnvironment { * * No-op if called on the device side. */ - @RavenwoodReplace + @RavenwoodRedirect public static void ensureRavenwoodInitialized() { } - private static void ensureRavenwoodInitialized$ravenwood() { - nativeEnsureRavenwoodInitialized(); - } - /** * USE IT SPARINGLY! Returns true if it's running on Ravenwood, hostside test environment. * @@ -89,15 +85,11 @@ public final class RavenwoodEnvironment { * Get the object back from the address obtained from * {@link dalvik.system.VMRuntime#addressOf(Object)}. */ - @RavenwoodReplace + @RavenwoodRedirect public <T> T fromAddress(long address) { throw notSupportedOnDevice(); } - private <T> T fromAddress$ravenwood(long address) { - return nativeFromAddress(address); - } - /** * See {@link Workaround}. It's only usable on Ravenwood. */ @@ -113,20 +105,11 @@ public final class RavenwoodEnvironment { /** * @return the "ravenwood-runtime" directory. */ - @RavenwoodReplace + @RavenwoodRedirect public String getRavenwoodRuntimePath() { throw notSupportedOnDevice(); } - private String getRavenwoodRuntimePath$ravenwood() { - return nativeGetRavenwoodRuntimePath(); - } - - // Private native methods that are actually substituted on Ravenwood - private native <T> T nativeFromAddress(long address); - private native String nativeGetRavenwoodRuntimePath(); - private static native void nativeEnsureRavenwoodInitialized(); - /** * A set of APIs used to work around missing features on Ravenwood. Ideally, this class should * be empty, and all its APIs should be able to be implemented properly. diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java index 11c220b14bcc..0ec55f958f38 100644 --- a/core/java/com/android/internal/widget/LockPatternView.java +++ b/core/java/com/android/internal/widget/LockPatternView.java @@ -120,6 +120,7 @@ public class LockPatternView extends View { private static final String TAG = "LockPatternView"; private OnPatternListener mOnPatternListener; + private ExternalHapticsPlayer mExternalHapticsPlayer; @UnsupportedAppUsage private final ArrayList<Cell> mPattern = new ArrayList<Cell>(9); @@ -317,6 +318,13 @@ public class LockPatternView extends View { void onPatternDetected(List<Cell> pattern); } + /** An external haptics player for pattern updates. */ + public interface ExternalHapticsPlayer{ + + /** Perform haptic feedback when a cell is added to the pattern. */ + void performCellAddedFeedback(); + } + public LockPatternView(Context context) { this(context, null); } @@ -461,6 +469,15 @@ public class LockPatternView extends View { } /** + * Set the external haptics player for feedback on pattern detection. + * @param player The external player. + */ + @UnsupportedAppUsage + public void setExternalHapticsPlayer(ExternalHapticsPlayer player) { + mExternalHapticsPlayer = player; + } + + /** * Set the pattern explicitely (rather than waiting for the user to input * a pattern). * @param displayMode How to display the pattern. @@ -847,6 +864,16 @@ public class LockPatternView extends View { return null; } + @Override + public boolean performHapticFeedback(int feedbackConstant, int flags) { + if (mExternalHapticsPlayer != null) { + mExternalHapticsPlayer.performCellAddedFeedback(); + return true; + } else { + return super.performHapticFeedback(feedbackConstant, flags); + } + } + private void addCellToPattern(Cell newCell) { mPatternDrawLookup[newCell.getRow()][newCell.getColumn()] = true; mPattern.add(newCell); diff --git a/core/jni/android_view_TunnelModeEnabledListener.cpp b/core/jni/android_view_TunnelModeEnabledListener.cpp index af7bae8c89dd..d9ab9571cfbe 100644 --- a/core/jni/android_view_TunnelModeEnabledListener.cpp +++ b/core/jni/android_view_TunnelModeEnabledListener.cpp @@ -88,20 +88,19 @@ void nativeDestroy(JNIEnv* env, jclass clazz, jlong ptr) { void nativeRegister(JNIEnv* env, jclass clazz, jlong ptr) { sp<TunnelModeEnabledListener> listener = reinterpret_cast<TunnelModeEnabledListener*>(ptr); - if (SurfaceComposerClient::addTunnelModeEnabledListener(listener) != OK) { - constexpr auto error_msg = "Couldn't addTunnelModeEnabledListener"; - ALOGE(error_msg); - jniThrowRuntimeException(env, error_msg); + status_t status = SurfaceComposerClient::addTunnelModeEnabledListener(listener); + if (status != OK) { + ALOGE("Couldn't addTunnelModeEnabledListener (%d)", status); + jniThrowRuntimeException(env, "Couldn't addTunnelModeEnabledListener"); } } void nativeUnregister(JNIEnv* env, jclass clazz, jlong ptr) { sp<TunnelModeEnabledListener> listener = reinterpret_cast<TunnelModeEnabledListener*>(ptr); - - if (SurfaceComposerClient::removeTunnelModeEnabledListener(listener) != OK) { - constexpr auto error_msg = "Couldn't removeTunnelModeEnabledListener"; - ALOGE(error_msg); - jniThrowRuntimeException(env, error_msg); + status_t status = SurfaceComposerClient::removeTunnelModeEnabledListener(listener); + if (status != OK) { + ALOGE("Couldn't removeTunnelModeEnabledListener (%d)", status); + jniThrowRuntimeException(env, "Couldn't removeTunnelModeEnabledListener"); } } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index a19b71c68deb..d35c66ed719e 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -4033,7 +4033,6 @@ <!-- Allows an application to manage policy related to block package uninstallation. <p>Protection level: internal|role <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only. - @FlaggedApi("android.app.admin.flags.dedicated_device_control_api_enabled") --> <permission android:name="android.permission.MANAGE_DEVICE_POLICY_BLOCK_UNINSTALL" android:protectionLevel="internal|role" /> @@ -4041,7 +4040,6 @@ <!-- Allows an application to manage policy related to camera toggle. <p>Protection level: internal|role <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only. - @FlaggedApi("android.app.admin.flags.dedicated_device_control_api_enabled") --> <permission android:name="android.permission.MANAGE_DEVICE_POLICY_CAMERA_TOGGLE" android:protectionLevel="internal|role" /> @@ -4049,7 +4047,6 @@ <!-- Allows an application to manage policy related to microphone toggle. <p>Protection level: internal|role <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only. - @FlaggedApi("android.app.admin.flags.dedicated_device_control_api_enabled") --> <permission android:name="android.permission.MANAGE_DEVICE_POLICY_MICROPHONE_TOGGLE" android:protectionLevel="internal|role" /> diff --git a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java index 10aed8d51d09..14292725506e 100644 --- a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java +++ b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java @@ -16,8 +16,6 @@ package android.graphics; -import static com.android.text.flags.Flags.FLAG_VENDOR_CUSTOM_LOCALE_FALLBACK; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -32,7 +30,6 @@ import android.graphics.fonts.FontFamily; import android.graphics.fonts.SystemFonts; import android.graphics.text.PositionedGlyphs; import android.graphics.text.TextRunShaper; -import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.text.FontConfig; @@ -931,7 +928,6 @@ public class TypefaceSystemFallbackTest { return String.format(xml, op, lang, font); } - @RequiresFlagsEnabled(FLAG_VENDOR_CUSTOM_LOCALE_FALLBACK) @Test public void testBuildSystemFallback__Customization_locale_prepend() { final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); @@ -947,7 +943,6 @@ public class TypefaceSystemFallbackTest { assertB3emFontIsUsed(typeface); } - @RequiresFlagsEnabled(FLAG_VENDOR_CUSTOM_LOCALE_FALLBACK) @Test public void testBuildSystemFallback__Customization_locale_replace() { final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); @@ -963,7 +958,6 @@ public class TypefaceSystemFallbackTest { assertB3emFontIsUsed(typeface); } - @RequiresFlagsEnabled(FLAG_VENDOR_CUSTOM_LOCALE_FALLBACK) @Test public void testBuildSystemFallback__Customization_locale_append() { final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); @@ -979,7 +973,6 @@ public class TypefaceSystemFallbackTest { assertA3emFontIsUsed(typeface); } - @RequiresFlagsEnabled(FLAG_VENDOR_CUSTOM_LOCALE_FALLBACK) @Test public void testBuildSystemFallback__Customization_locale_ScriptMismatch() { final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); @@ -995,7 +988,6 @@ public class TypefaceSystemFallbackTest { assertA3emFontIsUsed(typeface); } - @RequiresFlagsEnabled(FLAG_VENDOR_CUSTOM_LOCALE_FALLBACK) @Test public void testBuildSystemFallback__Customization_locale_SubscriptMatch() { final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 9a55b80f37e2..880f30c6cdc0 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -580,6 +580,11 @@ applications that come with the platform <permission name="android.permission.PREPARE_FACTORY_RESET" /> <!-- Permission required for CTS test - FileIntegrityManagerTest --> <permission name="android.permission.SETUP_FSVERITY" /> + <!-- Permissions required for CTS test - AppFunctionManagerTest --> + <permission name="android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED" /> + <permission name="android.permission.EXECUTE_APP_FUNCTIONS" /> + <!-- Permission required for CTS test - CtsNfcTestCases --> + <permission name="android.permission.NFC_SET_CONTROLLER_ALWAYS_ON" /> </privapp-permissions> <privapp-permissions package="com.android.statementservice"> diff --git a/graphics/java/android/graphics/fonts/FontCustomizationParser.java b/graphics/java/android/graphics/fonts/FontCustomizationParser.java index ba5628cd2bc1..b7bf0553bcc6 100644 --- a/graphics/java/android/graphics/fonts/FontCustomizationParser.java +++ b/graphics/java/android/graphics/fonts/FontCustomizationParser.java @@ -182,10 +182,8 @@ public class FontCustomizationParser { // For ignoring the customization, consume the new-locale-family element but don't // register any customizations. - if (com.android.text.flags.Flags.vendorCustomLocaleFallback()) { - outCustomization.add(new FontConfig.Customization.LocaleFallback( - Locale.forLanguageTag(lang), intOp, family)); - } + outCustomization.add(new FontConfig.Customization.LocaleFallback( + Locale.forLanguageTag(lang), intOp, family)); } else { throw new IllegalArgumentException("Unknown customizationType=" + customizationType); } diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt new file mode 100644 index 000000000000..35d459f27534 --- /dev/null +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt @@ -0,0 +1,324 @@ +/* + * 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.bubbles.bar + +import android.app.ActivityManager +import android.content.Context +import android.graphics.Insets +import android.graphics.Rect +import android.view.LayoutInflater +import android.view.View +import android.view.WindowManager +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation +import com.android.internal.protolog.ProtoLog +import com.android.wm.shell.R +import com.android.wm.shell.bubbles.Bubble +import com.android.wm.shell.bubbles.BubbleData +import com.android.wm.shell.bubbles.BubbleExpandedViewManager +import com.android.wm.shell.bubbles.BubblePositioner +import com.android.wm.shell.bubbles.BubbleTaskView +import com.android.wm.shell.bubbles.BubbleTaskViewFactory +import com.android.wm.shell.bubbles.DeviceConfig +import com.android.wm.shell.bubbles.RegionSamplingProvider +import com.android.wm.shell.common.ShellExecutor +import com.android.wm.shell.shared.bubbles.BubbleBarLocation +import com.android.wm.shell.shared.handles.RegionSamplingHelper +import com.android.wm.shell.taskview.TaskView +import com.android.wm.shell.taskview.TaskViewTaskController +import com.google.common.truth.Truth.assertThat +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever +import java.util.Collections +import java.util.concurrent.Executor + +/** Tests for [BubbleBarExpandedViewTest] */ +@SmallTest +@RunWith(AndroidJUnit4::class) +class BubbleBarExpandedViewTest { + companion object { + const val SCREEN_WIDTH = 2000 + const val SCREEN_HEIGHT = 1000 + } + + private val context = ApplicationProvider.getApplicationContext<Context>() + private val windowManager = context.getSystemService(WindowManager::class.java) + + private lateinit var mainExecutor: TestExecutor + private lateinit var bgExecutor: TestExecutor + + private lateinit var expandedViewManager: BubbleExpandedViewManager + private lateinit var positioner: BubblePositioner + private lateinit var bubbleTaskView: BubbleTaskView + + private lateinit var bubbleExpandedView: BubbleBarExpandedView + private var testableRegionSamplingHelper: TestableRegionSamplingHelper? = null + private var regionSamplingProvider: TestRegionSamplingProvider? = null + + @Before + fun setUp() { + ProtoLog.REQUIRE_PROTOLOGTOOL = false + mainExecutor = TestExecutor() + bgExecutor = TestExecutor() + positioner = BubblePositioner(context, windowManager) + positioner.setShowingInBubbleBar(true) + val deviceConfig = + DeviceConfig( + windowBounds = Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT), + isLargeScreen = true, + isSmallTablet = false, + isLandscape = true, + isRtl = false, + insets = Insets.of(10, 20, 30, 40) + ) + positioner.update(deviceConfig) + + expandedViewManager = createExpandedViewManager() + bubbleTaskView = FakeBubbleTaskViewFactory().create() + + val inflater = LayoutInflater.from(context) + + regionSamplingProvider = TestRegionSamplingProvider() + + bubbleExpandedView = (inflater.inflate( + R.layout.bubble_bar_expanded_view, null, false /* attachToRoot */ + ) as BubbleBarExpandedView) + bubbleExpandedView.initialize( + expandedViewManager, + positioner, + false /* isOverflow */, + bubbleTaskView, + mainExecutor, + bgExecutor, + regionSamplingProvider + ) + + getInstrumentation().runOnMainSync(Runnable { + bubbleExpandedView.onAttachedToWindow() + // Helper should be created once attached to window + testableRegionSamplingHelper = regionSamplingProvider!!.helper + }) + } + + @After + fun tearDown() { + testableRegionSamplingHelper?.stopAndDestroy() + } + + @Test + fun testCreateSamplingHelper_onAttach() { + assertThat(testableRegionSamplingHelper).isNotNull() + } + + @Test + fun testDestroySamplingHelper_onDetach() { + bubbleExpandedView.onDetachedFromWindow() + assertThat(testableRegionSamplingHelper!!.isDestroyed).isTrue() + } + + @Test + fun testStopSampling_onDragStart() { + bubbleExpandedView.setContentVisibility(true) + assertThat(testableRegionSamplingHelper!!.isStarted).isTrue() + + bubbleExpandedView.setDragging(true) + assertThat(testableRegionSamplingHelper!!.isStopped).isTrue() + } + + @Test + fun testStartSampling_onDragEnd() { + bubbleExpandedView.setDragging(true) + bubbleExpandedView.setContentVisibility(true) + assertThat(testableRegionSamplingHelper!!.isStopped).isTrue() + + bubbleExpandedView.setDragging(false) + assertThat(testableRegionSamplingHelper!!.isStarted).isTrue() + } + + @Test + fun testStartSampling_onContentVisible() { + bubbleExpandedView.setContentVisibility(true) + assertThat(testableRegionSamplingHelper!!.setWindowVisible).isTrue() + assertThat(testableRegionSamplingHelper!!.isStarted).isTrue() + } + + @Test + fun testStopSampling_onContentInvisible() { + bubbleExpandedView.setContentVisibility(false) + + assertThat(testableRegionSamplingHelper!!.setWindowInvisible).isTrue() + assertThat(testableRegionSamplingHelper!!.isStopped).isTrue() + } + + @Test + fun testSampling_startStopAnimating_visible() { + bubbleExpandedView.isAnimating = true + bubbleExpandedView.setContentVisibility(true) + assertThat(testableRegionSamplingHelper!!.isStopped).isTrue() + + bubbleExpandedView.isAnimating = false + assertThat(testableRegionSamplingHelper!!.isStarted).isTrue() + } + + @Test + fun testSampling_startStopAnimating_invisible() { + bubbleExpandedView.isAnimating = true + bubbleExpandedView.setContentVisibility(false) + assertThat(testableRegionSamplingHelper!!.isStopped).isTrue() + testableRegionSamplingHelper!!.reset() + + bubbleExpandedView.isAnimating = false + assertThat(testableRegionSamplingHelper!!.isStopped).isTrue() + } + + private inner class FakeBubbleTaskViewFactory : BubbleTaskViewFactory { + override fun create(): BubbleTaskView { + val taskViewTaskController = mock<TaskViewTaskController>() + val taskView = TaskView(context, taskViewTaskController) + val taskInfo = mock<ActivityManager.RunningTaskInfo>() + whenever(taskViewTaskController.taskInfo).thenReturn(taskInfo) + return BubbleTaskView(taskView, mainExecutor) + } + } + + private inner class TestRegionSamplingProvider : RegionSamplingProvider { + + lateinit var helper: TestableRegionSamplingHelper + + override fun createHelper( + sampledView: View?, + callback: RegionSamplingHelper.SamplingCallback?, + backgroundExecutor: Executor?, + mainExecutor: Executor? + ): RegionSamplingHelper { + helper = TestableRegionSamplingHelper(sampledView, callback, backgroundExecutor, + mainExecutor) + return helper + } + } + + private inner class TestableRegionSamplingHelper( + sampledView: View?, + samplingCallback: SamplingCallback?, + backgroundExecutor: Executor?, + mainExecutor: Executor? + ) : RegionSamplingHelper(sampledView, samplingCallback, backgroundExecutor, mainExecutor) { + + var isStarted = false + var isStopped = false + var isDestroyed = false + var setWindowVisible = false + var setWindowInvisible = false + + override fun start(initialSamplingBounds: Rect) { + super.start(initialSamplingBounds) + isStarted = true + } + + override fun stop() { + super.stop() + isStopped = true + } + + override fun stopAndDestroy() { + super.stopAndDestroy() + isDestroyed = true + } + + override fun setWindowVisible(visible: Boolean) { + super.setWindowVisible(visible) + if (visible) { + setWindowVisible = true + } else { + setWindowInvisible = true + } + } + + fun reset() { + isStarted = false + isStopped = false + isDestroyed = false + setWindowVisible = false + setWindowInvisible = false + } + } + + private fun createExpandedViewManager(): BubbleExpandedViewManager { + return object : BubbleExpandedViewManager { + override val overflowBubbles: List<Bubble> + get() = Collections.emptyList() + + override fun setOverflowListener(listener: BubbleData.Listener) { + } + + override fun collapseStack() { + } + + override fun updateWindowFlagsForBackpress(intercept: Boolean) { + } + + override fun promoteBubbleFromOverflow(bubble: Bubble) { + } + + override fun removeBubble(key: String, reason: Int) { + } + + override fun dismissBubble(bubble: Bubble, reason: Int) { + } + + override fun setAppBubbleTaskId(key: String, taskId: Int) { + } + + override fun isStackExpanded(): Boolean { + return true + } + + override fun isShowingAsBubbleBar(): Boolean { + return true + } + + override fun hideCurrentInputMethod() { + } + + override fun updateBubbleBarLocation(location: BubbleBarLocation) { + } + } + } + + private class TestExecutor : ShellExecutor { + + private val runnables: MutableList<Runnable> = mutableListOf() + + override fun execute(runnable: Runnable) { + runnables.add(runnable) + } + + override fun executeDelayed(runnable: Runnable, delayMillis: Long) { + execute(runnable) + } + + override fun removeCallbacks(runnable: Runnable?) {} + + override fun hasCallback(runnable: Runnable?): Boolean = false + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/handles/RegionSamplingHelper.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/handles/RegionSamplingHelper.java index b92b8ef657a3..a06cf78d0898 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/handles/RegionSamplingHelper.java +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/handles/RegionSamplingHelper.java @@ -329,7 +329,7 @@ public class RegionSamplingHelper implements View.OnAttachStateChangeListener, /** * Get the sampled region of interest from the sampled view * @param sampledView The view that this helper is attached to for convenience - * @return the region to be sampled in sceen coordinates. Return {@code null} to avoid + * @return the region to be sampled in screen coordinates. Return {@code null} to avoid * sampling in this frame */ Rect getSampledRegion(View sampledView); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebUtils.kt new file mode 100644 index 000000000000..05ce36120c4f --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebUtils.kt @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:JvmName("AppToWebUtils") + +package com.android.wm.shell.apptoweb + +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.net.Uri + +private val browserIntent = Intent() + .setAction(Intent.ACTION_VIEW) + .addCategory(Intent.CATEGORY_BROWSABLE) + .setData(Uri.parse("http:")) + +/** + * Returns a boolean indicating whether a given package is a browser app. + */ +fun isBrowserApp(context: Context, packageName: String, userId: Int): Boolean { + browserIntent.setPackage(packageName) + val list = context.packageManager.queryIntentActivitiesAsUser( + browserIntent, PackageManager.MATCH_ALL, userId + ) + + list.forEach { + if (it.activityInfo != null && it.handleAllWebDataURI) { + return true + } + } + return false +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AssistContentRequester.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AssistContentRequester.kt new file mode 100644 index 000000000000..249185eca323 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AssistContentRequester.kt @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.apptoweb + +import android.app.ActivityTaskManager +import android.app.IActivityTaskManager +import android.app.IAssistDataReceiver +import android.app.assist.AssistContent +import android.content.Context +import android.graphics.Bitmap +import android.os.Bundle +import android.os.RemoteException +import android.util.Slog +import java.lang.ref.WeakReference +import java.util.Collections +import java.util.WeakHashMap +import java.util.concurrent.Executor + +/** + * Can be used to request the AssistContent from a provided task id, useful for getting the web uri + * if provided from the task. + */ +class AssistContentRequester( + context: Context, + private val callBackExecutor: Executor, + private val systemInteractionExecutor: Executor +) { + interface Callback { + // Called when the [AssistContent] of the requested task is available. + fun onAssistContentAvailable(assistContent: AssistContent?) + } + + private val activityTaskManager: IActivityTaskManager = ActivityTaskManager.getService() + private val attributionTag: String? = context.attributionTag + private val packageName: String = context.applicationContext.packageName + + // If system loses the callback, our internal cache of original callback will also get cleared. + private val pendingCallbacks = Collections.synchronizedMap(WeakHashMap<Any, Callback>()) + + /** + * Request the [AssistContent] from the task with the provided id. + * + * @param taskId to query for the content. + * @param callback to call when the content is available, called on the main thread. + */ + fun requestAssistContent(taskId: Int, callback: Callback) { + // ActivityTaskManager interaction here is synchronous, so call off the main thread. + systemInteractionExecutor.execute { + try { + val success = activityTaskManager.requestAssistDataForTask( + AssistDataReceiver(callback, this), + taskId, + packageName, + attributionTag, + false /* fetchStructure */ + ) + if (!success) { + executeOnMainExecutor { callback.onAssistContentAvailable(null) } + } + } catch (e: RemoteException) { + Slog.e(TAG, "Requesting assist content failed for task: $taskId", e) + } + } + } + + private fun executeOnMainExecutor(callback: Runnable) { + callBackExecutor.execute(callback) + } + + private class AssistDataReceiver( + callback: Callback, + parent: AssistContentRequester + ) : IAssistDataReceiver.Stub() { + // The AssistDataReceiver binder callback object is passed to a system server, that may + // keep hold of it for longer than the lifetime of the AssistContentRequester object, + // potentially causing a memory leak. In the callback passed to the system server, only + // keep a weak reference to the parent object and lookup its callback if it still exists. + private val parentRef: WeakReference<AssistContentRequester> + private val callbackKey = Any() + + init { + parent.pendingCallbacks[callbackKey] = callback + parentRef = WeakReference(parent) + } + + override fun onHandleAssistData(data: Bundle?) { + val content = data?.getParcelable(ASSIST_KEY_CONTENT, AssistContent::class.java) + if (content == null) { + Slog.d(TAG, "Received AssistData, but no AssistContent found") + return + } + val requester = parentRef.get() + if (requester != null) { + val callback = requester.pendingCallbacks[callbackKey] + if (callback != null) { + requester.executeOnMainExecutor { callback.onAssistContentAvailable(content) } + } else { + Slog.d(TAG, "Callback received after calling UI was disposed of") + } + } else { + Slog.d(TAG, "Callback received after Requester was collected") + } + } + + override fun onHandleAssistScreenshot(screenshot: Bitmap) {} + } + + companion object { + private const val TAG = "AssistContentRequester" + private const val ASSIST_KEY_CONTENT = "content" + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OWNERS index bfe1306a60e6..6207e5b020f7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OWNERS +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OWNERS @@ -1,6 +1,8 @@ atsjenk@google.com jorgegil@google.com madym@google.com +mattsziklay@google.com +mdehaini@google.com pbdr@google.com tkachenkoi@google.com vaniadesmonda@google.com 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 0c95934abf93..169361ad5f6b 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 @@ -609,7 +609,8 @@ public class Bubble implements BubbleViewProvider { callback.onBubbleViewsReady(bubble); } }, - mMainExecutor); + mMainExecutor, + mBgExecutor); if (mInflateSynchronously) { mInflationTaskLegacy.onPostExecute(mInflationTaskLegacy.doInBackground()); } else { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt index f32974e1765d..68c4657f2b68 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt @@ -80,7 +80,10 @@ class BubbleOverflow(private val context: Context, private val positioner: Bubbl expandedViewManager, positioner, /* isOverflow= */ true, - /* bubbleTaskView= */ null + /* bubbleTaskView= */ null, + /* mainExecutor= */ null, + /* backgroundExecutor= */ null, + /* regionSamplingProvider= */ null ) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java index 5f8f0fd0c54c..0c0fd7b10f6e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java @@ -60,6 +60,9 @@ public class BubbleTaskViewHelper { /** Called when back is pressed on the task root. */ void onBackPressed(); + + /** Called when task removal has started. */ + void onTaskRemovalStarted(); } private final Context mContext; @@ -190,6 +193,7 @@ public class BubbleTaskViewHelper { ((ViewGroup) mParentView).removeView(mTaskView); mTaskView = null; } + mListener.onTaskRemovalStarted(); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java index 13855f73fb4a..3982a237dd3b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java @@ -38,6 +38,7 @@ import android.graphics.drawable.Icon; import android.util.Log; import android.util.PathParser; import android.view.LayoutInflater; +import android.view.View; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.graphics.ColorUtils; @@ -47,6 +48,7 @@ import com.android.launcher3.icons.BubbleIconFactory; import com.android.wm.shell.R; import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView; import com.android.wm.shell.bubbles.bar.BubbleBarLayerView; +import com.android.wm.shell.shared.handles.RegionSamplingHelper; import java.lang.ref.WeakReference; import java.util.Objects; @@ -222,7 +224,16 @@ public class BubbleViewInfoTask { ProtoLog.v(WM_SHELL_BUBBLES, "Task initializing bubble bar expanded view key=%s", mBubble.getKey()); viewInfo.bubbleBarExpandedView.initialize(mExpandedViewManager.get(), - mPositioner.get(), false /* isOverflow */, viewInfo.taskView); + mPositioner.get(), false /* isOverflow */, viewInfo.taskView, + mMainExecutor, mBgExecutor, new RegionSamplingProvider() { + @Override + public RegionSamplingHelper createHelper(View sampledView, + RegionSamplingHelper.SamplingCallback callback, + Executor backgroundExecutor, Executor mainExecutor) { + return RegionSamplingProvider.super.createHelper(sampledView, + callback, backgroundExecutor, mainExecutor); + } + }); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskLegacy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskLegacy.java index 5cfebf8f1647..1b7bb0db6516 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskLegacy.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskLegacy.java @@ -38,6 +38,7 @@ import android.os.AsyncTask; import android.util.Log; import android.util.PathParser; import android.view.LayoutInflater; +import android.view.View; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.graphics.ColorUtils; @@ -46,6 +47,7 @@ import com.android.launcher3.icons.BubbleIconFactory; import com.android.wm.shell.R; import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView; import com.android.wm.shell.bubbles.bar.BubbleBarLayerView; +import com.android.wm.shell.shared.handles.RegionSamplingHelper; import java.lang.ref.WeakReference; import java.util.Objects; @@ -85,6 +87,7 @@ public class BubbleViewInfoTaskLegacy extends private boolean mSkipInflation; private Callback mCallback; private Executor mMainExecutor; + private Executor mBackgroundExecutor; /** * Creates a task to load information for the provided {@link Bubble}. Once all info @@ -100,7 +103,8 @@ public class BubbleViewInfoTaskLegacy extends BubbleIconFactory factory, boolean skipInflation, Callback c, - Executor mainExecutor) { + Executor mainExecutor, + Executor backgroundExecutor) { mBubble = b; mContext = new WeakReference<>(context); mExpandedViewManager = new WeakReference<>(expandedViewManager); @@ -112,6 +116,7 @@ public class BubbleViewInfoTaskLegacy extends mSkipInflation = skipInflation; mCallback = c; mMainExecutor = mainExecutor; + mBackgroundExecutor = backgroundExecutor; } @Override @@ -123,7 +128,7 @@ public class BubbleViewInfoTaskLegacy extends if (mLayerView.get() != null) { return BubbleViewInfo.populateForBubbleBar(mContext.get(), mExpandedViewManager.get(), mTaskViewFactory.get(), mPositioner.get(), mLayerView.get(), mIconFactory, - mBubble, mSkipInflation); + mBubble, mSkipInflation, mMainExecutor, mBackgroundExecutor); } else { return BubbleViewInfo.populate(mContext.get(), mExpandedViewManager.get(), mTaskViewFactory.get(), mPositioner.get(), mStackView.get(), mIconFactory, @@ -188,7 +193,9 @@ public class BubbleViewInfoTaskLegacy extends BubbleBarLayerView layerView, BubbleIconFactory iconFactory, Bubble b, - boolean skipInflation) { + boolean skipInflation, + Executor mainExecutor, + Executor backgroundExecutor) { BubbleViewInfo info = new BubbleViewInfo(); if (!skipInflation && !b.isInflated()) { @@ -197,7 +204,16 @@ public class BubbleViewInfoTaskLegacy extends info.bubbleBarExpandedView = (BubbleBarExpandedView) inflater.inflate( R.layout.bubble_bar_expanded_view, layerView, false /* attachToRoot */); info.bubbleBarExpandedView.initialize( - expandedViewManager, positioner, false /* isOverflow */, bubbleTaskView); + expandedViewManager, positioner, false /* isOverflow */, bubbleTaskView, + mainExecutor, backgroundExecutor, new RegionSamplingProvider() { + @Override + public RegionSamplingHelper createHelper(View sampledView, + RegionSamplingHelper.SamplingCallback callback, + Executor backgroundExecutor, Executor mainExecutor) { + return RegionSamplingProvider.super.createHelper(sampledView, + callback, backgroundExecutor, mainExecutor); + } + }); } if (!populateCommonInfo(info, c, b, iconFactory)) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/RegionSamplingProvider.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/RegionSamplingProvider.java new file mode 100644 index 000000000000..30f5c8fd56c3 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/RegionSamplingProvider.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.bubbles; + +import android.view.View; + +import com.android.wm.shell.shared.handles.RegionSamplingHelper; + +import java.util.concurrent.Executor; + +/** + * Wrapper to provide a {@link com.android.wm.shell.shared.handles.RegionSamplingHelper} to allow + * testing it. + */ +public interface RegionSamplingProvider { + + /** Creates and returns the region sampling helper */ + default RegionSamplingHelper createHelper(View sampledView, + RegionSamplingHelper.SamplingCallback callback, + Executor backgroundExecutor, + Executor mainExecutor) { + return new RegionSamplingHelper(sampledView, + callback, backgroundExecutor, mainExecutor); + } + +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java index 565fde0a853c..74c3748dccaf 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java @@ -253,6 +253,7 @@ public class BubbleBarAnimationHelper { return; } setDragPivot(bbev); + bbev.setDragging(true); // Corner radius gets scaled, apply the reverse scale to ensure we have the desired radius final float cornerRadius = bbev.getDraggedCornerRadius() / EXPANDED_VIEW_DRAG_SCALE; @@ -329,6 +330,7 @@ public class BubbleBarAnimationHelper { public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); bbev.resetPivot(); + bbev.setDragging(false); } }); startNewDragAnimation(animatorSet); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java index f90b2aa95555..ec235a5d84ab 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java @@ -19,10 +19,7 @@ package com.android.wm.shell.bubbles.bar; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import android.annotation.Nullable; -import android.app.ActivityManager; import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Color; import android.graphics.Insets; import android.graphics.Outline; import android.graphics.Rect; @@ -37,6 +34,7 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.widget.FrameLayout; import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; import com.android.wm.shell.R; import com.android.wm.shell.bubbles.Bubble; @@ -46,9 +44,12 @@ import com.android.wm.shell.bubbles.BubblePositioner; import com.android.wm.shell.bubbles.BubbleTaskView; import com.android.wm.shell.bubbles.BubbleTaskViewHelper; import com.android.wm.shell.bubbles.Bubbles; +import com.android.wm.shell.bubbles.RegionSamplingProvider; import com.android.wm.shell.shared.bubbles.BubbleBarLocation; +import com.android.wm.shell.shared.handles.RegionSamplingHelper; import com.android.wm.shell.taskview.TaskView; +import java.util.concurrent.Executor; import java.util.function.Supplier; /** Expanded view of a bubble when it's part of the bubble bar. */ @@ -92,16 +93,35 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView private boolean mIsOverflow; private BubbleTaskViewHelper mBubbleTaskViewHelper; private BubbleBarMenuViewController mMenuViewController; - private @Nullable Supplier<Rect> mLayerBoundsSupplier; - private @Nullable Listener mListener; + @Nullable + private Supplier<Rect> mLayerBoundsSupplier; + @Nullable + private Listener mListener; private BubbleBarHandleView mHandleView; - private @Nullable TaskView mTaskView; - private @Nullable BubbleOverflowContainerView mOverflowView; + @Nullable + private TaskView mTaskView; + @Nullable + private BubbleOverflowContainerView mOverflowView; + /** + * The handle shown in the caption area is tinted based on the background color of the area. + * This can vary so we sample the caption region and update the handle color based on that. + * If we're showing the overflow, the helper and executors will be null. + */ + @Nullable + private RegionSamplingHelper mRegionSamplingHelper; + @Nullable + private RegionSamplingProvider mRegionSamplingProvider; + @Nullable + private Executor mMainExecutor; + @Nullable + private Executor mBackgroundExecutor; + private final Rect mSampleRect = new Rect(); + private final int[] mLoc = new int[2]; + + /** Height of the caption inset at the top of the TaskView */ private int mCaptionHeight; - - private int mBackgroundColor; /** Corner radius used when view is resting */ private float mRestingCornerRadius = 0f; /** Corner radius applied while dragging */ @@ -116,6 +136,7 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView */ private boolean mIsContentVisible = false; private boolean mIsAnimating; + private boolean mIsDragging; public BubbleBarExpandedView(Context context) { this(context, null); @@ -154,21 +175,20 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView setOnTouchListener((v, event) -> true); } - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - // Hide manage menu when view disappears - mMenuViewController.hideMenu(false /* animated */); - } - /** Initializes the view, must be called before doing anything else. */ public void initialize(BubbleExpandedViewManager expandedViewManager, BubblePositioner positioner, boolean isOverflow, - @Nullable BubbleTaskView bubbleTaskView) { + @Nullable BubbleTaskView bubbleTaskView, + @Nullable Executor mainExecutor, + @Nullable Executor backgroundExecutor, + @Nullable RegionSamplingProvider regionSamplingProvider) { mManager = expandedViewManager; mPositioner = positioner; mIsOverflow = isOverflow; + mMainExecutor = mainExecutor; + mBackgroundExecutor = backgroundExecutor; + mRegionSamplingProvider = regionSamplingProvider; if (mIsOverflow) { mOverflowView = (BubbleOverflowContainerView) LayoutInflater.from(getContext()).inflate( @@ -191,6 +211,7 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView mTaskView.setEnableSurfaceClipping(true); mTaskView.setCornerRadius(mCurrentCornerRadius); mTaskView.setVisibility(VISIBLE); + mTaskView.setCaptionInsets(Insets.of(0, mCaptionHeight, 0, 0)); // Handle view needs to draw on top of task view. bringChildToFront(mHandleView); @@ -245,32 +266,40 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView return mHandleView; } - // TODO (b/275087636): call this when theme/config changes /** Updates the view based on the current theme. */ public void applyThemeAttrs() { + mCaptionHeight = getResources().getDimensionPixelSize( + R.dimen.bubble_bar_expanded_view_caption_height); mRestingCornerRadius = getResources().getDimensionPixelSize( - R.dimen.bubble_bar_expanded_view_corner_radius - ); + R.dimen.bubble_bar_expanded_view_corner_radius); mDraggedCornerRadius = getResources().getDimensionPixelSize( - R.dimen.bubble_bar_expanded_view_corner_radius_dragged - ); + R.dimen.bubble_bar_expanded_view_corner_radius_dragged); mCurrentCornerRadius = mRestingCornerRadius; - final TypedArray ta = mContext.obtainStyledAttributes(new int[]{ - android.R.attr.colorBackgroundFloating}); - mBackgroundColor = ta.getColor(0, Color.WHITE); - ta.recycle(); - mCaptionHeight = getResources().getDimensionPixelSize( - R.dimen.bubble_bar_expanded_view_caption_height); - if (mTaskView != null) { mTaskView.setCornerRadius(mCurrentCornerRadius); - updateHandleColor(true /* animated */); + mTaskView.setCaptionInsets(Insets.of(0, mCaptionHeight, 0, 0)); } } @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + // Hide manage menu when view disappears + mMenuViewController.hideMenu(false /* animated */); + if (mRegionSamplingHelper != null) { + mRegionSamplingHelper.stopAndDestroy(); + } + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + recreateRegionSamplingHelper(); + } + + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); if (mTaskView != null) { @@ -284,16 +313,13 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); if (mTaskView != null) { - mTaskView.layout(l, t, r, - t + mTaskView.getMeasuredHeight()); - mTaskView.setCaptionInsets(Insets.of(0, mCaptionHeight, 0, 0)); + mTaskView.layout(l, t, r, t + mTaskView.getMeasuredHeight()); } } @Override public void onTaskCreated() { setContentVisibility(true); - updateHandleColor(false /* animated */); if (mListener != null) { mListener.onTaskCreated(); } @@ -305,11 +331,70 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView } @Override + public void onTaskRemovalStarted() { + if (mRegionSamplingHelper != null) { + mRegionSamplingHelper.stopAndDestroy(); + } + } + + @Override public void onBackPressed() { if (mListener == null) return; mListener.onBackPressed(); } + /** + * Set whether this view is currently being dragged. + * + * When dragging, the handle is hidden and content shouldn't be sampled. When dragging has + * ended we should start again. + */ + public void setDragging(boolean isDragging) { + if (isDragging != mIsDragging) { + mIsDragging = isDragging; + updateSamplingState(); + } + } + + /** Returns whether region sampling should be enabled, i.e. if task view content is visible. */ + private boolean shouldSampleRegion() { + return mTaskView != null + && mTaskView.getTaskInfo() != null + && !mIsDragging + && !mIsAnimating + && mIsContentVisible; + } + + /** + * Handles starting or stopping the region sampling helper based on + * {@link #shouldSampleRegion()}. + */ + private void updateSamplingState() { + if (mRegionSamplingHelper == null) return; + boolean shouldSample = shouldSampleRegion(); + if (shouldSample) { + mRegionSamplingHelper.start(getCaptionSampleRect()); + } else { + mRegionSamplingHelper.stop(); + } + } + + /** Returns the current area of the caption bar, in screen coordinates. */ + Rect getCaptionSampleRect() { + if (mTaskView == null) return null; + mTaskView.getLocationOnScreen(mLoc); + mSampleRect.set(mLoc[0], mLoc[1], + mLoc[0] + mTaskView.getWidth(), + mLoc[1] + mCaptionHeight); + return mSampleRect; + } + + @VisibleForTesting + @Nullable + public RegionSamplingHelper getRegionSamplingHelper() { + return mRegionSamplingHelper; + } + /** Cleans up the expanded view, should be called when the bubble is no longer active. */ public void cleanUpExpandedState() { mMenuViewController.hideMenu(false /* animated */); @@ -394,27 +479,14 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView if (!mIsAnimating) { mTaskView.setAlpha(visible ? 1f : 0f); + if (mRegionSamplingHelper != null) { + mRegionSamplingHelper.setWindowVisible(visible); + } + updateSamplingState(); } } /** - * Updates the handle color based on the task view status bar or background color; if those - * are transparent it defaults to the background color pulled from system theme attributes. - */ - private void updateHandleColor(boolean animated) { - if (mTaskView == null || mTaskView.getTaskInfo() == null) return; - int color = mBackgroundColor; - ActivityManager.TaskDescription taskDescription = mTaskView.getTaskInfo().taskDescription; - if (taskDescription.getStatusBarColor() != Color.TRANSPARENT) { - color = taskDescription.getStatusBarColor(); - } else if (taskDescription.getBackgroundColor() != Color.TRANSPARENT) { - color = taskDescription.getBackgroundColor(); - } - final boolean isRegionDark = Color.luminance(color) <= 0.5; - mHandleView.updateHandleColor(isRegionDark, animated); - } - - /** * Sets the alpha of both this view and the task view. */ public void setTaskViewAlpha(float alpha) { @@ -442,6 +514,11 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView */ public void setAnimating(boolean animating) { mIsAnimating = animating; + if (mIsAnimating) { + // Stop sampling while animating -- when animating is done setContentVisibility will + // re-trigger sampling if we're visible. + updateSamplingState(); + } // If we're done animating, apply the correct visibility. if (!animating) { setContentVisibility(mIsContentVisible); @@ -481,6 +558,37 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView } } + private void recreateRegionSamplingHelper() { + if (mRegionSamplingHelper != null) { + mRegionSamplingHelper.stopAndDestroy(); + } + if (mMainExecutor == null || mBackgroundExecutor == null + || mRegionSamplingProvider == null) { + // Null when it's the overflow / don't need sampling then. + return; + } + mRegionSamplingHelper = mRegionSamplingProvider.createHelper(this, + new RegionSamplingHelper.SamplingCallback() { + @Override + public void onRegionDarknessChanged(boolean isRegionDark) { + if (mHandleView != null) { + mHandleView.updateHandleColor(isRegionDark, + true /* animated */); + } + } + + @Override + public Rect getSampledRegion(View sampledView) { + return getCaptionSampleRect(); + } + + @Override + public boolean isSamplingEnabled() { + return shouldSampleRegion(); + } + }, mMainExecutor, mBackgroundExecutor); + } + private class HandleViewAccessibilityDelegate extends AccessibilityDelegate { @Override public void onInitializeAccessibilityNodeInfo(@NonNull View host, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java index c91567d7d8be..e781c07f01a7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java @@ -42,7 +42,9 @@ public class BubbleBarHandleView extends View { private final @ColorInt int mHandleLightColor; private final @ColorInt int mHandleDarkColor; - private @Nullable ObjectAnimator mColorChangeAnim; + private @ColorInt int mCurrentColor; + @Nullable + private ObjectAnimator mColorChangeAnim; public BubbleBarHandleView(Context context) { this(context, null /* attrs */); @@ -88,13 +90,17 @@ public class BubbleBarHandleView extends View { * * @param isRegionDark Whether the background behind the handle is dark, and thus the handle * should be light (and vice versa). - * @param animated Whether to animate the change, or apply it immediately. + * @param animated Whether to animate the change, or apply it immediately. */ public void updateHandleColor(boolean isRegionDark, boolean animated) { int newColor = isRegionDark ? mHandleLightColor : mHandleDarkColor; + if (newColor == mCurrentColor) { + return; + } if (mColorChangeAnim != null) { mColorChangeAnim.cancel(); } + mCurrentColor = newColor; if (animated) { mColorChangeAnim = ObjectAnimator.ofArgb(this, "backgroundColor", newColor); mColorChangeAnim.addListener(new AnimatorListenerAdapter() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index 02ecfd983d73..7054c17cfeb0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -38,6 +38,7 @@ import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.activityembedding.ActivityEmbeddingController; import com.android.wm.shell.apptoweb.AppToWebGenericLinksParser; +import com.android.wm.shell.apptoweb.AssistContentRequester; import com.android.wm.shell.bubbles.BubbleController; import com.android.wm.shell.bubbles.BubbleData; import com.android.wm.shell.bubbles.BubbleDataRepository; @@ -240,6 +241,7 @@ public abstract class WMShellModule { RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, InteractionJankMonitor interactionJankMonitor, AppToWebGenericLinksParser genericLinksParser, + AssistContentRequester assistContentRequester, MultiInstanceHelper multiInstanceHelper, Optional<DesktopTasksLimiter> desktopTasksLimiter, Optional<DesktopActivityOrientationChangeHandler> desktopActivityOrientationHandler) { @@ -263,6 +265,7 @@ public abstract class WMShellModule { rootTaskDisplayAreaOrganizer, interactionJankMonitor, genericLinksParser, + assistContentRequester, multiInstanceHelper, desktopTasksLimiter, desktopActivityOrientationHandler); @@ -291,6 +294,15 @@ public abstract class WMShellModule { return new AppToWebGenericLinksParser(context, mainExecutor); } + @Provides + static AssistContentRequester provideAssistContentRequester( + Context context, + @ShellMainThread ShellExecutor shellExecutor, + @ShellBackgroundThread ShellExecutor bgExecutor + ) { + return new AssistContentRequester(context, shellExecutor, bgExecutor); + } + // // Freeform // diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java deleted file mode 100644 index 1dad4137a9df..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.splitscreen; - -import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN; - -import android.content.Context; -import android.view.SurfaceSession; -import android.window.WindowContainerToken; -import android.window.WindowContainerTransaction; - -import com.android.internal.protolog.ProtoLog; -import com.android.launcher3.icons.IconProvider; -import com.android.wm.shell.ShellTaskOrganizer; -import com.android.wm.shell.common.SyncTransactionQueue; -import com.android.wm.shell.windowdecor.WindowDecorViewModel; - -import java.util.Optional; - -/** - * Main stage for split-screen mode. When split-screen is active all standard activity types launch - * on the main stage, except for task that are explicitly pinned to the {@link StageTaskListener}. - * @see StageCoordinator - */ -class MainStage extends StageTaskListener { - private boolean mIsActive = false; - - MainStage(Context context, ShellTaskOrganizer taskOrganizer, int displayId, - StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue, - SurfaceSession surfaceSession, IconProvider iconProvider, - Optional<WindowDecorViewModel> windowDecorViewModel) { - super(context, taskOrganizer, displayId, callbacks, syncQueue, surfaceSession, - iconProvider, windowDecorViewModel); - } - - boolean isActive() { - return mIsActive; - } - - void activate(WindowContainerTransaction wct, boolean includingTopTask) { - if (mIsActive) return; - ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "activate: main stage includingTopTask=%b", - includingTopTask); - - if (includingTopTask) { - reparentTopTask(wct); - } - - mIsActive = true; - } - - void deactivate(WindowContainerTransaction wct) { - deactivate(wct, false /* toTop */); - } - - void deactivate(WindowContainerTransaction wct, boolean toTop) { - if (!mIsActive) return; - ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "deactivate: main stage toTop=%b rootTaskInfo=%s", - toTop, mRootTaskInfo); - mIsActive = false; - - if (mRootTaskInfo == null) return; - final WindowContainerToken rootToken = mRootTaskInfo.token; - wct.reparentTasks( - rootToken, - null /* newParent */, - null /* windowingModes */, - null /* activityTypes */, - toTop); - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java index 526c1d4a179d..b36b1f84d21f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java @@ -44,7 +44,7 @@ public interface SplitScreen { int STAGE_TYPE_UNDEFINED = -1; /** * The main stage type. - * @see MainStage + * @see StageTaskListener */ int STAGE_TYPE_MAIN = 0; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index f3959cca050b..f3113dce94a4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -34,6 +34,7 @@ import static android.view.WindowManager.transitTypeToString; import static android.window.TransitionInfo.FLAG_IS_DISPLAY; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER; +import static com.android.wm.shell.Flags.enableFlexibleSplit; import static com.android.wm.shell.common.split.SplitLayout.PARALLAX_ALIGN_CENTER; import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition; import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage; @@ -154,14 +155,12 @@ import java.util.Set; import java.util.concurrent.Executor; /** - * Coordinates the staging (visibility, sizing, ...) of the split-screen {@link MainStage} and - * other stages. + * Coordinates the staging (visibility, sizing, ...) of the split-screen stages. * Some high-level rules: * - The {@link StageCoordinator} is only considered active if the other stages contain at * least one child task. - * - The {@link MainStage} should only have children if the coordinator is active. - * - The {@link SplitLayout} divider is only visible if both the {@link MainStage} - * and other stages are visible. + * - The {@link SplitLayout} divider is only visible if multiple {@link StageTaskListener}s are + * visible * - Both stages are put under a single-top root task. * This rules are mostly implemented in {@link #onStageVisibilityChanged(StageListenerImpl)} and * {@link #onStageHasChildrenChanged(StageListenerImpl).} @@ -174,7 +173,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, private final SurfaceSession mSurfaceSession = new SurfaceSession(); - private final MainStage mMainStage; + private final StageTaskListener mMainStage; private final StageListenerImpl mMainStageListener = new StageListenerImpl(); private final StageTaskListener mSideStage; private final StageListenerImpl mSideStageListener = new StageListenerImpl(); @@ -329,7 +328,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, taskOrganizer.createRootTask(displayId, WINDOWING_MODE_FULLSCREEN, this /* listener */); ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Creating main/side root task"); - mMainStage = new MainStage( + mMainStage = new StageTaskListener( mContext, mTaskOrganizer, mDisplayId, @@ -367,8 +366,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, @VisibleForTesting StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue, - ShellTaskOrganizer taskOrganizer, MainStage mainStage, StageTaskListener sideStage, - DisplayController displayController, DisplayImeController displayImeController, + ShellTaskOrganizer taskOrganizer, StageTaskListener mainStage, + StageTaskListener sideStage, DisplayController displayController, + DisplayImeController displayImeController, DisplayInsetsController displayInsetsController, SplitLayout splitLayout, Transitions transitions, TransactionPool transactionPool, ShellExecutor mainExecutor, Handler mainHandler, Optional<RecentTasksController> recentTasks, @@ -420,6 +420,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return mSideStageListener.mVisible && mMainStageListener.mVisible; } + private void activateSplit(WindowContainerTransaction wct, boolean includingTopTask) { + mMainStage.activate(wct, includingTopTask); + } + public boolean isSplitActive() { return mMainStage.isActive(); } @@ -505,10 +509,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "removeFromSideStage: task=%d", taskId); final WindowContainerTransaction wct = new WindowContainerTransaction(); - /** - * {@link MainStage} will be deactivated in {@link #onStageHasChildrenChanged} if the - * other stages no longer have children. - */ + + // MainStage will be deactivated in onStageHasChildrenChanged() if the other stages + // no longer have children. + final boolean result = mSideStage.removeTask(taskId, isSplitActive() ? mMainStage.mRootTaskInfo.token : null, wct); @@ -805,7 +809,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (!isSplitActive()) { // Build a request WCT that will launch both apps such that task 0 is on the main stage // while task 1 is on the side stage. - mMainStage.activate(wct, false /* reparent */); + activateSplit(wct, false /* reparentToTop */); } mSplitLayout.setDivideRatio(snapPosition); updateWindowBounds(mSplitLayout, wct); @@ -872,7 +876,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (!isSplitActive()) { // Build a request WCT that will launch both apps such that task 0 is on the main stage // while task 1 is on the side stage. - mMainStage.activate(wct, false /* reparent */); + activateSplit(wct, false /* reparentToTop */); } setSideStagePosition(splitPosition, wct); @@ -1439,7 +1443,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, setSideStagePosition(startPosition, wct); mSideStage.addTask(taskInfo, wct); } - mMainStage.activate(wct, true /* includingTopTask */); + activateSplit(wct, true /* reparentToTop */); prepareSplitLayout(wct, resizeAnim); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java index 459355305280..29b5114d87e6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java @@ -72,6 +72,10 @@ import java.util.function.Predicate; public class StageTaskListener implements ShellTaskOrganizer.TaskListener { private static final String TAG = StageTaskListener.class.getSimpleName(); + // No current way to enforce this but if enableFlexibleSplit() is enabled, then only 1 of the + // stages should have this be set/being used + private boolean mIsActive; + /** Callback interface for listening to changes in a split-screen stage. */ public interface StageListenerCallbacks { void onRootTaskAppeared(); @@ -475,6 +479,44 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener { }); } + // --------- + // Previously only used in MainStage + boolean isActive() { + return mIsActive; + } + + void activate(WindowContainerTransaction wct, boolean includingTopTask) { + if (mIsActive) return; + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "activate: includingTopTask=%b", + includingTopTask); + + if (includingTopTask) { + reparentTopTask(wct); + } + + mIsActive = true; + } + + void deactivate(WindowContainerTransaction wct) { + deactivate(wct, false /* toTop */); + } + + void deactivate(WindowContainerTransaction wct, boolean toTop) { + if (!mIsActive) return; + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "deactivate: toTop=%b rootTaskInfo=%s", + toTop, mRootTaskInfo); + mIsActive = false; + + if (mRootTaskInfo == null) return; + final WindowContainerToken rootToken = mRootTaskInfo.token; + wct.reparentTasks( + rootToken, + null /* newParent */, + null /* windowingModes */, + null /* activityTypes */, + toTop); + } + // -------- // Previously only used in SideStage boolean removeAllTasks(WindowContainerTransaction wct, boolean toTop) { 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 c88c1e28b011..79190689adc1 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 @@ -90,6 +90,7 @@ import com.android.wm.shell.R; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.apptoweb.AppToWebGenericLinksParser; +import com.android.wm.shell.apptoweb.AssistContentRequester; import com.android.wm.shell.common.DisplayChangeController; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayInsetsController; @@ -182,6 +183,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { private final Region mExclusionRegion = Region.obtain(); private boolean mInImmersiveMode; private final String mSysUIPackageName; + private final AssistContentRequester mAssistContentRequester; private final DisplayChangeController.OnDisplayChangingListener mOnDisplayChangingListener; private final ISystemGestureExclusionListener mGestureExclusionListener = @@ -217,6 +219,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, InteractionJankMonitor interactionJankMonitor, AppToWebGenericLinksParser genericLinksParser, + AssistContentRequester assistContentRequester, MultiInstanceHelper multiInstanceHelper, Optional<DesktopTasksLimiter> desktopTasksLimiter, Optional<DesktopActivityOrientationChangeHandler> activityOrientationChangeHandler @@ -238,6 +241,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { transitions, desktopTasksController, genericLinksParser, + assistContentRequester, multiInstanceHelper, new DesktopModeWindowDecoration.Factory(), new InputMonitorFactory(), @@ -267,6 +271,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { Transitions transitions, Optional<DesktopTasksController> desktopTasksController, AppToWebGenericLinksParser genericLinksParser, + AssistContentRequester assistContentRequester, MultiInstanceHelper multiInstanceHelper, DesktopModeWindowDecoration.Factory desktopModeWindowDecorFactory, InputMonitorFactory inputMonitorFactory, @@ -304,6 +309,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mInteractionJankMonitor = interactionJankMonitor; mDesktopTasksLimiter = desktopTasksLimiter; mActivityOrientationChangeHandler = activityOrientationChangeHandler; + mAssistContentRequester = assistContentRequester; mOnDisplayChangingListener = (displayId, fromRotation, toRotation, displayAreaInfo, t) -> { DesktopModeWindowDecoration decoration; RunningTaskInfo taskInfo; @@ -626,7 +632,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } else if (id == R.id.caption_handle || id == R.id.open_menu_button) { if (!decoration.isHandleMenuActive()) { moveTaskToFront(decoration.mTaskInfo); - decoration.createHandleMenu(mSplitScreenController); + decoration.createHandleMenu(); } } else if (id == R.id.maximize_window) { // TODO(b/346441962): move click detection logic into the decor's @@ -1270,6 +1276,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mSyncQueue, mRootTaskDisplayAreaOrganizer, mGenericLinksParser, + mAssistContentRequester, mMultiInstanceHelper); mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java index aa43c8dab2ad..142be91fe942 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java @@ -38,6 +38,7 @@ import android.annotation.Nullable; import android.annotation.SuppressLint; import android.app.ActivityManager; import android.app.WindowConfiguration.WindowingMode; +import android.app.assist.AssistContent; import android.content.ComponentName; import android.content.Context; import android.content.pm.ActivityInfo; @@ -75,6 +76,8 @@ import com.android.wm.shell.R; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.apptoweb.AppToWebGenericLinksParser; +import com.android.wm.shell.apptoweb.AppToWebUtils; +import com.android.wm.shell.apptoweb.AssistContentRequester; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.MultiInstanceHelper; @@ -150,6 +153,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin private CharSequence mAppName; private CapturedLink mCapturedLink; private Uri mGenericLink; + private Uri mWebUri; private Consumer<Uri> mOpenInBrowserClickListener; private ExclusionRegionListener mExclusionRegionListener; @@ -158,6 +162,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin private final MaximizeMenuFactory mMaximizeMenuFactory; private final HandleMenuFactory mHandleMenuFactory; private final AppToWebGenericLinksParser mGenericLinksParser; + private final AssistContentRequester mAssistContentRequester; // Hover state for the maximize menu and button. The menu will remain open as long as either of // these is true. See {@link #onMaximizeHoverStateChanged()}. @@ -184,16 +189,16 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin SyncTransactionQueue syncQueue, RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, AppToWebGenericLinksParser genericLinksParser, + AssistContentRequester assistContentRequester, MultiInstanceHelper multiInstanceHelper) { this (context, userContext, displayController, splitScreenController, taskOrganizer, taskInfo, taskSurface, handler, bgExecutor, choreographer, syncQueue, - rootTaskDisplayAreaOrganizer, genericLinksParser, SurfaceControl.Builder::new, - SurfaceControl.Transaction::new, WindowContainerTransaction::new, - SurfaceControl::new, new WindowManagerWrapper( + rootTaskDisplayAreaOrganizer, genericLinksParser, assistContentRequester, + SurfaceControl.Builder::new, SurfaceControl.Transaction::new, + WindowContainerTransaction::new, SurfaceControl::new, new WindowManagerWrapper( context.getSystemService(WindowManager.class)), - new SurfaceControlViewHostFactory() {}, - DefaultMaximizeMenuFactory.INSTANCE, DefaultHandleMenuFactory.INSTANCE, - multiInstanceHelper); + new SurfaceControlViewHostFactory() {}, DefaultMaximizeMenuFactory.INSTANCE, + DefaultHandleMenuFactory.INSTANCE, multiInstanceHelper); } DesktopModeWindowDecoration( @@ -210,6 +215,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin SyncTransactionQueue syncQueue, RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, AppToWebGenericLinksParser genericLinksParser, + AssistContentRequester assistContentRequester, Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier, Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier, Supplier<WindowContainerTransaction> windowContainerTransactionSupplier, @@ -230,6 +236,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mSyncQueue = syncQueue; mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer; mGenericLinksParser = genericLinksParser; + mAssistContentRequester = assistContentRequester; mMaximizeMenuFactory = maximizeMenuFactory; mHandleMenuFactory = handleMenuFactory; mMultiInstanceHelper = multiInstanceHelper; @@ -478,10 +485,18 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin @Nullable private Uri getBrowserLink() { + // Do not show browser link in browser applications + final ComponentName baseActivity = mTaskInfo.baseActivity; + if (baseActivity != null && AppToWebUtils.isBrowserApp(mContext, + baseActivity.getPackageName(), mUserContext.getUserId())) { + return null; + } // If the captured link is available and has not expired, return the captured link. // Otherwise, return the generic link which is set to null if a generic link is unavailable. if (mCapturedLink != null && !mCapturedLink.mExpired) { return mCapturedLink.mUri; + } else if (mWebUri != null) { + return mWebUri; } return mGenericLink; } @@ -987,18 +1002,32 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin } /** - * Create and display handle menu window. + * Updates app info and creates and displays handle menu window. */ - void createHandleMenu(SplitScreenController splitScreenController) { + void createHandleMenu() { + // Requests assist content. When content is received, calls {@link #onAssistContentReceived} + // which sets app info and creates the handle menu. + mAssistContentRequester.requestAssistContent( + mTaskInfo.taskId, this::onAssistContentReceived); + } + + /** + * Called when assist content is received. updates the saved links and creates the handle menu. + */ + @VisibleForTesting + void onAssistContentReceived(@Nullable AssistContent assistContent) { + mWebUri = assistContent == null ? null : assistContent.getWebUri(); loadAppInfoIfNeeded(); updateGenericLink(); + + // Create and display handle menu mHandleMenu = mHandleMenuFactory.create( this, mWindowManagerWrapper, mRelayoutParams.mLayoutResId, mAppIconBitmap, mAppName, - splitScreenController, + mSplitScreenController, DesktopModeStatus.canEnterDesktopMode(mContext), Flags.enableDesktopWindowingMultiInstanceFeatures() && mMultiInstanceHelper @@ -1012,6 +1041,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mHandleMenu.show( /* onToDesktopClickListener= */ () -> { mOnToDesktopClickListener.accept(APP_HANDLE_MENU_BUTTON); + mOnToDesktopClickListener.accept(APP_HANDLE_MENU_BUTTON); return Unit.INSTANCE; }, /* onToFullscreenClickListener= */ mOnToFullscreenClickListener, @@ -1333,6 +1363,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin SyncTransactionQueue syncQueue, RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, AppToWebGenericLinksParser genericLinksParser, + AssistContentRequester assistContentRequester, MultiInstanceHelper multiInstanceHelper) { return new DesktopModeWindowDecoration( context, @@ -1348,6 +1379,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin syncQueue, rootTaskDisplayAreaOrganizer, genericLinksParser, + assistContentRequester, multiInstanceHelper); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt index dfa5ab415992..9ef4b8cde8ef 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt @@ -29,6 +29,7 @@ import android.view.View.OnClickListener import android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS import android.view.WindowManager import android.widget.ImageButton +import com.android.internal.policy.SystemBarUtils import com.android.window.flags.Flags import com.android.wm.shell.R import com.android.wm.shell.shared.animation.Interpolators @@ -74,7 +75,10 @@ internal class AppHandleViewHolder( ) { captionHandle.imageTintList = ColorStateList.valueOf(getCaptionHandleBarColor(taskInfo)) this.taskInfo = taskInfo - if (!isCaptionVisible && hasStatusBarInputLayer()) { + // If handle is not in status bar region(i.e., bottom stage in vertical split), + // do not create an input layer + if (position.y >= SystemBarUtils.getStatusBarHeight(context)) return + if (!isCaptionVisible && hasStatusBarInputLayer() ) { disposeStatusBarInputLayer() return } @@ -120,7 +124,7 @@ internal class AppHandleViewHolder( inputManager.pilferPointers(v.viewRootImpl.inputToken) } captionHandle.dispatchTouchEvent(event) - true + return@setOnTouchListener true } windowManagerWrapper.updateViewLayout(view, lp) } diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt index ec1d4f7854fd..7640cb1fb616 100644 --- a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt @@ -195,6 +195,25 @@ class DesktopModeFlickerScenarios { .associateBy({ it }, { AssertionInvocationGroup.BLOCKING }), ) + val CORNER_RESIZE_TO_MAXIMUM_SIZE = + FlickerConfigEntry( + scenarioId = ScenarioId("CORNER_RESIZE_TO_MAXIMUM_SIZE"), + extractor = + TaggedScenarioExtractorBuilder() + .setTargetTag(CujType.CUJ_DESKTOP_MODE_RESIZE_WINDOW) + .setTransitionMatcher( + TaggedCujTransitionMatcher(associatedTransitionRequired = false) + ) + .build(), + assertions = + AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS + + listOf( + AppLayerIncreasesInSize(DESKTOP_MODE_APP), + AppWindowHasMaxDisplayHeight(DESKTOP_MODE_APP), + AppWindowHasMaxDisplayWidth(DESKTOP_MODE_APP) + ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }), + ) + val SNAP_RESIZE_LEFT_WITH_BUTTON = FlickerConfigEntry( scenarioId = ScenarioId("SNAP_RESIZE_LEFT_WITH_BUTTON"), diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ResizeAppToMaximumWindowSizeLandscape.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ResizeAppToMaximumWindowSizeLandscape.kt new file mode 100644 index 000000000000..0b98ba2a9cd4 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ResizeAppToMaximumWindowSizeLandscape.kt @@ -0,0 +1,52 @@ +/* + * 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.flicker + +import android.tools.Rotation +import android.tools.flicker.FlickerConfig +import android.tools.flicker.annotation.ExpectedScenarios +import android.tools.flicker.annotation.FlickerConfigProvider +import android.tools.flicker.config.FlickerConfig +import android.tools.flicker.config.FlickerServiceConfig +import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.CORNER_RESIZE_TO_MAXIMUM_SIZE +import com.android.wm.shell.scenarios.ResizeAppWithCornerResize +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Resize app window using corner resize to the greatest possible height and width in + * landscape mode. + * + * Assert that the maximum window size constraint is maintained. + */ +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class ResizeAppToMaximumWindowSizeLandscape : ResizeAppWithCornerResize( + rotation = Rotation.ROTATION_90 +) { + @ExpectedScenarios(["CORNER_RESIZE_TO_MAXIMUM_SIZE"]) + @Test + override fun resizeAppWithCornerResizeToMaximumSize() = + super.resizeAppWithCornerResizeToMaximumSize() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(CORNER_RESIZE_TO_MAXIMUM_SIZE) + } +} diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ResizeAppToMaximumWindowSizePortrait.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ResizeAppToMaximumWindowSizePortrait.kt new file mode 100644 index 000000000000..b1c04d38a46c --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ResizeAppToMaximumWindowSizePortrait.kt @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker + +import android.tools.flicker.FlickerConfig +import android.tools.flicker.annotation.ExpectedScenarios +import android.tools.flicker.annotation.FlickerConfigProvider +import android.tools.flicker.config.FlickerConfig +import android.tools.flicker.config.FlickerServiceConfig +import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.CORNER_RESIZE_TO_MAXIMUM_SIZE +import com.android.wm.shell.scenarios.ResizeAppWithCornerResize +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Resize app window using corner resize to the greatest possible height and width in + * portrait mode. + * + * Assert that the maximum window size constraint is maintained. + */ +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class ResizeAppToMaximumWindowSizePortrait : ResizeAppWithCornerResize() { + @ExpectedScenarios(["CORNER_RESIZE_TO_MAXIMUM_SIZE"]) + @Test + override fun resizeAppWithCornerResizeToMaximumSize() = + super.resizeAppWithCornerResizeToMaximumSize() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(CORNER_RESIZE_TO_MAXIMUM_SIZE) + } +} diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/CloseAllAppsWithAppHeaderExitTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/CloseAllAppsWithAppHeaderExitTest.kt new file mode 100644 index 000000000000..a4dc52beb85d --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/CloseAllAppsWithAppHeaderExitTest.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. + */ + +package com.android.wm.shell.functional + +import android.platform.test.annotations.Postsubmit +import com.android.wm.shell.scenarios.CloseAllAppsWithAppHeaderExit +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +/* Functional test for [CloseAllAppsWithAppHeaderExit]. */ +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +class CloseAllAppsWithAppHeaderExitTest() : CloseAllAppsWithAppHeaderExit() diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/EnterDesktopWithDragTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/EnterDesktopWithDragTest.kt new file mode 100644 index 000000000000..3d95f97c09ef --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/EnterDesktopWithDragTest.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. + */ + +package com.android.wm.shell.functional + +import android.platform.test.annotations.Postsubmit +import com.android.wm.shell.scenarios.EnterDesktopWithDrag +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +/* Functional test for [EnterDesktopWithDrag]. */ +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +class EnterDesktopWithDragTest : EnterDesktopWithDrag() diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/ExitDesktopWithDragToTopDragZoneTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/ExitDesktopWithDragToTopDragZoneTest.kt new file mode 100644 index 000000000000..140c5ec15812 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/ExitDesktopWithDragToTopDragZoneTest.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. + */ + +package com.android.wm.shell.functional + +import android.platform.test.annotations.Postsubmit +import com.android.wm.shell.scenarios.ExitDesktopWithDragToTopDragZone +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +/* Functional test for [ExitDesktopWithDragToTopDragZone]. */ +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +class ExitDesktopWithDragToTopDragZoneTest : ExitDesktopWithDragToTopDragZone() diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/MaximizeAppWindowTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/MaximizeAppWindowTest.kt new file mode 100644 index 000000000000..3d3dcd09cc63 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/MaximizeAppWindowTest.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. + */ + +package com.android.wm.shell.functional + +import android.platform.test.annotations.Postsubmit +import com.android.wm.shell.scenarios.MaximizeAppWindow +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +/* Functional test for [MaximizeAppWindow]. */ +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +class MaximizeAppWindowTest : MaximizeAppWindow() diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/OpenAppsInDesktopModeTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/OpenAppsInDesktopModeTest.kt new file mode 100644 index 000000000000..263e89f69e5a --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/OpenAppsInDesktopModeTest.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. + */ + +package com.android.wm.shell.functional + +import android.platform.test.annotations.Postsubmit +import com.android.wm.shell.scenarios.OpenAppsInDesktopMode +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +/* Functional test for [OpenAppsInDesktopMode]. */ +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +class OpenAppsInDesktopModeTest : OpenAppsInDesktopMode() diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/ResizeAppCornerMultiWindowAndPipTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/ResizeAppCornerMultiWindowAndPipTest.kt new file mode 100644 index 000000000000..13f4775c1074 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/ResizeAppCornerMultiWindowAndPipTest.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. + */ + +package com.android.wm.shell.functional + +import android.platform.test.annotations.Postsubmit +import com.android.wm.shell.scenarios.ResizeAppCornerMultiWindowAndPip +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +/* Functional test for [ResizeAppCornerMultiWindowAndPip]. */ +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +class ResizeAppCornerMultiWindowAndPipTest : ResizeAppCornerMultiWindowAndPip() diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/ResizeAppCornerMultiWindowTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/ResizeAppCornerMultiWindowTest.kt new file mode 100644 index 000000000000..bc9bb41bf320 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/ResizeAppCornerMultiWindowTest.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. + */ + +package com.android.wm.shell.functional + +import android.platform.test.annotations.Postsubmit +import com.android.wm.shell.scenarios.ResizeAppCornerMultiWindow +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +/* Functional test for [ResizeAppCornerMultiWindow]. */ +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +class ResizeAppCornerMultiWindowTest : ResizeAppCornerMultiWindow() diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/ResizeAppWithCornerResizeTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/ResizeAppWithCornerResizeTest.kt new file mode 100644 index 000000000000..46168eb7c002 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/ResizeAppWithCornerResizeTest.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. + */ + +package com.android.wm.shell.functional + +import android.platform.test.annotations.Postsubmit +import com.android.wm.shell.scenarios.ResizeAppWithCornerResize +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +/* Functional test for [ResizeAppWithCornerResize]. */ +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +class ResizeAppWithCornerResizeTest : ResizeAppWithCornerResize() diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/ResizeAppWithEdgeResizeTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/ResizeAppWithEdgeResizeTest.kt new file mode 100644 index 000000000000..ee2420021339 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/ResizeAppWithEdgeResizeTest.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.wm.shell.functional + +import android.platform.test.annotations.Postsubmit +import com.android.server.wm.flicker.helpers.MotionEventHelper +import com.android.wm.shell.scenarios.ResizeAppWithEdgeResize +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +/* Functional test for [ResizeAppWithEdgeResize]. */ +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +class ResizeAppWithEdgeResizeTest : + ResizeAppWithEdgeResize(MotionEventHelper.InputMethod.TOUCHPAD) diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/SnapResizeAppWindowWithButtonTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/SnapResizeAppWindowWithButtonTest.kt new file mode 100644 index 000000000000..38e85c755481 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/SnapResizeAppWindowWithButtonTest.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. + */ + +package com.android.wm.shell.functional + +import android.platform.test.annotations.Postsubmit +import com.android.wm.shell.scenarios.SnapResizeAppWindowWithButton +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +/* Functional test for [SnapResizeAppWindowWithButton]. */ +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +class SnapResizeAppWindowWithButtonTest : SnapResizeAppWindowWithButton() diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/SnapResizeAppWindowWithDragTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/SnapResizeAppWindowWithDragTest.kt new file mode 100644 index 000000000000..082a3fb0e171 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/SnapResizeAppWindowWithDragTest.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. + */ + +package com.android.wm.shell.functional + +import android.platform.test.annotations.Postsubmit +import com.android.wm.shell.scenarios.SnapResizeAppWindowWithDrag +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +/* Functional test for [SnapResizeAppWindowWithDrag]. */ +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +class SnapResizeAppWindowWithDragTest : SnapResizeAppWindowWithDrag() diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/SwitchToOverviewFromDesktopTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/SwitchToOverviewFromDesktopTest.kt new file mode 100644 index 000000000000..fdd0d8144130 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/SwitchToOverviewFromDesktopTest.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. + */ + +package com.android.wm.shell.functional + +import android.platform.test.annotations.Postsubmit +import com.android.wm.shell.scenarios.SwitchToOverviewFromDesktop +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +/* Functional test for [SwitchToOverviewFromDesktop]. */ +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +class SwitchToOverviewFromDesktopTest : SwitchToOverviewFromDesktop() diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/CloseAllAppsWithAppHeaderExit.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/CloseAllAppsWithAppHeaderExit.kt index e9056f3c44d4..351a70094654 100644 --- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/CloseAllAppsWithAppHeaderExit.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/CloseAllAppsWithAppHeaderExit.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.scenarios -import android.platform.test.annotations.Postsubmit import android.app.Instrumentation import android.tools.NavBar import android.tools.Rotation @@ -33,15 +32,12 @@ import com.android.wm.shell.Utils import org.junit.After import org.junit.Assume import org.junit.Before +import org.junit.Ignore import org.junit.Rule import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.BlockJUnit4ClassRunner -@RunWith(BlockJUnit4ClassRunner::class) -@Postsubmit -open class CloseAllAppsWithAppHeaderExit -@JvmOverloads +@Ignore("Base Test Class") +abstract class CloseAllAppsWithAppHeaderExit constructor(val rotation: Rotation = Rotation.ROTATION_0) { private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowMultiWindow.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowMultiWindow.kt index ca1dc1a7f658..3f9927f1fab6 100644 --- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowMultiWindow.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowMultiWindow.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.scenarios -import android.platform.test.annotations.Postsubmit import com.android.server.wm.flicker.helpers.DesktopModeAppHelper import com.android.server.wm.flicker.helpers.ImeAppHelper import com.android.server.wm.flicker.helpers.MailAppHelper @@ -26,13 +25,11 @@ import com.android.window.flags.Flags import org.junit.After import org.junit.Assume import org.junit.Before +import org.junit.Ignore import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.BlockJUnit4ClassRunner -@RunWith(BlockJUnit4ClassRunner::class) -@Postsubmit -open class DragAppWindowMultiWindow : DragAppWindowScenarioTestBase() +@Ignore("Test Base Class") +abstract class DragAppWindowMultiWindow : DragAppWindowScenarioTestBase() { private val imeAppHelper = ImeAppHelper(instrumentation) private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation)) 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 f4d641411114..967bd29958c2 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 @@ -16,7 +16,6 @@ package com.android.wm.shell.scenarios -import android.platform.test.annotations.Postsubmit import android.tools.NavBar import android.tools.Rotation import android.tools.flicker.rules.ChangeDisplayOrientationRule @@ -25,19 +24,16 @@ import com.android.wm.shell.Utils import org.junit.After import org.junit.Assume import org.junit.Before +import org.junit.Ignore import org.junit.Rule import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.BlockJUnit4ClassRunner -@RunWith(BlockJUnit4ClassRunner::class) -@Postsubmit -open class EnterDesktopWithDrag -@JvmOverloads +@Ignore("Test Base Class") +abstract class EnterDesktopWithDrag constructor( val rotation: Rotation = Rotation.ROTATION_0, isResizeable: Boolean = true, - isLandscapeApp: 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 b616e5347ac9..824c4482c1e6 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 @@ -16,7 +16,6 @@ package com.android.wm.shell.scenarios -import android.platform.test.annotations.Postsubmit import android.tools.NavBar import android.tools.Rotation import com.android.window.flags.Flags @@ -24,19 +23,16 @@ import com.android.wm.shell.Utils import org.junit.After import org.junit.Assume import org.junit.Before +import org.junit.Ignore import org.junit.Rule import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.BlockJUnit4ClassRunner -@RunWith(BlockJUnit4ClassRunner::class) -@Postsubmit -open class ExitDesktopWithDragToTopDragZone -@JvmOverloads +@Ignore("Test Base Class") +abstract class ExitDesktopWithDragToTopDragZone constructor( val rotation: Rotation = Rotation.ROTATION_0, isResizeable: Boolean = true, - isLandscapeApp: 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/OpenAppsInDesktopMode.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenAppsInDesktopMode.kt index a5c794b06c4a..aad266fb8374 100644 --- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenAppsInDesktopMode.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenAppsInDesktopMode.kt @@ -17,7 +17,6 @@ package com.android.wm.shell.scenarios import android.app.Instrumentation -import android.platform.test.annotations.Postsubmit import android.tools.flicker.rules.ChangeDisplayOrientationRule import android.tools.NavBar import android.tools.Rotation @@ -36,14 +35,12 @@ import com.android.wm.shell.Utils import org.junit.After import org.junit.Assume import org.junit.Before +import org.junit.Ignore import org.junit.Rule import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.BlockJUnit4ClassRunner -@RunWith(BlockJUnit4ClassRunner::class) -@Postsubmit -open class OpenAppsInDesktopMode(val rotation: Rotation = Rotation.ROTATION_0) { +@Ignore("Test Base Class") +abstract class OpenAppsInDesktopMode(val rotation: Rotation = Rotation.ROTATION_0) { private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() private val tapl = LauncherInstrumentation() @@ -83,4 +80,4 @@ open class OpenAppsInDesktopMode(val rotation: Rotation = Rotation.ROTATION_0) { secondApp.exit(wmHelper) firstApp.exit(wmHelper) } -}
\ No newline at end of file +} diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindow.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindow.kt index b6bca7a94287..bfee3181cbc0 100644 --- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindow.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindow.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.scenarios -import android.platform.test.annotations.Postsubmit import android.app.Instrumentation import android.tools.NavBar import android.tools.Rotation @@ -34,15 +33,12 @@ import com.android.wm.shell.Utils import org.junit.After import org.junit.Assume import org.junit.Before +import org.junit.Ignore import org.junit.Rule import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.BlockJUnit4ClassRunner -@RunWith(BlockJUnit4ClassRunner::class) -@Postsubmit -open class ResizeAppCornerMultiWindow -@JvmOverloads +@Ignore("Test Base Class") +abstract class ResizeAppCornerMultiWindow constructor(val rotation: Rotation = Rotation.ROTATION_0, val horizontalChange: Int = 50, val verticalChange: Int = -50) { diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindowAndPip.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindowAndPip.kt index 285ea13bb9d4..5b1b64e7c562 100644 --- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindowAndPip.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindowAndPip.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.scenarios -import android.platform.test.annotations.Postsubmit import android.app.Instrumentation import android.tools.NavBar import android.tools.Rotation @@ -35,15 +34,12 @@ import com.android.wm.shell.Utils import org.junit.After import org.junit.Assume import org.junit.Before +import org.junit.Ignore import org.junit.Rule import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.BlockJUnit4ClassRunner -@RunWith(BlockJUnit4ClassRunner::class) -@Postsubmit -open class ResizeAppCornerMultiWindowAndPip -@JvmOverloads +@Ignore("Test Base Class") +abstract class ResizeAppCornerMultiWindowAndPip constructor(val rotation: Rotation = Rotation.ROTATION_0, val horizontalChange: Int = 50, val verticalChange: Int = -50) { diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithCornerResize.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithCornerResize.kt index 42940a99b59f..bd25639466a3 100644 --- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithCornerResize.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithCornerResize.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.scenarios -import android.platform.test.annotations.Postsubmit import android.app.Instrumentation import android.tools.NavBar import android.tools.Rotation @@ -32,16 +31,12 @@ import com.android.wm.shell.Utils import org.junit.After import org.junit.Assume import org.junit.Before +import org.junit.Ignore import org.junit.Rule import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.BlockJUnit4ClassRunner -@RunWith(BlockJUnit4ClassRunner::class) -@Postsubmit -open class ResizeAppWithCornerResize -@JvmOverloads -constructor( +@Ignore("Test Base Class") +abstract class ResizeAppWithCornerResize( val rotation: Rotation = Rotation.ROTATION_0, val horizontalChange: Int = 200, val verticalChange: Int = -200, @@ -83,6 +78,25 @@ constructor( ) } + @Test + open fun resizeAppWithCornerResizeToMaximumSize() { + val maxResizeChange = 3000 + testApp.cornerResize( + wmHelper, + device, + DesktopModeAppHelper.Corners.RIGHT_TOP, + maxResizeChange, + -maxResizeChange + ) + testApp.cornerResize( + wmHelper, + device, + DesktopModeAppHelper.Corners.LEFT_BOTTOM, + -maxResizeChange, + maxResizeChange + ) + } + @After fun teardown() { testApp.exit(wmHelper) diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithEdgeResize.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithEdgeResize.kt index d094967e91e0..67802387b267 100644 --- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithEdgeResize.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithEdgeResize.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.scenarios -import android.platform.test.annotations.Postsubmit import android.app.Instrumentation import android.tools.NavBar import android.tools.Rotation @@ -32,15 +31,12 @@ import com.android.wm.shell.Utils import org.junit.After import org.junit.Assume import org.junit.Before +import org.junit.Ignore import org.junit.Rule import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.BlockJUnit4ClassRunner -@RunWith(BlockJUnit4ClassRunner::class) -@Postsubmit -open class ResizeAppWithEdgeResize -@JvmOverloads +@Ignore("Test Base Class") +abstract class ResizeAppWithEdgeResize constructor( val inputMethod: MotionEventHelper.InputMethod, val rotation: Rotation = Rotation.ROTATION_90 diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithButton.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithButton.kt index 33242db66f9f..2b40497844ef 100644 --- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithButton.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithButton.kt @@ -17,7 +17,6 @@ package com.android.wm.shell.scenarios import android.app.Instrumentation -import android.platform.test.annotations.Postsubmit import android.tools.NavBar import android.tools.Rotation import android.tools.traces.parsers.WindowManagerStateHelper @@ -32,15 +31,12 @@ import com.android.wm.shell.Utils import org.junit.After import org.junit.Assume import org.junit.Before +import org.junit.Ignore import org.junit.Rule import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.BlockJUnit4ClassRunner -@RunWith(BlockJUnit4ClassRunner::class) -@Postsubmit -open class SnapResizeAppWindowWithButton -@JvmOverloads +@Ignore("Test Base Class") +abstract class SnapResizeAppWindowWithButton constructor(private val toLeft: Boolean = true, isResizable: Boolean = true) { private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithDrag.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithDrag.kt index 14eb779165bb..b4bd7e1c5211 100644 --- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithDrag.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithDrag.kt @@ -17,7 +17,6 @@ package com.android.wm.shell.scenarios import android.app.Instrumentation -import android.platform.test.annotations.Postsubmit import android.tools.NavBar import android.tools.Rotation import android.tools.traces.parsers.WindowManagerStateHelper @@ -32,15 +31,12 @@ import com.android.wm.shell.Utils import org.junit.After import org.junit.Assume import org.junit.Before +import org.junit.Ignore import org.junit.Rule import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.BlockJUnit4ClassRunner -@RunWith(BlockJUnit4ClassRunner::class) -@Postsubmit -open class SnapResizeAppWindowWithDrag -@JvmOverloads +@Ignore("Test Base Class") +abstract class SnapResizeAppWindowWithDrag constructor(private val toLeft: Boolean = true, isResizable: Boolean = true) { private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() @@ -72,4 +68,4 @@ constructor(private val toLeft: Boolean = true, isResizable: Boolean = true) { fun teardown() { testApp.exit(wmHelper) } -}
\ No newline at end of file +} diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SwitchToOverviewFromDesktop.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SwitchToOverviewFromDesktop.kt index 53e36e23fd95..dad2eb633c72 100644 --- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SwitchToOverviewFromDesktop.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SwitchToOverviewFromDesktop.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.scenarios -import android.platform.test.annotations.Postsubmit import android.app.Instrumentation import android.tools.NavBar import android.tools.Rotation @@ -31,20 +30,17 @@ import com.android.wm.shell.Utils import org.junit.After import org.junit.Assume import org.junit.Before +import org.junit.Ignore import org.junit.Rule import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.BlockJUnit4ClassRunner /** * Base test for opening recent apps overview from desktop mode. * * Navigation mode can be passed as a constructor parameter, by default it is set to gesture navigation. */ -@RunWith(BlockJUnit4ClassRunner::class) -@Postsubmit -open class SwitchToOverviewFromDesktop -@JvmOverloads +@Ignore("Base Test Class") +abstract class SwitchToOverviewFromDesktop constructor(val navigationMode: NavBar = NavBar.MODE_GESTURAL) { private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java deleted file mode 100644 index b1befc46f383..000000000000 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.splitscreen; - -import static android.view.Display.DEFAULT_DISPLAY; - -import static com.google.common.truth.Truth.assertThat; - -import android.app.ActivityManager; -import android.view.SurfaceControl; -import android.view.SurfaceSession; -import android.window.WindowContainerTransaction; - -import androidx.test.annotation.UiThreadTest; -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.filters.SmallTest; - -import com.android.launcher3.icons.IconProvider; -import com.android.wm.shell.ShellTaskOrganizer; -import com.android.wm.shell.ShellTestCase; -import com.android.wm.shell.TestRunningTaskInfoBuilder; -import com.android.wm.shell.common.SyncTransactionQueue; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.util.Optional; - -/** Tests for {@link MainStage} */ -@SmallTest -@RunWith(AndroidJUnit4.class) -public class MainStageTests extends ShellTestCase { - @Mock private ShellTaskOrganizer mTaskOrganizer; - @Mock private StageTaskListener.StageListenerCallbacks mCallbacks; - @Mock private SyncTransactionQueue mSyncQueue; - @Mock private ActivityManager.RunningTaskInfo mRootTaskInfo; - @Mock private SurfaceControl mRootLeash; - @Mock private IconProvider mIconProvider; - private WindowContainerTransaction mWct = new WindowContainerTransaction(); - private SurfaceSession mSurfaceSession = new SurfaceSession(); - private MainStage mMainStage; - - @Before - @UiThreadTest - public void setup() { - MockitoAnnotations.initMocks(this); - mRootTaskInfo = new TestRunningTaskInfoBuilder().build(); - mMainStage = new MainStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks, - mSyncQueue, mSurfaceSession, mIconProvider, Optional.empty()); - mMainStage.onTaskAppeared(mRootTaskInfo, mRootLeash); - } - - @Test - public void testActiveDeactivate() { - mMainStage.activate(mWct, true /* reparent */); - assertThat(mMainStage.isActive()).isTrue(); - - mMainStage.deactivate(mWct); - assertThat(mMainStage.isActive()).isFalse(); - } -} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java index 4de227836104..751275b9e167 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java @@ -74,10 +74,10 @@ public class SplitTestUtils { final SurfaceControl mRootLeash; TestStageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue, - ShellTaskOrganizer taskOrganizer, MainStage mainStage, StageTaskListener sideStage, - DisplayController displayController, DisplayImeController imeController, - DisplayInsetsController insetsController, SplitLayout splitLayout, - Transitions transitions, TransactionPool transactionPool, + ShellTaskOrganizer taskOrganizer, StageTaskListener mainStage, + StageTaskListener sideStage, DisplayController displayController, + DisplayImeController imeController, DisplayInsetsController insetsController, + SplitLayout splitLayout, Transitions transitions, TransactionPool transactionPool, ShellExecutor mainExecutor, Handler mainHandler, Optional<RecentTasksController> recentTasks, LaunchAdjacentController launchAdjacentController, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java index e16743386489..af288c81616d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java @@ -116,7 +116,7 @@ public class SplitTransitionTests extends ShellTestCase { @Mock private SplitScreen.SplitInvocationListener mInvocationListener; private final TestShellExecutor mTestShellExecutor = new TestShellExecutor(); private SplitLayout mSplitLayout; - private MainStage mMainStage; + private StageTaskListener mMainStage; private StageTaskListener mSideStage; private StageCoordinator mStageCoordinator; private SplitScreenTransitions mSplitScreenTransitions; @@ -133,7 +133,7 @@ public class SplitTransitionTests extends ShellTestCase { doReturn(mockExecutor).when(mTransitions).getAnimExecutor(); doReturn(mock(SurfaceControl.Transaction.class)).when(mTransactionPool).acquire(); mSplitLayout = SplitTestUtils.createMockSplitLayout(); - mMainStage = spy(new MainStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock( + mMainStage = spy(new StageTaskListener(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock( StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession, mIconProvider, Optional.of(mWindowDecorViewModel))); mMainStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface()); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java index c9e1414c39b9..ce343b8a7fa9 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java @@ -97,7 +97,7 @@ public class StageCoordinatorTests extends ShellTestCase { @Mock private SyncTransactionQueue mSyncQueue; @Mock - private MainStage mMainStage; + private StageTaskListener mMainStage; @Mock private StageTaskListener mSideStage; @Mock diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java index acd612eb34d9..8b5cb97505c0 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java @@ -199,4 +199,13 @@ public final class StageTaskListenerTests extends ShellTestCase { assertThat(mStageTaskListener.removeTask(task.taskId, null, mWct)).isTrue(); verify(mWct).reparent(eq(task.token), isNull(), eq(false)); } + + @Test + public void testActiveDeactivate() { + mStageTaskListener.activate(mWct, true /* reparent */); + assertThat(mStageTaskListener.isActive()).isTrue(); + + mStageTaskListener.deactivate(mWct); + assertThat(mStageTaskListener.isActive()).isFalse(); + } } 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 be0549b6655d..3dd8a2bacbcd 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 @@ -73,6 +73,7 @@ import com.android.wm.shell.ShellTestCase import com.android.wm.shell.TestRunningTaskInfoBuilder import com.android.wm.shell.TestShellExecutor import com.android.wm.shell.apptoweb.AppToWebGenericLinksParser +import com.android.wm.shell.apptoweb.AssistContentRequester import com.android.wm.shell.common.DisplayChangeController import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.DisplayInsetsController @@ -165,6 +166,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { @Mock private lateinit var mockInteractionJankMonitor: InteractionJankMonitor @Mock private lateinit var mockGenericLinksParser: AppToWebGenericLinksParser @Mock private lateinit var mockUserHandle: UserHandle + @Mock private lateinit var mockAssistContentRequester: AssistContentRequester @Mock private lateinit var mockToast: Toast private val bgExecutor = TestShellExecutor() @Mock private lateinit var mockMultiInstanceHelper: MultiInstanceHelper @@ -218,6 +220,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { mockTransitions, Optional.of(mockDesktopTasksController), mockGenericLinksParser, + mockAssistContentRequester, mockMultiInstanceHelper, mockDesktopModeWindowDecorFactory, mockInputMonitorFactory, @@ -1131,7 +1134,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { whenever( mockDesktopModeWindowDecorFactory.create( any(), any(), any(), any(), any(), eq(task), any(), any(), any(), any(), any(), - any(), any(), any()) + any(), any(), any(), any()) ).thenReturn(decoration) decoration.mTaskInfo = task whenever(decoration.isFocused).thenReturn(task.isFocused) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java index 258c86094a36..b9e542a0e0c1 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java @@ -46,6 +46,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.ActivityManager; +import android.app.assist.AssistContent; import android.content.ComponentName; import android.content.Context; import android.content.pm.ActivityInfo; @@ -88,6 +89,7 @@ import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestRunningTaskInfoBuilder; import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.apptoweb.AppToWebGenericLinksParser; +import com.android.wm.shell.apptoweb.AssistContentRequester; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.MultiInstanceHelper; import com.android.wm.shell.common.ShellExecutor; @@ -133,6 +135,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { private static final Uri TEST_URI1 = Uri.parse("https://www.google.com/"); private static final Uri TEST_URI2 = Uri.parse("https://docs.google.com/"); + private static final Uri TEST_URI3 = Uri.parse("https://slides.google.com/"); @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT); @@ -175,6 +178,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { @Mock private WindowManager mMockWindowManager; @Mock + private AssistContentRequester mMockAssistContentRequester; + @Mock private HandleMenu mMockHandleMenu; @Mock private HandleMenuFactory mMockHandleMenuFactory; @@ -189,7 +194,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { private SurfaceControl.Transaction mMockTransaction; private StaticMockitoSession mMockitoSession; private TestableContext mTestableContext; - private ShellExecutor mBgExecutor = new TestShellExecutor(); + private final ShellExecutor mBgExecutor = new TestShellExecutor(); + private final AssistContent mAssistContent = new AssistContent(); /** Set up run before test class. */ @BeforeClass @@ -673,10 +679,11 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { public void capturedLink_handleMenuBrowserLinkSetToCapturedLinkIfValid() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */); final DesktopModeWindowDecoration decor = createWindowDecoration( - taskInfo, TEST_URI1 /* captured link */, TEST_URI2 /* generic link */); + taskInfo, TEST_URI1 /* captured link */, TEST_URI2 /* web uri */, + TEST_URI3 /* generic link */); // Verify handle menu's browser link set as captured link - decor.createHandleMenu(mMockSplitScreenController); + createHandleMenu(decor); verifyHandleMenuCreated(TEST_URI1); } @@ -685,7 +692,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { public void capturedLink_postsOnCapturedLinkExpiredRunnable() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */); final DesktopModeWindowDecoration decor = createWindowDecoration( - taskInfo, TEST_URI1 /* captured link */, null /* generic link */); + taskInfo, TEST_URI1 /* captured link */, null /* web uri */, + null /* generic link */); final ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class); // Run runnable to set captured link to expired @@ -694,7 +702,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { // Verify captured link is no longer valid by verifying link is not set as handle menu // browser link. - decor.createHandleMenu(mMockSplitScreenController); + createHandleMenu(decor); verifyHandleMenuCreated(null /* uri */); } @@ -703,7 +711,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { public void capturedLink_capturedLinkNotResetToSameLink() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */); final DesktopModeWindowDecoration decor = createWindowDecoration( - taskInfo, TEST_URI1 /* captured link */, null /* generic link */); + taskInfo, TEST_URI1 /* captured link */, null /* web uri */, + null /* generic link */); final ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class); // Run runnable to set captured link to expired @@ -714,7 +723,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { decor.relayout(taskInfo); // Verify handle menu's browser link not set to captured link since link is expired - decor.createHandleMenu(mMockSplitScreenController); + createHandleMenu(decor); verifyHandleMenuCreated(null /* uri */); } @@ -723,11 +732,12 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { public void capturedLink_capturedLinkStillUsedIfExpiredAfterHandleMenuCreation() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */); final DesktopModeWindowDecoration decor = createWindowDecoration( - taskInfo, TEST_URI1 /* captured link */, null /* generic link */); + taskInfo, TEST_URI1 /* captured link */, null /* web uri */, + null /* generic link */); final ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class); // Create handle menu before link expires - decor.createHandleMenu(mMockSplitScreenController); + createHandleMenu(decor); // Run runnable to set captured link to expired verify(mMockHandler).postDelayed(runnableArgument.capture(), anyLong()); @@ -735,7 +745,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { // Verify handle menu's browser link is set to captured link since menu was opened before // captured link expired - decor.createHandleMenu(mMockSplitScreenController); + createHandleMenu(decor); verifyHandleMenuCreated(TEST_URI1); } @@ -744,12 +754,13 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { public void capturedLink_capturedLinkExpiresAfterClick() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */); final DesktopModeWindowDecoration decor = createWindowDecoration( - taskInfo, TEST_URI1 /* captured link */, null /* generic link */); + taskInfo, TEST_URI1 /* captured link */, null /* web uri */, + null /* generic link */); final ArgumentCaptor<Function1<Uri, Unit>> openInBrowserCaptor = ArgumentCaptor.forClass(Function1.class); // Simulate menu opening and clicking open in browser button - decor.createHandleMenu(mMockSplitScreenController); + createHandleMenu(decor); verify(mMockHandleMenu).show( any(), any(), @@ -763,7 +774,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { // Verify handle menu's browser link not set to captured link since link not valid after // open in browser clicked - decor.createHandleMenu(mMockSplitScreenController); + createHandleMenu(decor); verifyHandleMenuCreated(null /* uri */); } @@ -772,10 +783,11 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { public void capturedLink_openInBrowserListenerCalledOnClick() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */); final DesktopModeWindowDecoration decor = createWindowDecoration( - taskInfo, TEST_URI1 /* captured link */, null /* generic link */); + taskInfo, TEST_URI1 /* captured link */, null /* web uri */, + null /* generic link */); final ArgumentCaptor<Function1<Uri, Unit>> openInBrowserCaptor = ArgumentCaptor.forClass(Function1.class); - decor.createHandleMenu(mMockSplitScreenController); + createHandleMenu(decor); verify(mMockHandleMenu).show( any(), any(), @@ -793,24 +805,38 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB) - public void genericLink_genericLinkUsedWhenCapturedLinkUnavailable() { + public void webUriLink_webUriLinkUsedWhenCapturedLinkUnavailable() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */); final DesktopModeWindowDecoration decor = createWindowDecoration( - taskInfo, null /* captured link */, TEST_URI2 /* generic link */); - - // Verify handle menu's browser link set as generic link no captured link is available - decor.createHandleMenu(mMockSplitScreenController); + taskInfo, null /* captured link */, TEST_URI2 /* web uri */, + TEST_URI3 /* generic link */); + // Verify handle menu's browser link set as web uri link when captured link is unavailable + createHandleMenu(decor); verifyHandleMenuCreated(TEST_URI2); } @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB) + public void genericLink_genericLinkUsedWhenCapturedLinkAndWebUriUnavailable() { + final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */); + final DesktopModeWindowDecoration decor = createWindowDecoration( + taskInfo, null /* captured link */, null /* web uri */, + TEST_URI3 /* generic link */); + + // Verify handle menu's browser link set as generic link when captured link and web uri link + // are unavailable + createHandleMenu(decor); + verifyHandleMenuCreated(TEST_URI3); + } + + @Test public void handleMenu_onCloseMenuClick_closesMenu() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); final DesktopModeWindowDecoration decoration = createWindowDecoration(taskInfo, true /* relayout */); final ArgumentCaptor<Function0<Unit>> closeClickListener = ArgumentCaptor.forClass(Function0.class); - decoration.createHandleMenu(mMockSplitScreenController); + createHandleMenu(decoration); verify(mMockHandleMenu).show( any(), any(), @@ -860,9 +886,10 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { private DesktopModeWindowDecoration createWindowDecoration( ActivityManager.RunningTaskInfo taskInfo, @Nullable Uri capturedLink, - @Nullable Uri genericLink) { + @Nullable Uri webUri, @Nullable Uri genericLink) { taskInfo.capturedLink = capturedLink; taskInfo.capturedLinkTimestamp = System.currentTimeMillis(); + mAssistContent.setWebUri(webUri); final String genericLinkString = genericLink == null ? null : genericLink.toString(); doReturn(genericLinkString).when(mMockGenericLinksParser).getGenericLink(any()); // Relayout to set captured link @@ -894,11 +921,10 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { mContext, mMockDisplayController, mMockSplitScreenController, mMockShellTaskOrganizer, taskInfo, mMockSurfaceControl, mMockHandler, mBgExecutor, mMockChoreographer, mMockSyncQueue, mMockRootTaskDisplayAreaOrganizer, - mMockGenericLinksParser, SurfaceControl.Builder::new, mMockTransactionSupplier, - WindowContainerTransaction::new, SurfaceControl::new, - new WindowManagerWrapper(mMockWindowManager), - mMockSurfaceControlViewHostFactory, maximizeMenuFactory, mMockHandleMenuFactory, - mMockMultiInstanceHelper); + mMockGenericLinksParser, mMockAssistContentRequester, SurfaceControl.Builder::new, + mMockTransactionSupplier, WindowContainerTransaction::new, SurfaceControl::new, + new WindowManagerWrapper(mMockWindowManager), mMockSurfaceControlViewHostFactory, + maximizeMenuFactory, mMockHandleMenuFactory, mMockMultiInstanceHelper); windowDecor.setCaptionListeners(mMockTouchEventListener, mMockTouchEventListener, mMockTouchEventListener, mMockTouchEventListener); windowDecor.setExclusionRegionListener(mMockExclusionRegionListener); @@ -926,6 +952,13 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { } + private void createHandleMenu(@NonNull DesktopModeWindowDecoration decor) { + decor.createHandleMenu(); + // Call DesktopModeWindowDecoration#onAssistContentReceived because decor waits to receive + // {@link AssistContent} before creating the menu + decor.onAssistContentReceived(mAssistContent); + } + private static boolean hasNoInputChannelFeature(RelayoutParams params) { return (params.mInputFeatures & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) != 0; diff --git a/libs/hostgraphics/Android.bp b/libs/hostgraphics/Android.bp index 09232b64616d..a58493aa47ca 100644 --- a/libs/hostgraphics/Android.bp +++ b/libs/hostgraphics/Android.bp @@ -7,6 +7,17 @@ package { default_applicable_licenses: ["frameworks_base_license"], } +cc_library_headers { + name: "libhostgraphics_headers", + host_supported: true, + export_include_dirs: ["include"], + target: { + windows: { + enabled: true, + }, + }, +} + cc_library_host_static { name: "libhostgraphics", @@ -30,12 +41,13 @@ cc_library_host_static { ], header_libs: [ + "libhostgraphics_headers", "libnativebase_headers", "libnativedisplay_headers", "libnativewindow_headers", ], - export_include_dirs: ["."], + export_include_dirs: ["include"], target: { windows: { diff --git a/libs/hostgraphics/gui/BufferItem.h b/libs/hostgraphics/include/gui/BufferItem.h index e95a9231dfaf..e95a9231dfaf 100644 --- a/libs/hostgraphics/gui/BufferItem.h +++ b/libs/hostgraphics/include/gui/BufferItem.h diff --git a/libs/hostgraphics/gui/BufferItemConsumer.h b/libs/hostgraphics/include/gui/BufferItemConsumer.h index c25941151800..c25941151800 100644 --- a/libs/hostgraphics/gui/BufferItemConsumer.h +++ b/libs/hostgraphics/include/gui/BufferItemConsumer.h diff --git a/libs/hostgraphics/gui/BufferQueue.h b/libs/hostgraphics/include/gui/BufferQueue.h index 67a8c00fd267..67a8c00fd267 100644 --- a/libs/hostgraphics/gui/BufferQueue.h +++ b/libs/hostgraphics/include/gui/BufferQueue.h diff --git a/libs/hostgraphics/gui/ConsumerBase.h b/libs/hostgraphics/include/gui/ConsumerBase.h index 7f7309e8a3a8..7f7309e8a3a8 100644 --- a/libs/hostgraphics/gui/ConsumerBase.h +++ b/libs/hostgraphics/include/gui/ConsumerBase.h diff --git a/libs/hostgraphics/gui/IGraphicBufferConsumer.h b/libs/hostgraphics/include/gui/IGraphicBufferConsumer.h index 14ac4fe71cc8..14ac4fe71cc8 100644 --- a/libs/hostgraphics/gui/IGraphicBufferConsumer.h +++ b/libs/hostgraphics/include/gui/IGraphicBufferConsumer.h diff --git a/libs/hostgraphics/gui/IGraphicBufferProducer.h b/libs/hostgraphics/include/gui/IGraphicBufferProducer.h index 8fd8590d10d7..8fd8590d10d7 100644 --- a/libs/hostgraphics/gui/IGraphicBufferProducer.h +++ b/libs/hostgraphics/include/gui/IGraphicBufferProducer.h diff --git a/libs/hostgraphics/gui/Surface.h b/libs/hostgraphics/include/gui/Surface.h index 2774f89cb54c..2774f89cb54c 100644 --- a/libs/hostgraphics/gui/Surface.h +++ b/libs/hostgraphics/include/gui/Surface.h diff --git a/libs/hostgraphics/ui/Fence.h b/libs/hostgraphics/include/ui/Fence.h index 187c3116f61c..187c3116f61c 100644 --- a/libs/hostgraphics/ui/Fence.h +++ b/libs/hostgraphics/include/ui/Fence.h diff --git a/libs/hostgraphics/ui/GraphicBuffer.h b/libs/hostgraphics/include/ui/GraphicBuffer.h index cda45e4660ca..cda45e4660ca 100644 --- a/libs/hostgraphics/ui/GraphicBuffer.h +++ b/libs/hostgraphics/include/ui/GraphicBuffer.h diff --git a/libs/hwui/FeatureFlags.h b/libs/hwui/FeatureFlags.h index c1c30f5379ab..c0cedf12c0ae 100644 --- a/libs/hwui/FeatureFlags.h +++ b/libs/hwui/FeatureFlags.h @@ -25,14 +25,6 @@ namespace android { namespace text_feature { -inline bool fix_double_underline() { -#ifdef __ANDROID__ - return com_android_text_flags_fix_double_underline(); -#else - return true; -#endif // __ANDROID__ -} - inline bool deprecate_ui_fonts() { #ifdef __ANDROID__ return com_android_text_flags_deprecate_ui_fonts(); diff --git a/libs/hwui/HardwareBitmapUploader.cpp b/libs/hwui/HardwareBitmapUploader.cpp index a2748b050a2d..236c3736816e 100644 --- a/libs/hwui/HardwareBitmapUploader.cpp +++ b/libs/hwui/HardwareBitmapUploader.cpp @@ -318,6 +318,11 @@ bool HardwareBitmapUploader::has1010102Support() { return has101012Support; } +bool HardwareBitmapUploader::has10101010Support() { + static bool has1010110Support = checkSupport(AHARDWAREBUFFER_FORMAT_R10G10B10A10_UNORM); + return has1010110Support; +} + bool HardwareBitmapUploader::hasAlpha8Support() { static bool hasAlpha8Support = checkSupport(AHARDWAREBUFFER_FORMAT_R8_UNORM); return hasAlpha8Support; @@ -376,6 +381,19 @@ static FormatInfo determineFormat(const SkBitmap& skBitmap, bool usingGL) { } formatInfo.format = GL_RGBA; break; + case kRGBA_10x6_SkColorType: + formatInfo.isSupported = HardwareBitmapUploader::has10101010Support(); + if (formatInfo.isSupported) { + formatInfo.type = 0; // Not supported in GL + formatInfo.bufferFormat = AHARDWAREBUFFER_FORMAT_R10G10B10A10_UNORM; + formatInfo.vkFormat = VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16; + } else { + formatInfo.type = GL_UNSIGNED_BYTE; + formatInfo.bufferFormat = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM; + formatInfo.vkFormat = VK_FORMAT_R8G8B8A8_UNORM; + } + formatInfo.format = 0; // Not supported in GL + break; case kAlpha_8_SkColorType: formatInfo.isSupported = HardwareBitmapUploader::hasAlpha8Support(); if (formatInfo.isSupported) { diff --git a/libs/hwui/HardwareBitmapUploader.h b/libs/hwui/HardwareBitmapUploader.h index 00ee99648889..76cb80b722d0 100644 --- a/libs/hwui/HardwareBitmapUploader.h +++ b/libs/hwui/HardwareBitmapUploader.h @@ -33,12 +33,14 @@ public: #ifdef __ANDROID__ static bool hasFP16Support(); static bool has1010102Support(); + static bool has10101010Support(); static bool hasAlpha8Support(); #else static bool hasFP16Support() { return true; } static bool has1010102Support() { return true; } + static bool has10101010Support() { return true; } static bool hasAlpha8Support() { return true; } #endif }; diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp index 72e83afbd96f..9e825fb350d6 100644 --- a/libs/hwui/SkiaCanvas.cpp +++ b/libs/hwui/SkiaCanvas.cpp @@ -841,9 +841,6 @@ void SkiaCanvas::drawGlyphs(ReadGlyphFunc glyphFunc, int count, const Paint& pai sk_sp<SkTextBlob> textBlob(builder.make()); applyLooper(&paintCopy, [&](const SkPaint& p) { mCanvas->drawTextBlob(textBlob, 0, 0, p); }); - if (!text_feature::fix_double_underline()) { - drawTextDecorations(x, y, totalAdvance, paintCopy); - } } void SkiaCanvas::drawLayoutOnPath(const minikin::Layout& layout, float hOffset, float vOffset, diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp index 80b6c0385fca..5af4af27babd 100644 --- a/libs/hwui/hwui/Canvas.cpp +++ b/libs/hwui/hwui/Canvas.cpp @@ -110,28 +110,26 @@ void Canvas::drawText(const uint16_t* text, int textSize, int start, int count, DrawTextFunctor f(layout, this, paint, x, y, layout.getAdvance()); MinikinUtils::forFontRun(layout, &paint, f); - if (text_feature::fix_double_underline()) { - Paint copied(paint); - PaintFilter* filter = getPaintFilter(); - if (filter != nullptr) { - filter->filterFullPaint(&copied); + Paint copied(paint); + PaintFilter* filter = getPaintFilter(); + if (filter != nullptr) { + filter->filterFullPaint(&copied); + } + const bool isUnderline = copied.isUnderline(); + const bool isStrikeThru = copied.isStrikeThru(); + if (isUnderline || isStrikeThru) { + const SkScalar left = x; + const SkScalar right = x + layout.getAdvance(); + if (isUnderline) { + const SkScalar top = y + f.getUnderlinePosition(); + drawStroke(left, right, top, f.getUnderlineThickness(), copied, this); } - const bool isUnderline = copied.isUnderline(); - const bool isStrikeThru = copied.isStrikeThru(); - if (isUnderline || isStrikeThru) { - const SkScalar left = x; - const SkScalar right = x + layout.getAdvance(); - if (isUnderline) { - const SkScalar top = y + f.getUnderlinePosition(); - drawStroke(left, right, top, f.getUnderlineThickness(), copied, this); - } - if (isStrikeThru) { - float textSize = paint.getSkFont().getSize(); - const float position = textSize * Paint::kStdStrikeThru_Top; - const SkScalar thickness = textSize * Paint::kStdStrikeThru_Thickness; - const SkScalar top = y + position; - drawStroke(left, right, top, thickness, copied, this); - } + if (isStrikeThru) { + float textSize = paint.getSkFont().getSize(); + const float position = textSize * Paint::kStdStrikeThru_Top; + const SkScalar thickness = textSize * Paint::kStdStrikeThru_Thickness; + const SkScalar top = y + position; + drawStroke(left, right, top, thickness, copied, this); } } } diff --git a/libs/hwui/hwui/DrawTextFunctor.h b/libs/hwui/hwui/DrawTextFunctor.h index 0efb2c81af01..d7bf20130b71 100644 --- a/libs/hwui/hwui/DrawTextFunctor.h +++ b/libs/hwui/hwui/DrawTextFunctor.h @@ -142,32 +142,30 @@ public: canvas->drawGlyphs(glyphFunc, glyphCount, paint, x, y, totalAdvance); } - if (text_feature::fix_double_underline()) { - // Extract underline position and thickness. - if (paint.isUnderline()) { - SkFontMetrics metrics; - paint.getSkFont().getMetrics(&metrics); - const float textSize = paint.getSkFont().getSize(); - SkScalar position; - if (!metrics.hasUnderlinePosition(&position)) { - position = textSize * Paint::kStdUnderline_Top; - } - SkScalar thickness; - if (!metrics.hasUnderlineThickness(&thickness)) { - thickness = textSize * Paint::kStdUnderline_Thickness; - } - - // If multiple fonts are used, use the most bottom position and most thick stroke - // width as the underline position. This follows the CSS standard: - // https://www.w3.org/TR/css-text-decor-3/#text-underline-position-property - // <quote> - // The exact position and thickness of line decorations is UA-defined in this level. - // However, for underlines and overlines the UA must use a single thickness and - // position on each line for the decorations deriving from a single decorating box. - // </quote> - underlinePosition = std::max(underlinePosition, position); - underlineThickness = std::max(underlineThickness, thickness); + // Extract underline position and thickness. + if (paint.isUnderline()) { + SkFontMetrics metrics; + paint.getSkFont().getMetrics(&metrics); + const float textSize = paint.getSkFont().getSize(); + SkScalar position; + if (!metrics.hasUnderlinePosition(&position)) { + position = textSize * Paint::kStdUnderline_Top; } + SkScalar thickness; + if (!metrics.hasUnderlineThickness(&thickness)) { + thickness = textSize * Paint::kStdUnderline_Thickness; + } + + // If multiple fonts are used, use the most bottom position and most thick stroke + // width as the underline position. This follows the CSS standard: + // https://www.w3.org/TR/css-text-decor-3/#text-underline-position-property + // <quote> + // The exact position and thickness of line decorations is UA-defined in this level. + // However, for underlines and overlines the UA must use a single thickness and + // position on each line for the decorations deriving from a single decorating box. + // </quote> + underlinePosition = std::max(underlinePosition, position); + underlineThickness = std::max(underlineThickness, thickness); } } diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp index bce84ae77c87..e3023937964e 100644 --- a/libs/hwui/renderthread/VulkanManager.cpp +++ b/libs/hwui/renderthread/VulkanManager.cpp @@ -318,6 +318,15 @@ void VulkanManager::setupDevice(skgpu::VulkanExtensions& grExtensions, tailPNext = &deviceFaultFeatures->pNext; } + if (grExtensions.hasExtension(VK_EXT_RGBA10X6_FORMATS_EXTENSION_NAME, 1)) { + VkPhysicalDeviceRGBA10X6FormatsFeaturesEXT* formatFeatures = + new VkPhysicalDeviceRGBA10X6FormatsFeaturesEXT; + formatFeatures->sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RGBA10X6_FORMATS_FEATURES_EXT; + formatFeatures->pNext = nullptr; + *tailPNext = formatFeatures; + tailPNext = &formatFeatures->pNext; + } + // query to get the physical device features mGetPhysicalDeviceFeatures2(mPhysicalDevice, &features); // this looks like it would slow things down, diff --git a/libs/hwui/tests/unit/UnderlineTest.cpp b/libs/hwui/tests/unit/UnderlineTest.cpp index c70a30477ecf..ecb06d8ca4db 100644 --- a/libs/hwui/tests/unit/UnderlineTest.cpp +++ b/libs/hwui/tests/unit/UnderlineTest.cpp @@ -109,9 +109,7 @@ DrawTextFunctor processFunctor(const std::vector<uint16_t>& text, Paint* paint) return f; } -TEST_WITH_FLAGS(UnderlineTest, Roboto, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::text::flags, - fix_double_underline))) { +TEST(UnderlineTest, Roboto) { float textSize = 100; Paint paint; paint.getSkFont().setSize(textSize); @@ -123,9 +121,7 @@ TEST_WITH_FLAGS(UnderlineTest, Roboto, EXPECT_EQ(ROBOTO_THICKNESS_EM * textSize, functor.getUnderlineThickness()); } -TEST_WITH_FLAGS(UnderlineTest, NotoCJK, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::text::flags, - fix_double_underline))) { +TEST(UnderlineTest, NotoCJK) { float textSize = 100; Paint paint; paint.getSkFont().setSize(textSize); @@ -137,9 +133,7 @@ TEST_WITH_FLAGS(UnderlineTest, NotoCJK, EXPECT_EQ(NOTO_CJK_THICKNESS_EM * textSize, functor.getUnderlineThickness()); } -TEST_WITH_FLAGS(UnderlineTest, Mixture, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::text::flags, - fix_double_underline))) { +TEST(UnderlineTest, Mixture) { float textSize = 100; Paint paint; paint.getSkFont().setSize(textSize); diff --git a/libs/hwui/utils/Color.cpp b/libs/hwui/utils/Color.cpp index 6a560b365247..9673c5f03642 100644 --- a/libs/hwui/utils/Color.cpp +++ b/libs/hwui/utils/Color.cpp @@ -49,6 +49,10 @@ static inline SkImageInfo createImageInfo(int32_t width, int32_t height, int32_t colorType = kRGBA_1010102_SkColorType; alphaType = kPremul_SkAlphaType; break; + case AHARDWAREBUFFER_FORMAT_R10G10B10A10_UNORM: + colorType = kRGBA_10x6_SkColorType; + alphaType = kPremul_SkAlphaType; + break; case AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT: colorType = kRGBA_F16_SkColorType; alphaType = kPremul_SkAlphaType; @@ -86,6 +90,8 @@ uint32_t ColorTypeToBufferFormat(SkColorType colorType) { return AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM; case kRGBA_1010102_SkColorType: return AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM; + case kRGBA_10x6_SkColorType: + return AHARDWAREBUFFER_FORMAT_R10G10B10A10_UNORM; case kARGB_4444_SkColorType: // Hardcoding the value from android::PixelFormat static constexpr uint64_t kRGBA4444 = 7; @@ -108,6 +114,8 @@ SkColorType BufferFormatToColorType(uint32_t format) { return kRGB_565_SkColorType; case AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM: return kRGBA_1010102_SkColorType; + case AHARDWAREBUFFER_FORMAT_R10G10B10A10_UNORM: + return kRGBA_10x6_SkColorType; case AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT: return kRGBA_F16_SkColorType; case AHARDWAREBUFFER_FORMAT_R8_UNORM: diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 25fae76768d9..4981cb31ce7d 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -6984,6 +6984,27 @@ public class AudioManager { } /** + * Test method for enabling/disabling the volume controller long press timeout for checking + * whether two consecutive volume adjustments should be treated as a volume long press. + * + * <p>Used only for testing + * + * @param enable true for enabling, otherwise will be disabled (test mode) + * + * @hide + **/ + @TestApi + @SuppressLint("UnflaggedApi") // @TestApi without associated feature. + @RequiresPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) + public void setVolumeControllerLongPressTimeoutEnabled(boolean enable) { + try { + getService().setVolumeControllerLongPressTimeoutEnabled(enable); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Only useful for volume controllers. * @hide */ diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index a96562d55f67..9af6b2842988 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -303,6 +303,9 @@ interface IAudioService { void notifyVolumeControllerVisible(in IVolumeController controller, boolean visible); + @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED") + oneway void setVolumeControllerLongPressTimeoutEnabled(boolean enable); + boolean isStreamAffectedByRingerMode(int streamType); boolean isStreamAffectedByMute(int streamType); diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt index cdf8f89f683a..cc5ff8168567 100644 --- a/nfc/api/system-current.txt +++ b/nfc/api/system-current.txt @@ -82,6 +82,7 @@ package android.nfc { method public void onEnableStarted(); method public void onHceEventReceived(int); method public void onNdefRead(@NonNull java.util.function.Consumer<java.lang.Boolean>); + method public void onReaderOptionChanged(boolean); method public void onRfDiscoveryStarted(boolean); method public void onRfFieldActivated(boolean); method public void onRoutingChanged(); diff --git a/nfc/java/android/nfc/INfcOemExtensionCallback.aidl b/nfc/java/android/nfc/INfcOemExtensionCallback.aidl index b65c83773618..e49ef7e80705 100644 --- a/nfc/java/android/nfc/INfcOemExtensionCallback.aidl +++ b/nfc/java/android/nfc/INfcOemExtensionCallback.aidl @@ -37,6 +37,7 @@ interface INfcOemExtensionCallback { void onTagDispatch(in ResultReceiver isSkipped); void onRoutingChanged(); void onHceEventReceived(int action); + void onReaderOptionChanged(boolean enabled); void onCardEmulationActivated(boolean isActivated); void onRfFieldActivated(boolean isActivated); void onRfDiscoveryStarted(boolean isDiscoveryStarted); diff --git a/nfc/java/android/nfc/NfcOemExtension.java b/nfc/java/android/nfc/NfcOemExtension.java index 632f693c4fad..d51b704a7e7f 100644 --- a/nfc/java/android/nfc/NfcOemExtension.java +++ b/nfc/java/android/nfc/NfcOemExtension.java @@ -223,6 +223,13 @@ public final class NfcOemExtension { void onHceEventReceived(@HostCardEmulationAction int action); /** + * API to notify when reader option has been changed using + * {@link NfcAdapter#enableReaderOption(boolean)} by some app. + * @param enabled Flag indicating ReaderMode enabled/disabled + */ + void onReaderOptionChanged(boolean enabled); + + /** * Notifies NFC is activated in listen mode. * NFC Forum NCI-2.3 ch.5.2.6 specification * @@ -488,6 +495,12 @@ public final class NfcOemExtension { handleVoidCallback(action, cb::onHceEventReceived, ex)); } + @Override + public void onReaderOptionChanged(boolean enabled) throws RemoteException { + mCallbackMap.forEach((cb, ex) -> + handleVoidCallback(enabled, cb::onReaderOptionChanged, ex)); + } + private <T> void handleVoidCallback( T input, Consumer<T> callbackMethod, Executor executor) { synchronized (mLock) { diff --git a/packages/SettingsLib/DataStore/Android.bp b/packages/SettingsLib/DataStore/Android.bp index 86c8f0da83cb..6840e10161f3 100644 --- a/packages/SettingsLib/DataStore/Android.bp +++ b/packages/SettingsLib/DataStore/Android.bp @@ -17,6 +17,7 @@ android_library { "androidx.annotation_annotation", "androidx.collection_collection-ktx", "androidx.core_core-ktx", + "error_prone_annotations", "guava", ], kotlincflags: ["-Xjvm-default=all"], diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyValueStore.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyValueStore.kt new file mode 100644 index 000000000000..3d4133732915 --- /dev/null +++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyValueStore.kt @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.datastore + +import android.content.SharedPreferences + +/** Interface of key-value store. */ +interface KeyValueStore : KeyedObservable<String> { + + /** Returns if the storage contains persistent value of given key. */ + fun contains(key: String): Boolean + + /** Gets default value of given key. */ + fun <T : Any> getDefaultValue(key: String, valueType: Class<T>): T? = + when (valueType) { + Boolean::class.javaObjectType -> false + Float::class.javaObjectType -> 0f + Int::class.javaObjectType -> 0 + Long::class.javaObjectType -> 0 + else -> null + } + as T? + + /** Gets value of given key. */ + fun <T : Any> getValue(key: String, valueType: Class<T>): T? + + /** + * Sets value for given key. + * + * @param key key + * @param valueType value type + * @param value value to set, null means remove + */ + fun <T : Any> setValue(key: String, valueType: Class<T>, value: T?) +} + +/** [SharedPreferences] based [KeyValueStore]. */ +interface SharedPreferencesKeyValueStore : KeyValueStore { + + /** [SharedPreferences] of the key-value store. */ + val sharedPreferences: SharedPreferences + + override fun contains(key: String): Boolean = sharedPreferences.contains(key) + + override fun <T : Any> getValue(key: String, valueType: Class<T>): T? = + when (valueType) { + Boolean::class.javaObjectType -> sharedPreferences.getBoolean(key, false) + Float::class.javaObjectType -> sharedPreferences.getFloat(key, 0f) + Int::class.javaObjectType -> sharedPreferences.getInt(key, 0) + Long::class.javaObjectType -> sharedPreferences.getLong(key, 0) + String::class.javaObjectType -> sharedPreferences.getString(key, null) + Set::class.javaObjectType -> sharedPreferences.getStringSet(key, null) + else -> null + } + as T? + + override fun <T : Any> setValue(key: String, valueType: Class<T>, value: T?) { + if (value == null) { + sharedPreferences.edit().remove(key).apply() + return + } + val edit = sharedPreferences.edit() + when (valueType) { + Boolean::class.javaObjectType -> edit.putBoolean(key, value as Boolean) + Float::class.javaObjectType -> edit.putFloat(key, value as Float) + Int::class.javaObjectType -> edit.putInt(key, value as Int) + Long::class.javaObjectType -> edit.putLong(key, value as Long) + String::class.javaObjectType -> edit.putString(key, value as String) + Set::class.javaObjectType -> edit.putStringSet(key, value as Set<String>) + else -> {} + } + edit.apply() + } +} diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt index 4ce1d3790e8b..ec903179f496 100644 --- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt +++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt @@ -19,6 +19,7 @@ package com.android.settingslib.datastore import androidx.annotation.AnyThread import androidx.annotation.GuardedBy import androidx.collection.MutableScatterMap +import com.google.errorprone.annotations.CanIgnoreReturnValue import java.util.WeakHashMap import java.util.concurrent.Executor @@ -62,8 +63,9 @@ interface KeyedObservable<K> { * * @param observer observer to be notified * @param executor executor to run the callback + * @return if the observer is newly added */ - fun addObserver(observer: KeyedObserver<K?>, executor: Executor) + @CanIgnoreReturnValue fun addObserver(observer: KeyedObserver<K?>, executor: Executor): Boolean /** * Adds an observer on given key. @@ -73,14 +75,24 @@ interface KeyedObservable<K> { * @param key key to observe * @param observer observer to be notified * @param executor executor to run the callback + * @return if the observer is newly added */ - fun addObserver(key: K, observer: KeyedObserver<K>, executor: Executor) + @CanIgnoreReturnValue + fun addObserver(key: K, observer: KeyedObserver<K>, executor: Executor): Boolean - /** Removes observer. */ - fun removeObserver(observer: KeyedObserver<K?>) + /** + * Removes observer. + * + * @return if the observer is found and removed + */ + @CanIgnoreReturnValue fun removeObserver(observer: KeyedObserver<K?>): Boolean - /** Removes observer on given key. */ - fun removeObserver(key: K, observer: KeyedObserver<K>) + /** + * Removes observer on given key. + * + * @return if the observer is found and removed + */ + @CanIgnoreReturnValue fun removeObserver(key: K, observer: KeyedObserver<K>): Boolean /** * Notifies all observers that a change occurs. @@ -111,14 +123,17 @@ open class KeyedDataObservable<K> : KeyedObservable<K> { @GuardedBy("itself") private val keyedObservers = MutableScatterMap<K, WeakHashMap<KeyedObserver<K>, Executor>>() - override fun addObserver(observer: KeyedObserver<K?>, executor: Executor) { + @CanIgnoreReturnValue + override fun addObserver(observer: KeyedObserver<K?>, executor: Executor): Boolean { val oldExecutor = synchronized(observers) { observers.put(observer, executor) } if (oldExecutor != null && oldExecutor != executor) { throw IllegalStateException("Add $observer twice, old=$oldExecutor, new=$executor") } + return oldExecutor == null } - override fun addObserver(key: K, observer: KeyedObserver<K>, executor: Executor) { + @CanIgnoreReturnValue + override fun addObserver(key: K, observer: KeyedObserver<K>, executor: Executor): Boolean { val oldExecutor = synchronized(keyedObservers) { keyedObservers.getOrPut(key) { WeakHashMap() }.put(observer, executor) @@ -126,20 +141,23 @@ open class KeyedDataObservable<K> : KeyedObservable<K> { if (oldExecutor != null && oldExecutor != executor) { throw IllegalStateException("Add $observer twice, old=$oldExecutor, new=$executor") } + return oldExecutor == null } - override fun removeObserver(observer: KeyedObserver<K?>) { - synchronized(observers) { observers.remove(observer) } - } + @CanIgnoreReturnValue + override fun removeObserver(observer: KeyedObserver<K?>) = + synchronized(observers) { observers.remove(observer) } != null - override fun removeObserver(key: K, observer: KeyedObserver<K>) { + @CanIgnoreReturnValue + override fun removeObserver(key: K, observer: KeyedObserver<K>) = synchronized(keyedObservers) { - val observers = keyedObservers[key] - if (observers?.remove(observer) != null && observers.isEmpty()) { + val observers = keyedObservers[key] ?: return false + val removed = observers.remove(observer) != null + if (removed && observers.isEmpty()) { keyedObservers.remove(key) } + removed } - } override fun notifyChange(reason: Int) { // make a copy to avoid potential ConcurrentModificationException diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsGlobalStore.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsGlobalStore.kt new file mode 100644 index 000000000000..4aef0fcfdc15 --- /dev/null +++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsGlobalStore.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.settingslib.datastore + +import android.content.ContentResolver +import android.content.Context +import android.provider.Settings.Global +import android.provider.Settings.SettingNotFoundException + +/** + * [KeyValueStore] for [Global] settings. + * + * By default, a boolean type `true` value is stored as `1` and `false` value is stored as `0`. + */ +class SettingsGlobalStore private constructor(contentResolver: ContentResolver) : + SettingsStore(contentResolver) { + + override val tag: String + get() = "SettingsGlobalStore" + + override fun contains(key: String): Boolean = Global.getString(contentResolver, key) != null + + override fun <T : Any> getValue(key: String, valueType: Class<T>): T? = + try { + when (valueType) { + Boolean::class.javaObjectType -> Global.getInt(contentResolver, key) != 0 + Float::class.javaObjectType -> Global.getFloat(contentResolver, key) + Int::class.javaObjectType -> Global.getInt(contentResolver, key) + Long::class.javaObjectType -> Global.getLong(contentResolver, key) + String::class.javaObjectType -> Global.getString(contentResolver, key) + else -> throw UnsupportedOperationException("Get $key $valueType") + } + as T? + } catch (e: SettingNotFoundException) { + null + } + + override fun <T : Any> setValue(key: String, valueType: Class<T>, value: T?) { + if (value == null) { + Global.putString(contentResolver, key, null) + return + } + when (valueType) { + Boolean::class.javaObjectType -> + Global.putInt(contentResolver, key, if (value == true) 1 else 0) + Float::class.javaObjectType -> Global.putFloat(contentResolver, key, value as Float) + Int::class.javaObjectType -> Global.putInt(contentResolver, key, value as Int) + Long::class.javaObjectType -> Global.putLong(contentResolver, key, value as Long) + String::class.javaObjectType -> Global.putString(contentResolver, key, value as String) + else -> throw UnsupportedOperationException("Set $key $valueType") + } + } + + companion object { + @Volatile private var instance: SettingsGlobalStore? = null + + @JvmStatic + fun get(context: Context): SettingsGlobalStore = + instance + ?: synchronized(this) { + instance + ?: SettingsGlobalStore(context.applicationContext.contentResolver).also { + instance = it + } + } + } +} diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsSecureStore.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsSecureStore.kt new file mode 100644 index 000000000000..9f41ecbc7370 --- /dev/null +++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsSecureStore.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.settingslib.datastore + +import android.content.ContentResolver +import android.content.Context +import android.provider.Settings.Secure +import android.provider.Settings.SettingNotFoundException + +/** + * [KeyValueStore] for [Secure] settings. + * + * By default, a boolean type `true` value is stored as `1` and `false` value is stored as `0`. + */ +class SettingsSecureStore private constructor(contentResolver: ContentResolver) : + SettingsStore(contentResolver) { + + override val tag: String + get() = "SettingsSecureStore" + + override fun contains(key: String): Boolean = Secure.getString(contentResolver, key) != null + + override fun <T : Any> getValue(key: String, valueType: Class<T>): T? = + try { + when (valueType) { + Boolean::class.javaObjectType -> Secure.getInt(contentResolver, key) != 0 + Float::class.javaObjectType -> Secure.getFloat(contentResolver, key) + Int::class.javaObjectType -> Secure.getInt(contentResolver, key) + Long::class.javaObjectType -> Secure.getLong(contentResolver, key) + String::class.javaObjectType -> Secure.getString(contentResolver, key) + else -> throw UnsupportedOperationException("Get $key $valueType") + } + as T? + } catch (e: SettingNotFoundException) { + null + } + + override fun <T : Any> setValue(key: String, valueType: Class<T>, value: T?) { + if (value == null) { + Secure.putString(contentResolver, key, null) + return + } + when (valueType) { + Boolean::class.javaObjectType -> + Secure.putInt(contentResolver, key, if (value == true) 1 else 0) + Float::class.javaObjectType -> Secure.putFloat(contentResolver, key, value as Float) + Int::class.javaObjectType -> Secure.putInt(contentResolver, key, value as Int) + Long::class.javaObjectType -> Secure.putLong(contentResolver, key, value as Long) + String::class.javaObjectType -> Secure.putString(contentResolver, key, value as String) + else -> throw UnsupportedOperationException("Set $key $valueType") + } + } + + companion object { + @Volatile private var instance: SettingsSecureStore? = null + + @JvmStatic + fun get(context: Context): SettingsSecureStore = + instance + ?: synchronized(this) { + instance + ?: SettingsSecureStore(context.applicationContext.contentResolver).also { + instance = it + } + } + } +} diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsStore.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsStore.kt new file mode 100644 index 000000000000..59816885f554 --- /dev/null +++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsStore.kt @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.datastore + +import android.content.ContentResolver +import android.database.ContentObserver +import android.net.Uri +import android.os.Handler +import android.os.Looper +import android.provider.Settings +import android.util.Log +import java.util.concurrent.Executor +import java.util.concurrent.atomic.AtomicInteger + +/** Base class of the Settings provider data stores. */ +open abstract class SettingsStore(protected val contentResolver: ContentResolver) : + KeyedDataObservable<String>(), KeyValueStore { + + /** + * Counter of observers. + * + * The value is accurate only when [addObserver] and [removeObserver] are called correctly. When + * an observer is not removed (and its weak reference is garbage collected), the content + * observer is not unregistered but this is not a big deal. + */ + private val counter = AtomicInteger() + + private val contentObserver = + object : ContentObserver(Handler(Looper.getMainLooper())) { + override fun onChange(selfChange: Boolean) { + super.onChange(selfChange, null) + } + + override fun onChange(selfChange: Boolean, uri: Uri?) { + val key = uri?.lastPathSegment ?: return + notifyChange(key, DataChangeReason.UPDATE) + } + } + + override fun addObserver(observer: KeyedObserver<String?>, executor: Executor) = + if (super.addObserver(observer, executor)) { + onObserverAdded() + true + } else { + false + } + + override fun addObserver(key: String, observer: KeyedObserver<String>, executor: Executor) = + if (super.addObserver(key, observer, executor)) { + onObserverAdded() + true + } else { + false + } + + private fun onObserverAdded() { + if (counter.getAndIncrement() != 0) return + Log.i(tag, "registerContentObserver") + contentResolver.registerContentObserver( + Settings.Global.getUriFor(""), + true, + contentObserver, + ) + } + + override fun removeObserver(observer: KeyedObserver<String?>) = + if (super.removeObserver(observer)) { + onObserverRemoved() + true + } else { + false + } + + override fun removeObserver(key: String, observer: KeyedObserver<String>) = + if (super.removeObserver(key, observer)) { + onObserverRemoved() + true + } else { + false + } + + private fun onObserverRemoved() { + if (counter.decrementAndGet() != 0) return + Log.i(tag, "unregisterContentObserver") + contentResolver.unregisterContentObserver(contentObserver) + } + + /** Tag for logging. */ + abstract val tag: String +} diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsSystemStore.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsSystemStore.kt new file mode 100644 index 000000000000..6cca7ed59534 --- /dev/null +++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsSystemStore.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.settingslib.datastore + +import android.content.ContentResolver +import android.content.Context +import android.provider.Settings.SettingNotFoundException +import android.provider.Settings.System + +/** + * [KeyValueStore] for [System] settings. + * + * By default, a boolean type `true` value is stored as `1` and `false` value is stored as `0`. + */ +class SettingsSystemStore private constructor(contentResolver: ContentResolver) : + SettingsStore(contentResolver) { + + override val tag: String + get() = "SettingsSystemStore" + + override fun contains(key: String): Boolean = System.getString(contentResolver, key) != null + + override fun <T : Any> getValue(key: String, valueType: Class<T>): T? = + try { + when (valueType) { + Boolean::class.javaObjectType -> System.getInt(contentResolver, key) != 0 + Float::class.javaObjectType -> System.getFloat(contentResolver, key) + Int::class.javaObjectType -> System.getInt(contentResolver, key) + Long::class.javaObjectType -> System.getLong(contentResolver, key) + String::class.javaObjectType -> System.getString(contentResolver, key) + else -> throw UnsupportedOperationException("Get $key $valueType") + } + as T? + } catch (e: SettingNotFoundException) { + null + } + + override fun <T : Any> setValue(key: String, valueType: Class<T>, value: T?) { + if (value == null) { + System.putString(contentResolver, key, null) + return + } + when (valueType) { + Boolean::class.javaObjectType -> + System.putInt(contentResolver, key, if (value == true) 1 else 0) + Float::class.javaObjectType -> System.putFloat(contentResolver, key, value as Float) + Int::class.javaObjectType -> System.putInt(contentResolver, key, value as Int) + Long::class.javaObjectType -> System.putLong(contentResolver, key, value as Long) + String::class.javaObjectType -> System.putString(contentResolver, key, value as String) + else -> throw UnsupportedOperationException("Set $key $valueType") + } + } + + companion object { + @Volatile private var instance: SettingsSystemStore? = null + + @JvmStatic + fun get(context: Context): SettingsSystemStore = + instance + ?: synchronized(this) { + instance + ?: SettingsSystemStore(context.applicationContext.contentResolver).also { + instance = it + } + } + } +} diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SharedPreferencesStorage.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SharedPreferencesStorage.kt index 0ca91cd4357a..ea17a56715ae 100644 --- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SharedPreferencesStorage.kt +++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SharedPreferencesStorage.kt @@ -40,8 +40,9 @@ private fun defaultVerbose() = Build.TYPE == "eng" * Note that existing entries in the SharedPreferences will NOT be deleted before restore. * * @param context Context to get SharedPreferences - * @param name Name of the SharedPreferences - * @param mode Operating mode, see [Context.getSharedPreferences] + * @param name Name of the backup restore storage + * @param sharedPreferences SharedPreferences object + * @param filePath shared preferences file path relative to data dir * @param verbose Verbose logging on key/value pairs during backup/restore. Enable for dev only! * @param filter Filter of key/value pairs for backup and restore. */ @@ -50,12 +51,14 @@ open class SharedPreferencesStorage constructor( context: Context, override val name: String, - @get:VisibleForTesting internal val sharedPreferences: SharedPreferences, + override val sharedPreferences: SharedPreferences, + filePath: String = getSharedPreferencesFilePath(context, name), private val codec: BackupCodec? = null, private val verbose: Boolean = defaultVerbose(), private val filter: (String, Any?) -> Boolean = { _, _ -> true }, ) : - BackupRestoreFileStorage(context, context.getSharedPreferencesFilePath(name)), + BackupRestoreFileStorage(context, filePath), + SharedPreferencesKeyValueStore, KeyedObservable<String> by KeyedDataObservable() { @JvmOverloads @@ -66,7 +69,15 @@ constructor( codec: BackupCodec? = null, verbose: Boolean = defaultVerbose(), filter: (String, Any?) -> Boolean = { _, _ -> true }, - ) : this(context, name, context.getSharedPreferences(name, mode), codec, verbose, filter) + ) : this( + context, + name, + context.getSharedPreferences(name, mode), + getSharedPreferencesFilePath(context, name), + codec, + verbose, + filter, + ) /** Name of the intermediate SharedPreferences. */ @VisibleForTesting @@ -80,7 +91,15 @@ constructor( return context.getSharedPreferences(intermediateName, Context.MODE_MULTI_PROCESS) } - private val sharedPreferencesListener = createSharedPreferenceListener() + private val sharedPreferencesListener = + SharedPreferences.OnSharedPreferenceChangeListener { _, key -> + if (key != null) { + notifyChange(key, DataChangeReason.UPDATE) + } else { + // On Android >= R, SharedPreferences.Editor.clear() will trigger this case + notifyChange(DataChangeReason.DELETE) + } + } init { // listener is weakly referenced, so unregister is optional @@ -183,7 +202,8 @@ constructor( else -> { Log.e( LOG_TAG, - "[$name] $operation $key=$value, unknown type: ${value?.javaClass}") + "[$name] $operation $key=$value, unknown type: ${value?.javaClass}", + ) } } } @@ -191,14 +211,31 @@ constructor( } companion object { - private fun Context.getSharedPreferencesFilePath(name: String): String { - val file = getSharedPreferencesFile(name) - return file.relativeTo(dataDirCompat).toString() + /** Returns the storage object of default [SharedPreferences]. */ + @JvmStatic + fun getDefault(context: Context, name: String): SharedPreferencesStorage { + val prefName = getDefaultSharedPreferencesName(context) + return SharedPreferencesStorage( + context, + name, + context.getSharedPreferences(prefName, Context.MODE_PRIVATE), + getSharedPreferencesFilePath(context, prefName), + ) } - /** Returns the absolute path of shared preferences file. */ + /** Returns the name of default [SharedPreferences]. */ @JvmStatic - fun Context.getSharedPreferencesFile(name: String): File { + fun getDefaultSharedPreferencesName(context: Context) = context.packageName + "_preferences" + + /** Returns the shared preferences file path relative to data dir. */ + @JvmStatic + fun getSharedPreferencesFilePath(context: Context, name: String): String { + val file = context.getSharedPreferencesFile(name) + return file.relativeTo(context.dataDirCompat).toString() + } + + /** Returns the absolute path of shared preferences file. */ + private fun Context.getSharedPreferencesFile(name: String): File { // ContextImpl.getSharedPreferencesPath is private return File(getSharedPreferencesDir(), "$name.xml") } diff --git a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/KeyedObserverTest.kt b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/KeyedObserverTest.kt index 0fdecb034f83..c99d4b386530 100644 --- a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/KeyedObserverTest.kt +++ b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/KeyedObserverTest.kt @@ -45,14 +45,14 @@ class KeyedObserverTest { @Test fun addObserver_sameExecutor() { - keyedObservable.addObserver(observer1, executor1) - keyedObservable.addObserver(observer1, executor1) + assertThat(keyedObservable.addObserver(observer1, executor1)).isTrue() + assertThat(keyedObservable.addObserver(observer1, executor1)).isFalse() } @Test fun addObserver_keyedObserver_sameExecutor() { - keyedObservable.addObserver(key1, keyedObserver1, executor1) - keyedObservable.addObserver(key1, keyedObserver1, executor1) + assertThat(keyedObservable.addObserver(key1, keyedObserver1, executor1)).isTrue() + assertThat(keyedObservable.addObserver(key1, keyedObserver1, executor1)).isFalse() } @Test @@ -109,15 +109,15 @@ class KeyedObserverTest { @Test fun addObserver_notifyObservers_removeObserver() { - keyedObservable.addObserver(observer1, executor1) - keyedObservable.addObserver(observer2, executor2) + assertThat(keyedObservable.addObserver(observer1, executor1)).isTrue() + assertThat(keyedObservable.addObserver(observer2, executor2)).isTrue() keyedObservable.notifyChange(DataChangeReason.UPDATE) verify(observer1).onKeyChanged(null, DataChangeReason.UPDATE) verify(observer2).onKeyChanged(null, DataChangeReason.UPDATE) reset(observer1, observer2) - keyedObservable.removeObserver(observer2) + assertThat(keyedObservable.removeObserver(observer2)).isTrue() keyedObservable.notifyChange(DataChangeReason.DELETE) verify(observer1).onKeyChanged(null, DataChangeReason.DELETE) @@ -126,15 +126,15 @@ class KeyedObserverTest { @Test fun addObserver_keyedObserver_notifyObservers_removeObserver() { - keyedObservable.addObserver(key1, keyedObserver1, executor1) - keyedObservable.addObserver(key2, keyedObserver2, executor2) + assertThat(keyedObservable.addObserver(key1, keyedObserver1, executor1)).isTrue() + assertThat(keyedObservable.addObserver(key2, keyedObserver2, executor2)).isTrue() keyedObservable.notifyChange(key1, DataChangeReason.UPDATE) verify(keyedObserver1).onKeyChanged(key1, DataChangeReason.UPDATE) verify(keyedObserver2, never()).onKeyChanged(key2, DataChangeReason.UPDATE) reset(keyedObserver1, keyedObserver2) - keyedObservable.removeObserver(key1, keyedObserver1) + assertThat(keyedObservable.removeObserver(key1, keyedObserver1)).isTrue() keyedObservable.notifyChange(key1, DataChangeReason.DELETE) verify(keyedObserver1, never()).onKeyChanged(key1, DataChangeReason.DELETE) @@ -143,9 +143,9 @@ class KeyedObserverTest { @Test fun notifyChange_addMoreTypeObservers_checkOnKeyChanged() { - keyedObservable.addObserver(observer1, executor1) - keyedObservable.addObserver(key1, keyedObserver1, executor1) - keyedObservable.addObserver(key2, keyedObserver2, executor1) + assertThat(keyedObservable.addObserver(observer1, executor1)).isTrue() + assertThat(keyedObservable.addObserver(key1, keyedObserver1, executor1)).isTrue() + assertThat(keyedObservable.addObserver(key2, keyedObserver2, executor1)).isTrue() keyedObservable.notifyChange(DataChangeReason.UPDATE) verify(observer1).onKeyChanged(null, DataChangeReason.UPDATE) @@ -171,25 +171,25 @@ class KeyedObserverTest { fun notifyChange_addObserverWithinCallback() { // ConcurrentModificationException is raised if it is not implemented correctly val observer: KeyedObserver<Any?> = KeyedObserver { _, _ -> - keyedObservable.addObserver(observer1, executor1) + assertThat(keyedObservable.addObserver(observer1, executor1)).isTrue() } - keyedObservable.addObserver(observer, executor1) + assertThat(keyedObservable.addObserver(observer, executor1)).isTrue() keyedObservable.notifyChange(DataChangeReason.UPDATE) - keyedObservable.removeObserver(observer) + assertThat(keyedObservable.removeObserver(observer)).isTrue() } @Test fun notifyChange_KeyedObserver_addObserverWithinCallback() { // ConcurrentModificationException is raised if it is not implemented correctly val keyObserver: KeyedObserver<Any?> = KeyedObserver { _, _ -> - keyedObservable.addObserver(key1, keyedObserver1, executor1) + assertThat(keyedObservable.addObserver(key1, keyedObserver1, executor1)).isTrue() } - keyedObservable.addObserver(key1, keyObserver, executor1) + assertThat(keyedObservable.addObserver(key1, keyObserver, executor1)).isTrue() keyedObservable.notifyChange(key1, DataChangeReason.UPDATE) - keyedObservable.removeObserver(key1, keyObserver) + assertThat(keyedObservable.removeObserver(key1, keyObserver)).isTrue() } } diff --git a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java index 4387b6f061c3..a0599bb32dd1 100644 --- a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java +++ b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java @@ -35,6 +35,7 @@ import android.view.ViewGroup.LayoutParams; import android.widget.FrameLayout; import android.widget.ImageView; +import androidx.annotation.Nullable; import androidx.annotation.RawRes; import androidx.annotation.StringRes; import androidx.preference.Preference; @@ -243,6 +244,14 @@ public class IllustrationPreference extends Preference { } /** + * Gets the content description set by {@link #setContentDescription}. + */ + @Nullable + public CharSequence getContentDescription() { + return mContentDescription; + } + + /** * Gets the lottie illustration resource id. */ public int getLottieAnimationResId() { diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt index 90cee163f8f3..1f3e24254027 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt @@ -25,6 +25,13 @@ object SettingsDimension { val paddingLarge = 16.dp val paddingExtraLarge = 24.dp + val spinnerHorizontalPadding = paddingExtraLarge + val spinnerVerticalPadding = paddingLarge + + val actionIconWidth = 32.dp + val actionIconHeight = 40.dp + val actionIconPadding = 4.dp + val itemIconSize = 24.dp val itemIconContainerSize = 72.dp val itemPaddingStart = paddingExtraLarge diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt index 5f320f7ade3f..9bbc16d56811 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt @@ -17,15 +17,24 @@ package com.android.settingslib.spa.widget.scaffold import androidx.appcompat.R +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.outlined.ArrowBack import androidx.compose.material.icons.outlined.Clear import androidx.compose.material.icons.outlined.FindInPage import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.res.stringResource import com.android.settingslib.spa.framework.compose.LocalNavController +import com.android.settingslib.spa.framework.theme.SettingsDimension +import com.android.settingslib.spa.framework.theme.SettingsShape +import com.android.settingslib.spa.framework.theme.isSpaExpressiveEnabled /** Action that navigates back to last page. */ @Composable @@ -50,6 +59,11 @@ private fun BackAction(contentDescription: String, onClick: () -> Unit) { Icon( imageVector = Icons.AutoMirrored.Outlined.ArrowBack, contentDescription = contentDescription, + modifier = if (isSpaExpressiveEnabled) Modifier + .size(SettingsDimension.actionIconWidth, SettingsDimension.actionIconHeight) + .clip(SettingsShape.CornerExtraLarge) + .background(MaterialTheme.colorScheme.onSurfaceVariant) + .padding(SettingsDimension.actionIconPadding) else Modifier ) } } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt index 4cf741e517be..7d8ee79b3344 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt @@ -39,6 +39,7 @@ import androidx.compose.ui.tooling.preview.Preview import com.android.settingslib.spa.framework.compose.horizontalValues import com.android.settingslib.spa.framework.compose.verticalValues import com.android.settingslib.spa.framework.theme.SettingsTheme +import com.android.settingslib.spa.framework.theme.isSpaExpressiveEnabled import com.android.settingslib.spa.framework.theme.settingsBackground import com.android.settingslib.spa.widget.preference.Preference import com.android.settingslib.spa.widget.preference.PreferenceModel @@ -55,6 +56,10 @@ fun SettingsScaffold( ) { ActivityTitle(title) val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() + if (isSpaExpressiveEnabled) { + LaunchedEffect(scrollBehavior.state.heightOffsetLimit) { scrollBehavior.collapse() } + } + Scaffold( modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { SettingsTopAppBar(title, scrollBehavior, actions) }, diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Spinner.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Spinner.kt index c48a1479555f..6b2db90c6b1f 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Spinner.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Spinner.kt @@ -46,6 +46,7 @@ import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.dp import com.android.settingslib.spa.framework.theme.SettingsDimension import com.android.settingslib.spa.framework.theme.SettingsTheme +import com.android.settingslib.spa.framework.theme.isSpaExpressiveEnabled data class SpinnerOption( val id: Int, @@ -70,7 +71,10 @@ fun Spinner(options: List<SpinnerOption>, selectedId: Int?, setId: (id: Int) -> ) .selectableGroup(), ) { - val contentPadding = PaddingValues(horizontal = SettingsDimension.itemPaddingEnd) + val contentPadding = if (isSpaExpressiveEnabled) PaddingValues( + horizontal = SettingsDimension.spinnerHorizontalPadding, + vertical = SettingsDimension.spinnerVerticalPadding + ) else PaddingValues(horizontal = SettingsDimension.itemPaddingEnd) Button( modifier = Modifier.semantics { role = Role.DropdownList }, onClick = { expanded = true }, @@ -129,7 +133,11 @@ private fun SpinnerText( text = option?.text ?: "", modifier = modifier .padding(end = SettingsDimension.itemPaddingEnd) - .padding(vertical = SettingsDimension.itemPaddingAround), + .then( + if (!isSpaExpressiveEnabled) + Modifier.padding(vertical = SettingsDimension.itemPaddingAround) + else Modifier + ), color = color, style = MaterialTheme.typography.labelLarge, ) diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt index 043219a65da4..c686708a3c18 100644 --- a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt +++ b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt @@ -61,6 +61,10 @@ class FakeZenModeRepository : ZenModeRepository { mutableModesFlow.value += zenModes } + fun addMode(mode: ZenMode) { + mutableModesFlow.value += mode + } + fun addMode(id: String, @AutomaticZenRule.Type type: Int = AutomaticZenRule.TYPE_UNKNOWN, active: Boolean = false) { mutableModesFlow.value += newMode(id, type, active) diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java index ca53fc2ba47e..3f3e1b280850 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java @@ -291,4 +291,12 @@ public class IllustrationPreferenceTest { assertThat(mPreference.isApplyDynamicColor()).isTrue(); } + + @Test + public void setContentDescription_getContentDescription_isEqual() { + final String contentDesc = "content desc"; + mPreference.setContentDescription(contentDesc); + + assertThat(mPreference.getContentDescription().toString()).isEqualTo(contentDesc); + } } diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 0b5187c44821..f3c5a186563d 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -941,6 +941,13 @@ <!-- Permission required for CTS test - FileIntegrityManagerTest --> <uses-permission android:name="android.permission.SETUP_FSVERITY" /> + <!-- Permissions required for CTS test - AppFunctionManagerTest --> + <uses-permission android:name="android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED" /> + <uses-permission android:name="android.permission.EXECUTE_APP_FUNCTIONS" /> + + <!-- Permission required for CTS test - CtsNfcTestCases --> + <uses-permission android:name="android.permission.NFC_SET_CONTROLLER_ALWAYS_ON" /> + <application android:label="@string/app_label" android:theme="@android:style/Theme.DeviceDefault.DayNight" diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_assistant_24dp.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_assistant.xml index 9c3417fde80a..6408a122a99e 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_assistant_24dp.xml +++ b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_assistant.xml @@ -1,6 +1,6 @@ <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" + android:width="108dp" + android:height="108dp" android:viewportWidth="24.0" android:viewportHeight="24.0" android:tint="@color/colorControlNormal"> diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_brightness_down_24dp.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_brightness_down.xml index a64a0d18acd1..f13239cf78fb 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_brightness_down_24dp.xml +++ b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_brightness_down.xml @@ -1,6 +1,6 @@ <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" + android:width="108dp" + android:height="108dp" android:viewportWidth="24.0" android:viewportHeight="24.0" android:tint="@color/colorControlNormal"> diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_brightness_up_24dp.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_brightness_up.xml index 40423c7c35dd..a5d15f96759f 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_brightness_up_24dp.xml +++ b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_brightness_up.xml @@ -15,8 +15,8 @@ --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" + android:width="108dp" + android:height="108dp" android:viewportWidth="24.0" android:viewportHeight="24.0" android:tint="@color/colorControlNormal"> diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_lock_24dp.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_lock.xml index a0f7b5d04379..212763283718 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_lock_24dp.xml +++ b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_lock.xml @@ -1,6 +1,6 @@ <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" + android:width="108dp" + android:height="108dp" android:viewportWidth="24.0" android:viewportHeight="24.0" android:tint="@color/colorControlNormal"> diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_notifications_24dp.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_notifications.xml index 8757f22f2af6..62c8d1ddaba2 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_notifications_24dp.xml +++ b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_notifications.xml @@ -1,6 +1,6 @@ <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" + android:width="108dp" + android:height="108dp" android:viewportWidth="24.0" android:viewportHeight="24.0" android:tint="@color/colorControlNormal"> diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_power_24dp.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_power.xml index 049013aa9763..ed11b44cc25a 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_power_24dp.xml +++ b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_power.xml @@ -1,6 +1,6 @@ <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" + android:width="108dp" + android:height="108dp" android:viewportWidth="24.0" android:viewportHeight="24.0" android:tint="@color/colorControlNormal"> diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_quick_settings_24dp.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_quick_settings.xml index 4f25e7d80bd6..2da63a61e84e 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_quick_settings_24dp.xml +++ b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_quick_settings.xml @@ -15,8 +15,8 @@ --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" + android:width="108dp" + android:height="108dp" android:viewportWidth="24.0" android:viewportHeight="24.0" android:tint="@color/colorControlNormal"> diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_recent_apps_24dp.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_recent_apps.xml index 38234c05d591..9763b8eefbe2 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_recent_apps_24dp.xml +++ b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_recent_apps.xml @@ -15,8 +15,8 @@ --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" + android:width="108dp" + android:height="108dp" android:viewportWidth="24" android:viewportHeight="24" android:tint="@color/colorControlNormal"> diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_screenshot_24dp.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_screenshot.xml index 6d7f49cf6919..2bfbd5b48991 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_screenshot_24dp.xml +++ b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_screenshot.xml @@ -15,8 +15,8 @@ --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" + android:width="108dp" + android:height="108dp" android:viewportWidth="24.0" android:viewportHeight="24.0" android:tint="@color/colorControlNormal"> diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_settings_24dp.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_settings.xml index 5ed6f19051bc..4ca9bfc7900d 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_settings_24dp.xml +++ b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_settings.xml @@ -15,8 +15,8 @@ --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" + android:width="108dp" + android:height="108dp" android:viewportWidth="24.0" android:viewportHeight="24.0" android:tint="@color/colorControlNormal"> diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_volume_down_24dp.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_volume_down.xml index 16653e8e9d3e..f924e5eb9493 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_volume_down_24dp.xml +++ b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_volume_down.xml @@ -15,8 +15,8 @@ --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" + android:width="108dp" + android:height="108dp" android:viewportWidth="24.0" android:viewportHeight="24.0" android:tint="@color/colorControlNormal"> diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_volume_up_24dp.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_volume_up.xml index e572c6adea33..41fe35184a2a 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_volume_up_24dp.xml +++ b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_volume_up.xml @@ -15,8 +15,8 @@ --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" + android:width="108dp" + android:height="108dp" android:viewportWidth="24.0" android:viewportHeight="24.0" android:tint="@color/colorControlNormal"> diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/menuitem_background_ripple.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/menuitem_background_ripple.xml new file mode 100644 index 000000000000..6cab464f0a9c --- /dev/null +++ b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/menuitem_background_ripple.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2024 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<ripple xmlns:android="http://schemas.android.com/apk/res/android" + android:color="@color/ripple_material_color" />
\ No newline at end of file diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/layout/grid_item.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/layout/grid_item.xml index 3c73eca732a1..a1130e6e2285 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/res/layout/grid_item.xml +++ b/packages/SystemUI/accessibility/accessibilitymenu/res/layout/grid_item.xml @@ -12,7 +12,8 @@ android:layout_height="@dimen/image_button_height" android:layout_alignParentTop="true" android:layout_centerHorizontal="true" - android:scaleType="fitCenter"/> + android:scaleType="fitCenter" + android:background="@drawable/menuitem_background_ripple" /> <TextView android:id="@+id/shortcutLabel" diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/model/A11yMenuShortcut.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/model/A11yMenuShortcut.java index c698d18bfde8..11ce41ee4d96 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/model/A11yMenuShortcut.java +++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/model/A11yMenuShortcut.java @@ -53,73 +53,73 @@ public class A11yMenuShortcut { /** Map stores all shortcut resource IDs that is in matching order of defined shortcut. */ private static final Map<ShortcutId, int[]> sShortcutResource = Map.ofEntries( Map.entry(ShortcutId.ID_ASSISTANT_VALUE, new int[] { - R.drawable.ic_logo_a11y_assistant_24dp, + R.drawable.ic_logo_a11y_assistant, R.color.assistant_color, R.string.assistant_utterance, R.string.assistant_label, }), Map.entry(ShortcutId.ID_A11YSETTING_VALUE, new int[] { - R.drawable.ic_logo_a11y_settings_24dp, + R.drawable.ic_logo_a11y_settings, R.color.a11y_settings_color, R.string.a11y_settings_label, R.string.a11y_settings_label, }), Map.entry(ShortcutId.ID_POWER_VALUE, new int[] { - R.drawable.ic_logo_a11y_power_24dp, + R.drawable.ic_logo_a11y_power, R.color.power_color, R.string.power_utterance, R.string.power_label, }), Map.entry(ShortcutId.ID_RECENT_VALUE, new int[] { - R.drawable.ic_logo_a11y_recent_apps_24dp, + R.drawable.ic_logo_a11y_recent_apps, R.color.recent_apps_color, R.string.recent_apps_label, R.string.recent_apps_label, }), Map.entry(ShortcutId.ID_LOCKSCREEN_VALUE, new int[] { - R.drawable.ic_logo_a11y_lock_24dp, + R.drawable.ic_logo_a11y_lock, R.color.lockscreen_color, R.string.lockscreen_label, R.string.lockscreen_label, }), Map.entry(ShortcutId.ID_QUICKSETTING_VALUE, new int[] { - R.drawable.ic_logo_a11y_quick_settings_24dp, + R.drawable.ic_logo_a11y_quick_settings, R.color.quick_settings_color, R.string.quick_settings_label, R.string.quick_settings_label, }), Map.entry(ShortcutId.ID_NOTIFICATION_VALUE, new int[] { - R.drawable.ic_logo_a11y_notifications_24dp, + R.drawable.ic_logo_a11y_notifications, R.color.notifications_color, R.string.notifications_label, R.string.notifications_label, }), Map.entry(ShortcutId.ID_SCREENSHOT_VALUE, new int[] { - R.drawable.ic_logo_a11y_screenshot_24dp, + R.drawable.ic_logo_a11y_screenshot, R.color.screenshot_color, R.string.screenshot_utterance, R.string.screenshot_label, }), Map.entry(ShortcutId.ID_BRIGHTNESS_UP_VALUE, new int[] { - R.drawable.ic_logo_a11y_brightness_up_24dp, + R.drawable.ic_logo_a11y_brightness_up, R.color.brightness_color, R.string.brightness_up_label, R.string.brightness_up_label, }), Map.entry(ShortcutId.ID_BRIGHTNESS_DOWN_VALUE, new int[] { - R.drawable.ic_logo_a11y_brightness_down_24dp, + R.drawable.ic_logo_a11y_brightness_down, R.color.brightness_color, R.string.brightness_down_label, R.string.brightness_down_label, }), Map.entry(ShortcutId.ID_VOLUME_UP_VALUE, new int[] { - R.drawable.ic_logo_a11y_volume_up_24dp, + R.drawable.ic_logo_a11y_volume_up, R.color.volume_color, R.string.volume_up_label, R.string.volume_up_label, }), Map.entry(ShortcutId.ID_VOLUME_DOWN_VALUE, new int[] { - R.drawable.ic_logo_a11y_volume_down_24dp, + R.drawable.ic_logo_a11y_volume_down, R.color.volume_color, R.string.volume_down_label, R.string.volume_down_label, diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/utils/ShortcutDrawableUtils.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/utils/ShortcutDrawableUtils.java deleted file mode 100644 index 28ba4b54107f..000000000000 --- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/utils/ShortcutDrawableUtils.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.systemui.accessibility.accessibilitymenu.utils; - -import android.content.Context; -import android.content.res.ColorStateList; -import android.graphics.Bitmap; -import android.graphics.Bitmap.Config; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.Paint.Style; -import android.graphics.drawable.AdaptiveIconDrawable; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.LayerDrawable; -import android.graphics.drawable.RippleDrawable; - -import com.android.systemui.accessibility.accessibilitymenu.R; - -/** Creates background drawable for a11y menu shortcut. */ -public class ShortcutDrawableUtils { - - /** - * To make the circular background of shortcut icons have higher resolution. The higher value of - * LENGTH is, the higher resolution of the circular background are. - */ - private static final int LENGTH = 480; - - private static final int RADIUS = LENGTH / 2; - private static final int COORDINATE = LENGTH / 2; - private static final int RIPPLE_COLOR_ID = R.color.ripple_material_color; - - private final Context mContext; - private final ColorStateList mRippleColorStateList; - - // Placeholder of drawable to prevent NullPointerException - private final ColorDrawable mTransparentDrawable = new ColorDrawable(Color.TRANSPARENT); - - public ShortcutDrawableUtils(Context context) { - this.mContext = context; - - int rippleColor = context.getColor(RIPPLE_COLOR_ID); - mRippleColorStateList = ColorStateList.valueOf(rippleColor); - } - - /** - * Creates a circular drawable in specific color for shortcut. - * - * @param colorResId color resource ID - * @return drawable circular drawable - */ - public Drawable createCircularDrawable(int colorResId) { - Bitmap output = Bitmap.createBitmap(LENGTH, LENGTH, Config.ARGB_8888); - Canvas canvas = new Canvas(output); - int color = mContext.getColor(colorResId); - Paint paint = new Paint(); - paint.setColor(color); - paint.setStrokeCap(Paint.Cap.ROUND); - paint.setStyle(Style.FILL); - canvas.drawCircle(COORDINATE, COORDINATE, RADIUS, paint); - - BitmapDrawable drawable = new BitmapDrawable(mContext.getResources(), output); - return drawable; - } - - /** - * Creates an adaptive icon drawable in specific color for shortcut. - * - * @param colorResId color resource ID - * @return drawable for adaptive icon - */ - public Drawable createAdaptiveIconDrawable(int colorResId) { - Drawable circleLayer = createCircularDrawable(colorResId); - RippleDrawable rippleLayer = new RippleDrawable(mRippleColorStateList, null, null); - - AdaptiveIconDrawable adaptiveIconDrawable = - new AdaptiveIconDrawable(circleLayer, mTransparentDrawable); - - Drawable[] layers = {adaptiveIconDrawable, rippleLayer}; - LayerDrawable drawable = new LayerDrawable(layers); - return drawable; - } -} diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java index c333a7a5e33e..aa1bbbdada65 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java +++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java @@ -16,7 +16,12 @@ package com.android.systemui.accessibility.accessibilitymenu.view; +import android.content.res.Resources; import android.graphics.Rect; +import android.graphics.drawable.AdaptiveIconDrawable; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.InsetDrawable; import android.view.LayoutInflater; import android.view.TouchDelegate; import android.view.View; @@ -26,11 +31,12 @@ import android.widget.BaseAdapter; import android.widget.ImageButton; import android.widget.TextView; +import androidx.annotation.NonNull; + import com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService; import com.android.systemui.accessibility.accessibilitymenu.R; import com.android.systemui.accessibility.accessibilitymenu.activity.A11yMenuSettingsActivity.A11yMenuPreferenceFragment; import com.android.systemui.accessibility.accessibilitymenu.model.A11yMenuShortcut; -import com.android.systemui.accessibility.accessibilitymenu.utils.ShortcutDrawableUtils; import java.util.List; @@ -43,16 +49,12 @@ public class A11yMenuAdapter extends BaseAdapter { private final AccessibilityMenuService mService; private final List<A11yMenuShortcut> mShortcutDataList; - private final ShortcutDrawableUtils mShortcutDrawableUtils; public A11yMenuAdapter( AccessibilityMenuService service, List<A11yMenuShortcut> shortcutDataList) { this.mService = service; this.mShortcutDataList = shortcutDataList; - - mShortcutDrawableUtils = new ShortcutDrawableUtils(service); - mLargeTextSize = service.getResources().getDimensionPixelOffset(R.dimen.large_label_text_size); } @@ -152,10 +154,10 @@ public class A11yMenuAdapter extends BaseAdapter { shortcutIconButton.setContentDescription( mService.getString(shortcutItem.imgContentDescription)); shortcutLabel.setText(shortcutItem.labelText); - shortcutIconButton.setImageResource(shortcutItem.imageSrc); - shortcutIconButton.setBackground( - mShortcutDrawableUtils.createAdaptiveIconDrawable(shortcutItem.imageColor)); + AdaptiveIconDrawable iconDrawable = getAdaptiveIconDrawable(convertView, + shortcutItem); + shortcutIconButton.setImageDrawable(iconDrawable); shortcutIconButton.setAccessibilityDelegate(new View.AccessibilityDelegate() { @Override @@ -167,4 +169,18 @@ public class A11yMenuAdapter extends BaseAdapter { }); } } + + @NonNull + private static AdaptiveIconDrawable getAdaptiveIconDrawable(@NonNull View convertView, + @NonNull A11yMenuShortcut shortcutItem) { + Resources resources = convertView.getResources(); + // Note: from the official guide, the foreground image of the adaptive icon should be + // sized at 108 x 108 dp + Drawable icon = resources.getDrawable(shortcutItem.imageSrc); + float inset = AdaptiveIconDrawable.getExtraInsetFraction(); + AdaptiveIconDrawable iconDrawable = new AdaptiveIconDrawable( + new ColorDrawable(resources.getColor(shortcutItem.imageColor)), + new InsetDrawable(icon, inset)); + return iconDrawable; + } } diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuViewPager.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuViewPager.java index 35f124874597..b899c45b0f7e 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuViewPager.java +++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuViewPager.java @@ -306,6 +306,10 @@ public class A11yMenuViewPager { (viewPagerHeight - topMargin - defaultMargin - (rowsInGridView * gridItemHeight)) / (rowsInGridView + 1); + // The interval is negative number when the viewPagerHeight is not able to fit + // the grid items, which result in text overlapping. + // Adjust the interval to 0 could solve the issue. + interval = Math.max(interval, 0); mViewPagerAdapter.setVerticalSpacing(interval); // Sets padding to view pager. diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index d1a59af1c471..892f778ff8c0 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -1396,3 +1396,9 @@ flag { } } +flag { + name: "non_touchscreen_devices_bypass_falsing" + namespace: "systemui" + description: "Allow non-touchscreen devices to bypass falsing" + bug: "319809270" +}
\ No newline at end of file diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt index ed1277666372..a4dc8fc565f6 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt @@ -216,7 +216,7 @@ fun CommunalContainer( /** Scene containing the glanceable hub UI. */ @Composable -private fun SceneScope.CommunalScene( +fun SceneScope.CommunalScene( backgroundType: CommunalBackgroundType, colors: CommunalColors, content: CommunalContent, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt index 201c419941c5..c63b29dd9051 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt @@ -65,6 +65,7 @@ import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridItemSpan import androidx.compose.foundation.lazy.grid.LazyGridState import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid +import androidx.compose.foundation.lazy.grid.itemsIndexed import androidx.compose.foundation.lazy.grid.rememberLazyGridState import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.selection.selectable @@ -136,6 +137,7 @@ import androidx.compose.ui.semantics.clearAndSetSemantics import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.customActions import androidx.compose.ui.semantics.onClick +import androidx.compose.ui.semantics.paneTitle import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.testTagsAsResourceId import androidx.compose.ui.text.style.TextAlign @@ -240,10 +242,15 @@ fun CommunalHub( } } + val paneTitle = stringResource(R.string.accessibility_content_description_for_communal_hub) + Box( modifier = modifier - .semantics { testTagsAsResourceId = true } + .semantics { + testTagsAsResourceId = true + this.paneTitle = paneTitle + } .testTag(COMMUNAL_HUB_TEST_TAG) .fillMaxSize() // Observe taps for selecting items @@ -690,21 +697,20 @@ private fun BoxScope.CommunalHubLazyGrid( horizontalArrangement = Arrangement.spacedBy(Dimensions.ItemSpacing), verticalArrangement = Arrangement.spacedBy(Dimensions.ItemSpacing), ) { - items( - count = list.size, - key = { index -> list[index].key }, - contentType = { index -> list[index].key }, - span = { index -> GridItemSpan(list[index].size.span) }, - ) { index -> + itemsIndexed( + items = list, + key = { _, item -> item.key }, + contentType = { _, item -> item.key }, + span = { _, item -> GridItemSpan(item.size.span) }, + ) { index, item -> val size = SizeF( Dimensions.CardWidth.value, - list[index].size.dp().value, + item.size.dp().value, ) val cardModifier = Modifier.requiredSize(width = size.width.dp, height = size.height.dp) if (viewModel.isEditMode && dragDropState != null) { - val selected by - remember(index) { derivedStateOf { list[index].key == selectedKey.value } } + val selected = item.key == selectedKey.value DraggableItem( modifier = if (dragDropState.draggingItemIndex == index) { @@ -716,12 +722,12 @@ private fun BoxScope.CommunalHubLazyGrid( }, dragDropState = dragDropState, selected = selected, - enabled = list[index].isWidgetContent(), + enabled = item.isWidgetContent(), index = index, ) { isDragging -> CommunalContent( modifier = cardModifier, - model = list[index], + model = item, viewModel = viewModel, size = size, selected = selected && !isDragging, @@ -734,7 +740,7 @@ private fun BoxScope.CommunalHubLazyGrid( } } else { CommunalContent( - model = list[index], + model = item, viewModel = viewModel, size = size, selected = false, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt index 8b6de6ab22f3..e41a7df39c21 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt @@ -17,20 +17,21 @@ package com.android.systemui.communal.ui.compose import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.SceneScope import com.android.compose.animation.scene.Swipe import com.android.compose.animation.scene.SwipeDirection import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult -import com.android.systemui.communal.ui.view.layout.sections.CommunalAppWidgetSection +import com.android.systemui.communal.shared.model.CommunalBackgroundType import com.android.systemui.communal.ui.viewmodel.CommunalViewModel -import com.android.systemui.communal.widgets.WidgetInteractionHandler +import com.android.systemui.communal.util.CommunalColors import com.android.systemui.dagger.SysUISingleton import com.android.systemui.lifecycle.ExclusiveActivatable import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.ui.composable.Scene -import com.android.systemui.statusbar.phone.SystemUIDialogFactory import javax.inject.Inject import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.flow.Flow @@ -43,9 +44,8 @@ class CommunalScene @Inject constructor( private val viewModel: CommunalViewModel, - private val dialogFactory: SystemUIDialogFactory, - private val interactionHandler: WidgetInteractionHandler, - private val widgetSection: CommunalAppWidgetSection, + private val communalColors: CommunalColors, + private val communalContent: CommunalContent, ) : ExclusiveActivatable(), Scene { override val key = Scenes.Communal @@ -63,12 +63,17 @@ constructor( @Composable override fun SceneScope.Content(modifier: Modifier) { - CommunalHub( - modifier = modifier, + val backgroundType by + viewModel.communalBackground.collectAsStateWithLifecycle( + initialValue = CommunalBackgroundType.ANIMATED + ) + + CommunalScene( + backgroundType = backgroundType, + colors = communalColors, + content = communalContent, viewModel = viewModel, - interactionHandler = interactionHandler, - widgetSection = widgetSection, - dialogFactory = dialogFactory, + modifier = modifier.horizontalNestedScrollToScene(), ) } } 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 ecb3d8cb04be..c25a45dc5cf6 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 @@ -52,6 +52,7 @@ import com.android.systemui.keyguard.ui.view.DeviceEntryIconView import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerDependencies import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerMessageAreaViewModel import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerUdfpsIconViewModel +import com.android.systemui.log.LongPressHandlingViewLogger import com.android.systemui.res.R import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -97,6 +98,7 @@ fun AlternateBouncer( Box { DeviceEntryIcon( viewModel = alternateBouncerDependencies.udfpsIconViewModel, + logger = alternateBouncerDependencies.logger, modifier = Modifier.width { udfpsLocation.width } .height { udfpsLocation.height } @@ -151,13 +153,14 @@ private fun StatusMessage( @Composable private fun DeviceEntryIcon( viewModel: AlternateBouncerUdfpsIconViewModel, + logger: LongPressHandlingViewLogger, modifier: Modifier = Modifier, ) { AndroidView( modifier = modifier, factory = { context -> val view = - DeviceEntryIconView(context, null).apply { + DeviceEntryIconView(context, null, logger = logger).apply { id = R.id.alternate_bouncer_udfps_icon_view contentDescription = context.resources.getString(R.string.accessibility_fingerprint_label) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt index 4129c25901e5..a525f36c71ce 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt @@ -18,7 +18,6 @@ package com.android.systemui.keyguard.ui.composable.section import android.content.Context import android.util.DisplayMetrics -import android.view.View import android.view.WindowManager import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -45,9 +44,11 @@ import com.android.systemui.keyguard.ui.view.DeviceEntryIconView import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryBackgroundViewModel import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryForegroundViewModel import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LongPressHandlingViewLogger +import com.android.systemui.log.dagger.LongPressTouchLog import com.android.systemui.plugins.FalsingManager import com.android.systemui.res.R -import com.android.systemui.shade.NotificationPanelView import com.android.systemui.statusbar.VibratorHelper import dagger.Lazy import javax.inject.Inject @@ -66,7 +67,7 @@ constructor( private val deviceEntryBackgroundViewModel: Lazy<DeviceEntryBackgroundViewModel>, private val falsingManager: Lazy<FalsingManager>, private val vibratorHelper: Lazy<VibratorHelper>, - private val notificationPanelView: NotificationPanelView, + @LongPressTouchLog private val logBuffer: LogBuffer, ) { @Composable fun SceneScope.LockIcon(overrideColor: Color? = null, modifier: Modifier = Modifier) { @@ -74,29 +75,30 @@ constructor( return } - notificationPanelView.findViewById<View?>(R.id.lock_icon_view)?.let { - notificationPanelView.removeView(it) - } - val context = LocalContext.current AndroidView( factory = { context -> val view = if (DeviceEntryUdfpsRefactor.isEnabled) { - DeviceEntryIconView(context, null).apply { - id = R.id.device_entry_icon_view - DeviceEntryIconViewBinder.bind( - applicationScope, - this, - deviceEntryIconViewModel.get(), - deviceEntryForegroundViewModel.get(), - deviceEntryBackgroundViewModel.get(), - falsingManager.get(), - vibratorHelper.get(), - overrideColor, + DeviceEntryIconView( + context, + null, + logger = LongPressHandlingViewLogger(logBuffer, tag = TAG) ) - } + .apply { + id = R.id.device_entry_icon_view + DeviceEntryIconViewBinder.bind( + applicationScope, + this, + deviceEntryIconViewModel.get(), + deviceEntryForegroundViewModel.get(), + deviceEntryBackgroundViewModel.get(), + falsingManager.get(), + vibratorHelper.get(), + overrideColor, + ) + } } else { // KeyguardBottomAreaRefactor.isEnabled LockIconView(context, null).apply { @@ -185,6 +187,10 @@ constructor( return IntRect(center, radius) } + + companion object { + private const val TAG = "LockSection" + } } private val LockIconElementKey = ElementKey("LockIcon") diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt index 4b3a39b367c9..897a8613263f 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt @@ -23,11 +23,13 @@ import androidx.compose.foundation.layout.offset import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import androidx.compose.ui.input.nestedscroll.NestedScrollSource import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.unit.IntOffset import com.android.compose.nestedscroll.PriorityNestedScrollConnection +import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight +import kotlin.math.max import kotlin.math.roundToInt +import kotlin.math.tanh import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -36,6 +38,7 @@ fun Modifier.stackVerticalOverscroll( coroutineScope: CoroutineScope, canScrollForward: () -> Boolean ): Modifier { + val screenHeight = LocalRawScreenHeight.current val overscrollOffset = remember { Animatable(0f) } val stackNestedScrollConnection = remember { NotificationStackNestedScrollConnection( @@ -43,7 +46,13 @@ fun Modifier.stackVerticalOverscroll( canScrollForward = canScrollForward, onScroll = { offsetAvailable -> coroutineScope.launch { - overscrollOffset.snapTo(overscrollOffset.value + offsetAvailable * 0.3f) + val maxProgress = screenHeight * 0.2f + val tilt = 3f + var offset = + overscrollOffset.value + + maxProgress * tanh(x = offsetAvailable / (maxProgress * tilt)) + offset = max(offset, -1f * maxProgress) + overscrollOffset.snapTo(offset) } }, onStop = { velocityAvailable -> @@ -79,13 +88,7 @@ fun NotificationStackNestedScrollConnection( offsetAvailable < 0f && offsetBeforeStart < 0f && !canScrollForward() }, canStartPostFling = { velocityAvailable -> velocityAvailable < 0f && !canScrollForward() }, - canContinueScroll = { source -> - if (source == NestedScrollSource.SideEffect) { - stackOffset() > STACK_OVERSCROLL_FLING_MIN_OFFSET - } else { - true - } - }, + canContinueScroll = { stackOffset() > 0f }, canScrollOnFling = true, onStart = { offsetAvailable -> onStart(offsetAvailable) }, onScroll = { offsetAvailable -> diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt index a2beba849b89..91ecfc18a76e 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt @@ -667,4 +667,3 @@ private val DEBUG_HUN_COLOR = Color(0f, 0f, 1f, 0.2f) private val DEBUG_BOX_COLOR = Color(0f, 1f, 0f, 0.2f) private const val HUN_SNOOZE_POSITIONAL_THRESHOLD_FRACTION = 0.25f private const val HUN_SNOOZE_VELOCITY_THRESHOLD = -70f -internal const val STACK_OVERSCROLL_FLING_MIN_OFFSET = -100f diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToCommunalTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToCommunalTransition.kt index 54019364c401..826a2550cca3 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToCommunalTransition.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToCommunalTransition.kt @@ -19,14 +19,19 @@ package com.android.systemui.scene.ui.composable.transitions import androidx.compose.animation.core.tween import com.android.compose.animation.scene.Edge import com.android.compose.animation.scene.TransitionBuilder +import com.android.systemui.communal.ui.compose.AllElements +import com.android.systemui.communal.ui.compose.Communal import com.android.systemui.scene.shared.model.Scenes fun TransitionBuilder.lockscreenToCommunalTransition() { - spec = tween(durationMillis = 500) + spec = tween(durationMillis = 1000) - // Translate lockscreen to the left. + // Translate lockscreen to the start direction. translate(Scenes.Lockscreen.rootElementKey, Edge.Start) - // Translate communal from the right. - translate(Scenes.Communal.rootElementKey, Edge.End) + // Translate communal hub grid from the end direction. + translate(Communal.Elements.Grid, Edge.End) + + // Fade all communal hub elements. + timestampRange(startMillis = 167, endMillis = 334) { fade(AllElements) } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt index 24fef711d397..f3577fab8686 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt @@ -190,14 +190,12 @@ internal class DraggableHandlerImpl( private fun computeSwipes(startedPosition: Offset?, pointersDown: Int): Swipes { val fromSource = startedPosition?.let { position -> - layoutImpl.swipeSourceDetector - .source( - layoutImpl.lastSize, - position.round(), - layoutImpl.density, - orientation, - ) - ?.resolve(layoutImpl.layoutDirection) + layoutImpl.swipeSourceDetector.source( + layoutImpl.lastSize, + position.round(), + layoutImpl.density, + orientation, + ) } val upOrLeft = diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt index 97c0cef30388..edd697bd7068 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt @@ -54,23 +54,23 @@ class FixedSizeEdgeDetector(val size: Dp) : SwipeSourceDetector { position: IntOffset, density: Density, orientation: Orientation, - ): Edge? { + ): Edge.Resolved? { val axisSize: Int val axisPosition: Int - val topOrLeft: Edge - val bottomOrRight: Edge + val topOrLeft: Edge.Resolved + val bottomOrRight: Edge.Resolved when (orientation) { Orientation.Horizontal -> { axisSize = layoutSize.width axisPosition = position.x - topOrLeft = Edge.Left - bottomOrRight = Edge.Right + topOrLeft = Edge.Resolved.Left + bottomOrRight = Edge.Resolved.Right } Orientation.Vertical -> { axisSize = layoutSize.height axisPosition = position.y - topOrLeft = Edge.Top - bottomOrRight = Edge.Bottom + topOrLeft = Edge.Resolved.Top + bottomOrRight = Edge.Resolved.Bottom } } 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 061613f999c6..004bb40bb0ad 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 @@ -379,6 +379,10 @@ sealed class UserAction { return this to UserActionResult(toScene = scene) } + infix fun to(overlay: OverlayKey): Pair<UserAction, UserActionResult> { + return this to UserActionResult(toOverlay = overlay) + } + /** Resolve this into a [Resolved] user action given [layoutDirection]. */ internal abstract fun resolve(layoutDirection: LayoutDirection): Resolved @@ -475,7 +479,7 @@ interface SwipeSourceDetector { position: IntOffset, density: Density, orientation: Orientation, - ): SwipeSource? + ): SwipeSource.Resolved? } /** The result of performing a [UserAction]. */ diff --git a/packages/SystemUI/compose/scene/src/com/android/systemui/communal/ui/compose/CommunalSwipeDetector.kt b/packages/SystemUI/compose/scene/src/com/android/systemui/communal/ui/compose/CommunalSwipeDetector.kt index 3fda9b85a5c2..41b015a2ede8 100644 --- a/packages/SystemUI/compose/scene/src/com/android/systemui/communal/ui/compose/CommunalSwipeDetector.kt +++ b/packages/SystemUI/compose/scene/src/com/android/systemui/communal/ui/compose/CommunalSwipeDetector.kt @@ -33,7 +33,7 @@ import kotlin.math.abs * SwipeSourceDetector} to enable fullscreen swipe handling to transition to and from the glanceable * hub. */ -class CommunalSwipeDetector(private var lastDirection: SwipeSource? = null) : +class CommunalSwipeDetector(private var lastDirection: SwipeSource.Resolved? = null) : SwipeSourceDetector, SwipeDetector { companion object { private const val TRAVEL_RATIO_THRESHOLD = .5f @@ -44,15 +44,15 @@ class CommunalSwipeDetector(private var lastDirection: SwipeSource? = null) : position: IntOffset, density: Density, orientation: Orientation - ): SwipeSource? { + ): SwipeSource.Resolved? { return lastDirection } override fun detectSwipe(change: PointerInputChange): Boolean { if (change.positionChange().x > 0) { - lastDirection = Edge.Left + lastDirection = Edge.Resolved.Left } else { - lastDirection = Edge.Right + lastDirection = Edge.Resolved.Right } // Determine whether the ratio of the distance traveled horizontally to the distance diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/FixedSizeEdgeDetectorTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/FixedSizeEdgeDetectorTest.kt index cceaf57ca82b..dea9283c985c 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/FixedSizeEdgeDetectorTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/FixedSizeEdgeDetectorTest.kt @@ -34,7 +34,7 @@ class FixedSizeEdgeDetectorTest { @Test fun horizontalEdges() { - fun horizontalEdge(position: Int): Edge? = + fun horizontalEdge(position: Int): Edge.Resolved? = detector.source( layoutSize, position = IntOffset(position, 0), @@ -42,17 +42,17 @@ class FixedSizeEdgeDetectorTest { Orientation.Horizontal, ) - assertThat(horizontalEdge(0)).isEqualTo(Edge.Left) - assertThat(horizontalEdge(30)).isEqualTo(Edge.Left) + assertThat(horizontalEdge(0)).isEqualTo(Edge.Resolved.Left) + assertThat(horizontalEdge(30)).isEqualTo(Edge.Resolved.Left) assertThat(horizontalEdge(31)).isEqualTo(null) assertThat(horizontalEdge(69)).isEqualTo(null) - assertThat(horizontalEdge(70)).isEqualTo(Edge.Right) - assertThat(horizontalEdge(100)).isEqualTo(Edge.Right) + assertThat(horizontalEdge(70)).isEqualTo(Edge.Resolved.Right) + assertThat(horizontalEdge(100)).isEqualTo(Edge.Resolved.Right) } @Test fun verticalEdges() { - fun verticalEdge(position: Int): Edge? = + fun verticalEdge(position: Int): Edge.Resolved? = detector.source( layoutSize, position = IntOffset(0, position), @@ -60,11 +60,11 @@ class FixedSizeEdgeDetectorTest { Orientation.Vertical, ) - assertThat(verticalEdge(0)).isEqualTo(Edge.Top) - assertThat(verticalEdge(30)).isEqualTo(Edge.Top) + assertThat(verticalEdge(0)).isEqualTo(Edge.Resolved.Top) + assertThat(verticalEdge(30)).isEqualTo(Edge.Resolved.Top) assertThat(verticalEdge(31)).isEqualTo(null) assertThat(verticalEdge(69)).isEqualTo(null) - assertThat(verticalEdge(70)).isEqualTo(Edge.Bottom) - assertThat(verticalEdge(100)).isEqualTo(Edge.Bottom) + assertThat(verticalEdge(70)).isEqualTo(Edge.Resolved.Bottom) + assertThat(verticalEdge(100)).isEqualTo(Edge.Resolved.Bottom) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt index e2bdc49d590c..bb152086cdab 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt @@ -30,10 +30,12 @@ import com.android.systemui.classifier.FalsingCollector import com.android.systemui.classifier.FalsingCollectorFake import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags +import com.android.systemui.haptics.msdl.msdlPlayer import com.android.systemui.res.R import com.android.systemui.statusbar.policy.DevicePostureController import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED +import com.android.systemui.testKosmos import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.whenever @@ -89,6 +91,9 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() { @Captor lateinit var postureCallbackCaptor: ArgumentCaptor<DevicePostureController.Callback> + private val kosmos = testKosmos() + private val msdlPlayer = kosmos.msdlPlayer + @Before fun setup() { MockitoAnnotations.initMocks(this) @@ -112,7 +117,8 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() { mKeyguardMessageAreaControllerFactory, mPostureController, fakeFeatureFlags, - mSelectedUserInteractor + mSelectedUserInteractor, + msdlPlayer, ) mKeyguardPatternView.onAttachedToWindow() } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManagerTest.java index 09aa286874b9..ca23228459e4 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManagerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManagerTest.java @@ -17,11 +17,11 @@ package com.android.systemui.accessibility.hearingaid; import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.bluetooth.BluetoothDevice; import android.testing.TestableLooper; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -51,6 +51,8 @@ public class HearingDevicesDialogManagerTest extends SysuiTestCase { @Rule public MockitoRule mockito = MockitoJUnit.rule(); + private static final int TEST_LAUNCH_SOURCE_ID = 1; + private final FakeExecutor mMainExecutor = new FakeExecutor(new FakeSystemClock()); private final FakeExecutor mBackgroundExecutor = new FakeExecutor(new FakeSystemClock()); @Mock @@ -70,7 +72,7 @@ public class HearingDevicesDialogManagerTest extends SysuiTestCase { @Before public void setUp() { - when(mDialogFactory.create(anyBoolean())).thenReturn(mDialogDelegate); + when(mDialogFactory.create(anyBoolean(), anyInt())).thenReturn(mDialogDelegate); when(mDialogDelegate.createDialog()).thenReturn(mDialog); mManager = new HearingDevicesDialogManager( @@ -86,21 +88,22 @@ public class HearingDevicesDialogManagerTest extends SysuiTestCase { public void showDialog_existHearingDevice_showPairNewDeviceFalse() { when(mDevicesChecker.isAnyPairedHearingDevice()).thenReturn(true); - mManager.showDialog(mExpandable); + mManager.showDialog(mExpandable, TEST_LAUNCH_SOURCE_ID); mBackgroundExecutor.runAllReady(); mMainExecutor.runAllReady(); - verify(mDialogFactory).create(eq(/* showPairNewDevice= */ false)); + verify(mDialogFactory).create(eq(/* showPairNewDevice= */ false), + eq(TEST_LAUNCH_SOURCE_ID)); } @Test public void showDialog_noHearingDevice_showPairNewDeviceTrue() { when(mDevicesChecker.isAnyPairedHearingDevice()).thenReturn(false); - mManager.showDialog(mExpandable); + mManager.showDialog(mExpandable, TEST_LAUNCH_SOURCE_ID); mBackgroundExecutor.runAllReady(); mMainExecutor.runAllReady(); - verify(mDialogFactory).create(eq(/* showPairNewDevice= */ true)); + verify(mDialogFactory).create(eq(/* showPairNewDevice= */ true), eq(TEST_LAUNCH_SOURCE_ID)); } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java index b7d99d2754fa..65825b2444af 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java @@ -97,6 +97,8 @@ import com.android.systemui.util.concurrency.FakeExecution; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; +import com.google.android.msdl.domain.MSDLPlayer; + import dagger.Lazy; import org.junit.Before; @@ -185,6 +187,8 @@ public class AuthControllerTest extends SysuiTestCase { private Resources mResources; @Mock private VibratorHelper mVibratorHelper; + @Mock + private MSDLPlayer mMSDLPlayer; private TestableContext mContextSpy; private Execution mExecution; @@ -1066,7 +1070,7 @@ public class AuthControllerTest extends SysuiTestCase { () -> mLogContextInteractor, () -> mPromptSelectionInteractor, () -> mCredentialViewModel, () -> mPromptViewModel, mInteractionJankMonitor, mHandler, mBackgroundExecutor, mUdfpsUtils, mVibratorHelper, - mLazyViewCapture); + mLazyViewCapture, mMSDLPlayer); } @Override diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt index 65236f02b635..e3b5f34c8e5a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt @@ -31,6 +31,8 @@ import com.android.systemui.common.ui.data.repository.fakeConfigurationRepositor import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.kosmos.testScope +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository import com.android.systemui.telephony.data.repository.fakeTelephonyRepository @@ -91,6 +93,8 @@ class BouncerActionButtonInteractorTest : SysuiTestCase() { kosmos.fakeTelephonyRepository.setHasTelephonyRadio(true) kosmos.telecomManager = telecomManager + + kosmos.sceneInteractor.changeScene(Scenes.Bouncer, "") } @Test @@ -130,6 +134,7 @@ class BouncerActionButtonInteractorTest : SysuiTestCase() { assertThat(metricsLogger.logs.element().category) .isEqualTo(MetricsProto.MetricsEvent.ACTION_EMERGENCY_CALL) verify(activityTaskManager).stopSystemLockTaskMode() + assertThat(kosmos.sceneInteractor.currentScene.value).isEqualTo(Scenes.Lockscreen) verify(telecomManager).showInCallScreen(eq(false)) } @@ -156,6 +161,7 @@ class BouncerActionButtonInteractorTest : SysuiTestCase() { assertThat(metricsLogger.logs.element().category) .isEqualTo(MetricsProto.MetricsEvent.ACTION_EMERGENCY_CALL) verify(activityTaskManager).stopSystemLockTaskMode() + assertThat(kosmos.sceneInteractor.currentScene.value).isEqualTo(Scenes.Lockscreen) // TODO(b/25189994): Test the activity has been started once we switch to the // ActivityStarter interface here. diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java index 53d82d7b2a07..956c12916c98 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java @@ -95,6 +95,7 @@ public class BrightLineFalsingManagerTest extends SysuiTestCase { when(mFalsingDataProvider.getRecentMotionEvents()).thenReturn(mMotionEventList); when(mKeyguardStateController.isShowing()).thenReturn(true); when(mFalsingDataProvider.isUnfolded()).thenReturn(false); + when(mFalsingDataProvider.isTouchScreenSource()).thenReturn(true); mBrightLineFalsingManager = new BrightLineFalsingManager(mFalsingDataProvider, mMetricsLogger, mClassifiers, mSingleTapClassifier, mLongTapClassifier, mDoubleTapClassifier, mHistoryTracker, mKeyguardStateController, @@ -193,6 +194,13 @@ public class BrightLineFalsingManagerTest extends SysuiTestCase { } @Test + public void testSkipNonTouchscreenDevices() { + assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isTrue(); + when(mFalsingDataProvider.isTouchScreenSource()).thenReturn(false); + assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isFalse(); + } + + @Test public void testTrackpadGesture() { assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isTrue(); when(mFalsingDataProvider.isFromTrackpad()).thenReturn(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/FalsingDataProviderTest.java index 49c6239d2541..df4b0480f5c7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/FalsingDataProviderTest.java @@ -18,6 +18,7 @@ package com.android.systemui.classifier; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -25,13 +26,20 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.hardware.devicestate.DeviceStateManager.FoldStateListener; +import android.hardware.input.IInputManager; +import android.hardware.input.InputManagerGlobal; +import android.os.RemoteException; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.util.DisplayMetrics; +import android.view.InputDevice; import android.view.KeyEvent; import android.view.MotionEvent; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; +import com.android.systemui.Flags; import com.android.systemui.classifier.FalsingDataProvider.GestureFinalizedListener; import com.android.systemui.dock.DockManagerFake; import com.android.systemui.statusbar.policy.BatteryController; @@ -56,11 +64,15 @@ public class FalsingDataProviderTest extends ClassifierTest { private FoldStateListener mFoldStateListener; private final DockManagerFake mDockManager = new DockManagerFake(); private DisplayMetrics mDisplayMetrics; + private IInputManager mIInputManager; + private InputManagerGlobal.TestSession inputManagerGlobalTestSession; @Before public void setup() { super.setup(); MockitoAnnotations.initMocks(this); + mIInputManager = mock(IInputManager.Stub.class); + inputManagerGlobalTestSession = InputManagerGlobal.createTestSession(mIInputManager); mDisplayMetrics = new DisplayMetrics(); mDisplayMetrics.xdpi = 100; mDisplayMetrics.ydpi = 100; @@ -73,6 +85,7 @@ public class FalsingDataProviderTest extends ClassifierTest { public void tearDown() { super.tearDown(); mDataProvider.onSessionEnd(); + inputManagerGlobalTestSession.close(); } @Test @@ -378,6 +391,79 @@ public class FalsingDataProviderTest extends ClassifierTest { } @Test + @DisableFlags(Flags.FLAG_NON_TOUCHSCREEN_DEVICES_BYPASS_FALSING) + public void test_isTouchscreenSource_flagOff_alwaysTrue() { + assertThat(mDataProvider.isTouchScreenSource()).isTrue(); + } + + @Test + @EnableFlags(Flags.FLAG_NON_TOUCHSCREEN_DEVICES_BYPASS_FALSING) + public void test_isTouchscreenSource_recentEventsEmpty_true() { + //send no events into the data provider + assertThat(mDataProvider.isTouchScreenSource()).isTrue(); + } + + @Test + @EnableFlags(Flags.FLAG_NON_TOUCHSCREEN_DEVICES_BYPASS_FALSING) + public void test_isTouchscreenSource_latestDeviceTouchscreen_true() throws RemoteException { + int deviceId = 999; + + InputDevice device = new InputDevice.Builder() + .setSources(InputDevice.SOURCE_CLASS_TRACKBALL | InputDevice.SOURCE_TOUCHSCREEN) + .setId(deviceId) + .build(); + when(mIInputManager.getInputDeviceIds()).thenReturn(new int[]{deviceId}); + when(mIInputManager.getInputDevice(anyInt())).thenReturn(device); + + MotionEvent event = MotionEvent.obtain(1, 0, MotionEvent.ACTION_UP, 1, + MotionEvent.PointerProperties.createArray(1), + MotionEvent.PointerCoords.createArray(1), 0, 0, 1.0f, 1.0f, deviceId, 0, + InputDevice.SOURCE_CLASS_NONE, 0, 0, 0); + + mDataProvider.onMotionEvent(event); + boolean result = mDataProvider.isTouchScreenSource(); + assertThat(result).isTrue(); + } + + @Test + @EnableFlags(Flags.FLAG_NON_TOUCHSCREEN_DEVICES_BYPASS_FALSING) + public void test_isTouchscreenSource_latestDeviceNonTouchscreen_false() throws RemoteException { + int deviceId = 9999; + + InputDevice device = new InputDevice.Builder() + .setSources(InputDevice.SOURCE_CLASS_TRACKBALL) + .setId(deviceId) + .build(); + when(mIInputManager.getInputDeviceIds()).thenReturn(new int[]{deviceId}); + when(mIInputManager.getInputDevice(anyInt())).thenReturn(device); + + MotionEvent event = MotionEvent.obtain(1, 0, MotionEvent.ACTION_UP, 1, + MotionEvent.PointerProperties.createArray(1), + MotionEvent.PointerCoords.createArray(1), 0, 0, 1.0f, 1.0f, deviceId, 0, + InputDevice.SOURCE_CLASS_NONE, 0, 0, 0); + + mDataProvider.onMotionEvent(event); + boolean result = mDataProvider.isTouchScreenSource(); + assertThat(result).isFalse(); + } + + @Test + @EnableFlags(Flags.FLAG_NON_TOUCHSCREEN_DEVICES_BYPASS_FALSING) + public void test_isTouchscreenSource_latestDeviceNull_true() { + // Do not mock InputManager for this test + inputManagerGlobalTestSession.close(); + + int nonExistentDeviceId = 9997; + MotionEvent event = MotionEvent.obtain(1, 0, MotionEvent.ACTION_UP, 1, + MotionEvent.PointerProperties.createArray(1), + MotionEvent.PointerCoords.createArray(1), 0, 0, 1.0f, 1.0f, nonExistentDeviceId, 0, + InputDevice.SOURCE_CLASS_NONE, 0, 0, 0); + + mDataProvider.onMotionEvent(event); + assertThat(mDataProvider.isTouchScreenSource()).isTrue(); + } + + @Test public void test_UnfoldedState_Folded() { FalsingDataProvider falsingDataProvider = createWithFoldCapability(true); when(mFoldStateListener.getFolded()).thenReturn(true); @@ -413,7 +499,7 @@ public class FalsingDataProviderTest extends ClassifierTest { } private FalsingDataProvider createWithFoldCapability(boolean foldable) { - return new FalsingDataProvider( - mDisplayMetrics, mBatteryController, mFoldStateListener, mDockManager, foldable); + return new FalsingDataProvider(mDisplayMetrics, mBatteryController, mFoldStateListener, + mDockManager, foldable); } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandlerTest.kt index bb400f274fbe..f06cd6aec8e0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandlerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandlerTest.kt @@ -67,7 +67,8 @@ class LongPressHandlingViewInteractionHandlerTest : SysuiTestCase() { isAttachedToWindow = { isAttachedToWindow }, onLongPressDetected = onLongPressDetected, onSingleTapDetected = onSingleTapDetected, - longPressDuration = { ViewConfiguration.getLongPressTimeout().toLong() } + longPressDuration = { ViewConfiguration.getLongPressTimeout().toLong() }, + allowedTouchSlop = ViewConfiguration.getTouchSlop(), ) underTest.isLongPressHandlingEnabled = true } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt index 34d926a23edb..25c533685ba7 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt @@ -27,6 +27,7 @@ import com.android.systemui.contextualeducation.GestureType import com.android.systemui.contextualeducation.GestureType.ALL_APPS import com.android.systemui.contextualeducation.GestureType.BACK import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.coroutines.collectValues import com.android.systemui.education.data.model.GestureEduModel import com.android.systemui.education.data.repository.contextualEducationRepository import com.android.systemui.education.data.repository.fakeEduClock @@ -62,6 +63,8 @@ class KeyboardTouchpadEduInteractorTest : SysuiTestCase() { private val underTest: KeyboardTouchpadEduInteractor = kosmos.keyboardTouchpadEduInteractor private val eduClock = kosmos.fakeEduClock + private val minDurationForNextEdu = + KeyboardTouchpadEduInteractor.minIntervalBetweenEdu + 1.seconds @Before fun setup() { @@ -93,7 +96,10 @@ class KeyboardTouchpadEduInteractorTest : SysuiTestCase() { triggerMaxEducationSignals(BACK) // runCurrent() to trigger 1st education runCurrent() + + eduClock.offset(minDurationForNextEdu) triggerMaxEducationSignals(BACK) + assertThat(model?.educationUiType).isEqualTo(EducationUiType.Notification) } @@ -115,6 +121,39 @@ class KeyboardTouchpadEduInteractorTest : SysuiTestCase() { } @Test + fun no2ndEducationBeforeMinEduIntervalReached() = + testScope.runTest { + val models by collectValues(underTest.educationTriggered) + triggerMaxEducationSignals(BACK) + runCurrent() + + // Offset a duration that is less than the required education interval + eduClock.offset(1.seconds) + triggerMaxEducationSignals(BACK) + runCurrent() + + assertThat(models.filterNotNull().size).isEqualTo(1) + } + + @Test + fun noNewEducationInfoAfterMaxEducationCountReached() = + testScope.runTest { + val models by collectValues(underTest.educationTriggered) + // Trigger 2 educations + triggerMaxEducationSignals(BACK) + runCurrent() + eduClock.offset(minDurationForNextEdu) + triggerMaxEducationSignals(BACK) + runCurrent() + + // Try triggering 3rd education + eduClock.offset(minDurationForNextEdu) + triggerMaxEducationSignals(BACK) + + assertThat(models.filterNotNull().size).isEqualTo(2) + } + + @Test fun startNewUsageSessionWhen2ndSignalReceivedAfterSessionDeadline() = testScope.runTest { val model by diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt index e075b7edda40..c4ac585f7e4a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt @@ -25,6 +25,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.contextualeducation.GestureType import com.android.systemui.contextualeducation.GestureType.BACK +import com.android.systemui.education.data.repository.fakeEduClock import com.android.systemui.education.domain.interactor.KeyboardTouchpadEduInteractor import com.android.systemui.education.domain.interactor.contextualEducationInteractor import com.android.systemui.education.domain.interactor.keyboardTouchpadEduInteractor @@ -35,6 +36,7 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.res.R import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat +import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest @@ -56,6 +58,9 @@ class ContextualEduUiCoordinatorTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope private val interactor = kosmos.contextualEducationInteractor + private val eduClock = kosmos.fakeEduClock + private val minDurationForNextEdu = + KeyboardTouchpadEduInteractor.minIntervalBetweenEdu + 1.seconds private lateinit var underTest: ContextualEduUiCoordinator @Mock private lateinit var toast: Toast @Mock private lateinit var notificationManager: NotificationManager @@ -94,6 +99,7 @@ class ContextualEduUiCoordinatorTest : SysuiTestCase() { fun showNotificationOn2ndEdu() = testScope.runTest { triggerEducation(BACK) + eduClock.offset(minDurationForNextEdu) triggerEducation(BACK) verify(notificationManager).notifyAsUser(any(), anyInt(), any(), any()) } @@ -110,7 +116,10 @@ class ContextualEduUiCoordinatorTest : SysuiTestCase() { testScope.runTest { val notificationCaptor = ArgumentCaptor.forClass(Notification::class.java) triggerEducation(BACK) + + eduClock.offset(minDurationForNextEdu) triggerEducation(BACK) + verify(notificationManager) .notifyAsUser(any(), anyInt(), notificationCaptor.capture(), any()) verifyNotificationContent( diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorTest.kt index af76b088787e..af76b088787e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorTest.kt diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderTest.kt index c1dcf37498d7..69ccc58cadbf 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderTest.kt @@ -41,7 +41,6 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.media.controls.shared.model.MediaData import com.android.systemui.media.controls.util.fakeMediaControllerFactory import com.android.systemui.media.controls.util.mediaFlags -import com.android.systemui.plugins.activityStarter import com.android.systemui.res.R import com.android.systemui.statusbar.SbnBuilder import com.android.systemui.testKosmos @@ -86,7 +85,6 @@ class MediaDataLoaderTest : SysuiTestCase() { context, testDispatcher, testScope, - kosmos.activityStarter, mediaControllerFactory, mediaFlags, kosmos.imageLoader, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt index 6f11b2a4265e..aaad0fc1b644 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt @@ -185,7 +185,6 @@ class InternetTileDataInteractorTest : SysuiTestCase() { val networkModel = WifiNetworkModel.Active( - networkId = 1, level = 4, ssid = "test ssid", ) @@ -221,7 +220,6 @@ class InternetTileDataInteractorTest : SysuiTestCase() { val networkModel = WifiNetworkModel.Active( - networkId = 1, level = 4, ssid = "test ssid", hotspotDeviceType = WifiNetworkModel.HotspotDeviceType.NONE, @@ -546,7 +544,6 @@ class InternetTileDataInteractorTest : SysuiTestCase() { private fun setWifiNetworkWithHotspot(hotspot: WifiNetworkModel.HotspotDeviceType) { val networkModel = WifiNetworkModel.Active( - networkId = 1, level = 4, ssid = "test ssid", hotspotDeviceType = hotspot, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingUserActionInteractorTest.kt index 4e5806902a10..5bd3645b4cab 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingUserActionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingUserActionInteractorTest.kt @@ -31,6 +31,7 @@ import com.android.systemui.qs.pipeline.domain.interactor.panelInteractor import com.android.systemui.qs.tiles.base.interactor.QSTileInput import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction import com.android.systemui.recordissue.RecordIssueDialogDelegate +import com.android.systemui.screenrecord.RecordingController import com.android.systemui.settings.UserContextProvider import com.android.systemui.settings.userTracker import com.android.systemui.statusbar.phone.KeyguardDismissUtil @@ -40,12 +41,16 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.Mock import org.mockito.Mockito.mock +import org.mockito.MockitoAnnotations @SmallTest @RunWith(AndroidJUnit4::class) class IssueRecordingUserActionInteractorTest : SysuiTestCase() { + @Mock private lateinit var recordingController: RecordingController + val user = UserHandle(1) val kosmos = Kosmos().also { it.testCase = this } @@ -56,6 +61,7 @@ class IssueRecordingUserActionInteractorTest : SysuiTestCase() { @Before fun setup() { + MockitoAnnotations.initMocks(this) hasCreatedDialogDelegate = false with(kosmos) { val factory = @@ -84,7 +90,8 @@ class IssueRecordingUserActionInteractorTest : SysuiTestCase() { dialogTransitionAnimator, panelInteractor, userTracker, - factory + factory, + recordingController, ) } } 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 91d8e2a75ef0..de3dc5730421 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 @@ -25,7 +25,9 @@ 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.notification.modes.TestModeBuilder import com.android.systemui.SysuiTestCase +import com.android.systemui.SysuiTestableContext import com.android.systemui.common.shared.model.asIcon import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues @@ -62,7 +64,12 @@ class ModesTileDataInteractorTest : SysuiTestCase() { context.orCreateTestableResources.apply { 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) + } + + val customPackageContext = SysuiTestableContext(context) + context.prepareCreatePackageContext(CUSTOM_PACKAGE, customPackageContext) + customPackageContext.orCreateTestableResources.apply { + addOverride(CUSTOM_DRAWABLE_ID, CUSTOM_DRAWABLE) } } @@ -146,35 +153,41 @@ class ModesTileDataInteractorTest : SysuiTestCase() { assertThat(tileData?.icon).isEqualTo(MODES_ICON) assertThat(tileData?.iconResId).isEqualTo(MODES_DRAWABLE_ID) - // Add an active mode: icon should be the mode icon. No iconResId, because we don't - // really know that it's a system icon. + // Add an active mode with a default icon: icon should be the mode icon, and the + // iconResId is also populated, because we know it's a system icon. zenModeRepository.addMode( - id = "Bedtime", + id = "Bedtime with default icon", type = AutomaticZenRule.TYPE_BEDTIME, active = true ) runCurrent() assertThat(tileData?.icon).isEqualTo(BEDTIME_ICON) - assertThat(tileData?.iconResId).isNull() + assertThat(tileData?.iconResId).isEqualTo(R.drawable.ic_zen_mode_type_bedtime) - // Add another, less-prioritized mode: icon should remain the first mode icon + // Add another, less-prioritized mode that has a *custom* icon: for now, icon should + // remain the first mode icon zenModeRepository.addMode( - id = "Driving", - type = AutomaticZenRule.TYPE_DRIVING, - active = true + TestModeBuilder() + .setId("Driving with custom icon") + .setType(AutomaticZenRule.TYPE_DRIVING) + .setPackage(CUSTOM_PACKAGE) + .setIconResId(CUSTOM_DRAWABLE_ID) + .setActive(true) + .build() ) runCurrent() assertThat(tileData?.icon).isEqualTo(BEDTIME_ICON) - assertThat(tileData?.iconResId).isNull() + assertThat(tileData?.iconResId).isEqualTo(R.drawable.ic_zen_mode_type_bedtime) // Deactivate more important mode: icon should be the less important, still active mode - zenModeRepository.deactivateMode("Bedtime") + // And because it's a package-provided icon, iconResId is not populated. + zenModeRepository.deactivateMode("Bedtime with default icon") runCurrent() - assertThat(tileData?.icon).isEqualTo(DRIVING_ICON) + assertThat(tileData?.icon).isEqualTo(CUSTOM_ICON) assertThat(tileData?.iconResId).isNull() // Deactivate remaining mode: back to the default modes icon - zenModeRepository.deactivateMode("Driving") + zenModeRepository.deactivateMode("Driving with custom icon") runCurrent() assertThat(tileData?.icon).isEqualTo(MODES_ICON) assertThat(tileData?.iconResId).isEqualTo(MODES_DRAWABLE_ID) @@ -241,15 +254,17 @@ class ModesTileDataInteractorTest : SysuiTestCase() { private companion object { val TEST_USER = UserHandle.of(1)!! + const val CUSTOM_PACKAGE = "com.some.mode.owner.package" val MODES_DRAWABLE_ID = R.drawable.ic_zen_priority_modes + const val CUSTOM_DRAWABLE_ID = 12345 val MODES_DRAWABLE = TestStubDrawable("modes_icon") val BEDTIME_DRAWABLE = TestStubDrawable("bedtime") - val DRIVING_DRAWABLE = TestStubDrawable("driving") + val CUSTOM_DRAWABLE = TestStubDrawable("custom") val MODES_ICON = MODES_DRAWABLE.asIcon() val BEDTIME_ICON = BEDTIME_DRAWABLE.asIcon() - val DRIVING_ICON = DRIVING_DRAWABLE.asIcon() + val CUSTOM_ICON = CUSTOM_DRAWABLE.asIcon() } } 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 f7bdcb8086ef..c3d45dbbd09a 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,7 +18,6 @@ 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 @@ -109,26 +108,7 @@ class ModesTileMapperTest : SysuiTestCase() { } @Test - @EnableFlags(Flags.FLAG_MODES_UI_ICONS) - 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() { + fun state_modelHasIconResId_includesIconResId() { val icon = TestStubDrawable("res123").asIcon() val model = ModesTileModel( diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt index ec79cc6ef5da..d1804608d130 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt @@ -21,6 +21,8 @@ package com.android.systemui.scene.domain.startable import android.app.StatusBarManager import android.hardware.face.FaceManager import android.os.PowerManager +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags import android.view.Display import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest @@ -28,6 +30,8 @@ import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.SceneKey import com.android.internal.logging.uiEventLoggerFake import com.android.internal.policy.IKeyguardDismissCallback +import com.android.keyguard.AuthInteractionProperties +import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository import com.android.systemui.authentication.shared.model.AuthenticationMethodModel @@ -48,6 +52,7 @@ import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus import com.android.systemui.deviceentry.shared.model.SuccessFaceAuthenticationStatus import com.android.systemui.flags.EnableSceneContainer +import com.android.systemui.haptics.msdl.fakeMSDLPlayer import com.android.systemui.haptics.vibratorHelper import com.android.systemui.keyevent.data.repository.fakeKeyEventRepository import com.android.systemui.keyguard.data.repository.biometricSettingsRepository @@ -95,6 +100,7 @@ import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvision import com.android.systemui.statusbar.sysuiStatusBarStateController import com.android.systemui.testKosmos import com.android.systemui.util.mockito.mock +import com.google.android.msdl.data.model.MSDLToken import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow @@ -137,6 +143,8 @@ class SceneContainerStartableTest : SysuiTestCase() { private val powerInteractor = kosmos.powerInteractor private val fakeTrustRepository = kosmos.fakeTrustRepository private val uiEventLoggerFake = kosmos.uiEventLoggerFake + private val msdlPlayer = kosmos.fakeMSDLPlayer + private val authInteractionProperties = AuthInteractionProperties() private lateinit var underTest: SceneContainerStartable @@ -654,6 +662,7 @@ class SceneContainerStartableTest : SysuiTestCase() { } @Test + @DisableFlags(Flags.FLAG_MSDL_FEEDBACK) fun playSuccessHaptics_onSuccessfulLockscreenAuth_udfps() = testScope.runTest { val currentSceneKey by collectLastValue(sceneInteractor.currentScene) @@ -680,6 +689,31 @@ class SceneContainerStartableTest : SysuiTestCase() { } @Test + @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) + fun playSuccessMSDLHaptics_onSuccessfulLockscreenAuth_udfps() = + testScope.runTest { + val currentSceneKey by collectLastValue(sceneInteractor.currentScene) + val playSuccessHaptic by + collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic) + + setupBiometricAuth(hasUdfps = true) + assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) + assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isFalse() + + underTest.start() + unlockWithFingerprintAuth() + + assertThat(playSuccessHaptic).isNotNull() + assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) + assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.UNLOCK) + assertThat(msdlPlayer.latestPropertiesPlayed).isEqualTo(authInteractionProperties) + + updateFingerprintAuthStatus(isSuccess = true) + assertThat(currentSceneKey).isEqualTo(Scenes.Gone) + } + + @Test + @DisableFlags(Flags.FLAG_MSDL_FEEDBACK) fun playSuccessHaptics_onSuccessfulLockscreenAuth_sfps() = testScope.runTest { val currentSceneKey by collectLastValue(sceneInteractor.currentScene) @@ -707,6 +741,32 @@ class SceneContainerStartableTest : SysuiTestCase() { } @Test + @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) + fun playSuccessMSDLHaptics_onSuccessfulLockscreenAuth_sfps() = + testScope.runTest { + val currentSceneKey by collectLastValue(sceneInteractor.currentScene) + val playSuccessHaptic by + collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic) + + setupBiometricAuth(hasSfps = true) + assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) + assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isFalse() + + underTest.start() + allowHapticsOnSfps() + unlockWithFingerprintAuth() + + assertThat(playSuccessHaptic).isNotNull() + assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) + assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.UNLOCK) + assertThat(msdlPlayer.latestPropertiesPlayed).isEqualTo(authInteractionProperties) + + updateFingerprintAuthStatus(isSuccess = true) + assertThat(currentSceneKey).isEqualTo(Scenes.Gone) + } + + @Test + @DisableFlags(Flags.FLAG_MSDL_FEEDBACK) fun playErrorHaptics_onFailedLockscreenAuth_udfps() = testScope.runTest { val currentSceneKey by collectLastValue(sceneInteractor.currentScene) @@ -727,6 +787,27 @@ class SceneContainerStartableTest : SysuiTestCase() { } @Test + @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) + fun playMSDLErrorHaptics_onFailedLockscreenAuth_udfps() = + testScope.runTest { + val currentSceneKey by collectLastValue(sceneInteractor.currentScene) + val playErrorHaptic by collectLastValue(deviceEntryHapticsInteractor.playErrorHaptic) + + setupBiometricAuth(hasUdfps = true) + assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) + assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isFalse() + + underTest.start() + updateFingerprintAuthStatus(isSuccess = false) + + assertThat(playErrorHaptic).isNotNull() + assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) + assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.FAILURE) + assertThat(msdlPlayer.latestPropertiesPlayed).isEqualTo(authInteractionProperties) + } + + @Test + @DisableFlags(Flags.FLAG_MSDL_FEEDBACK) fun playErrorHaptics_onFailedLockscreenAuth_sfps() = testScope.runTest { val currentSceneKey by collectLastValue(sceneInteractor.currentScene) @@ -747,6 +828,27 @@ class SceneContainerStartableTest : SysuiTestCase() { } @Test + @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) + fun playMSDLErrorHaptics_onFailedLockscreenAuth_sfps() = + testScope.runTest { + val currentSceneKey by collectLastValue(sceneInteractor.currentScene) + val playErrorHaptic by collectLastValue(deviceEntryHapticsInteractor.playErrorHaptic) + + setupBiometricAuth(hasSfps = true) + assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) + assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isFalse() + + underTest.start() + updateFingerprintAuthStatus(isSuccess = false) + + assertThat(playErrorHaptic).isNotNull() + assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) + assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.FAILURE) + assertThat(msdlPlayer.latestPropertiesPlayed).isEqualTo(authInteractionProperties) + } + + @Test + @DisableFlags(Flags.FLAG_MSDL_FEEDBACK) fun skipsSuccessHaptics_whenPowerButtonDown_sfps() = testScope.runTest { val currentSceneKey by collectLastValue(sceneInteractor.currentScene) @@ -774,6 +876,32 @@ class SceneContainerStartableTest : SysuiTestCase() { } @Test + @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) + fun skipsMSDLSuccessHaptics_whenPowerButtonDown_sfps() = + testScope.runTest { + val currentSceneKey by collectLastValue(sceneInteractor.currentScene) + val playSuccessHaptic by + collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic) + + setupBiometricAuth(hasSfps = true) + assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) + assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isFalse() + + underTest.start() + allowHapticsOnSfps(isPowerButtonDown = true) + unlockWithFingerprintAuth() + + assertThat(playSuccessHaptic).isNull() + assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) + assertThat(msdlPlayer.latestTokenPlayed).isNull() + assertThat(msdlPlayer.latestPropertiesPlayed).isNull() + + updateFingerprintAuthStatus(isSuccess = true) + assertThat(currentSceneKey).isEqualTo(Scenes.Gone) + } + + @Test + @DisableFlags(Flags.FLAG_MSDL_FEEDBACK) fun skipsSuccessHaptics_whenPowerButtonRecentlyPressed_sfps() = testScope.runTest { val currentSceneKey by collectLastValue(sceneInteractor.currentScene) @@ -801,6 +929,32 @@ class SceneContainerStartableTest : SysuiTestCase() { } @Test + @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) + fun skipsMSDLSuccessHaptics_whenPowerButtonRecentlyPressed_sfps() = + testScope.runTest { + val currentSceneKey by collectLastValue(sceneInteractor.currentScene) + val playSuccessHaptic by + collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic) + + setupBiometricAuth(hasSfps = true) + assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) + assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isFalse() + + underTest.start() + allowHapticsOnSfps(lastPowerPress = 50) + unlockWithFingerprintAuth() + + assertThat(playSuccessHaptic).isNull() + assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) + assertThat(msdlPlayer.latestTokenPlayed).isNull() + assertThat(msdlPlayer.latestPropertiesPlayed).isNull() + + updateFingerprintAuthStatus(isSuccess = true) + assertThat(currentSceneKey).isEqualTo(Scenes.Gone) + } + + @Test + @DisableFlags(Flags.FLAG_MSDL_FEEDBACK) fun skipsErrorHaptics_whenPowerButtonDown_sfps() = testScope.runTest { val currentSceneKey by collectLastValue(sceneInteractor.currentScene) @@ -822,6 +976,28 @@ class SceneContainerStartableTest : SysuiTestCase() { } @Test + @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) + fun skipsMSDLErrorHaptics_whenPowerButtonDown_sfps() = + testScope.runTest { + val currentSceneKey by collectLastValue(sceneInteractor.currentScene) + val playErrorHaptic by collectLastValue(deviceEntryHapticsInteractor.playErrorHaptic) + + setupBiometricAuth(hasSfps = true) + assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) + assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isFalse() + + underTest.start() + kosmos.fakeKeyEventRepository.setPowerButtonDown(true) + updateFingerprintAuthStatus(isSuccess = false) + + assertThat(playErrorHaptic).isNull() + assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) + assertThat(msdlPlayer.latestTokenPlayed).isNull() + assertThat(msdlPlayer.latestPropertiesPlayed).isNull() + } + + @Test + @DisableFlags(Flags.FLAG_MSDL_FEEDBACK) fun skipsFaceErrorHaptics_nonSfps_coEx() = testScope.runTest { val currentSceneKey by collectLastValue(sceneInteractor.currentScene) @@ -842,6 +1018,26 @@ class SceneContainerStartableTest : SysuiTestCase() { } @Test + @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) + fun skipsMSDLFaceErrorHaptics_nonSfps_coEx() = + testScope.runTest { + val currentSceneKey by collectLastValue(sceneInteractor.currentScene) + val playErrorHaptic by collectLastValue(deviceEntryHapticsInteractor.playErrorHaptic) + + setupBiometricAuth(hasUdfps = true, hasFace = true) + assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) + assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isFalse() + + underTest.start() + updateFaceAuthStatus(isSuccess = false) + + assertThat(playErrorHaptic).isNull() + assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) + assertThat(msdlPlayer.latestTokenPlayed).isNull() + assertThat(msdlPlayer.latestPropertiesPlayed).isNull() + } + + @Test fun hydrateSystemUiState() = testScope.runTest { val transitionStateFlow = prepareState() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt index 84c728cd9412..b9ca8fc2d181 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt @@ -93,7 +93,7 @@ class WifiInteractorImplTest : SysuiTestCase() { fun ssid_carrierMergedNetwork_outputsNull() = testScope.runTest { wifiRepository.setWifiNetwork( - WifiNetworkModel.CarrierMerged(networkId = 1, subscriptionId = 2, level = 1) + WifiNetworkModel.CarrierMerged(subscriptionId = 2, level = 1) ) var latest: String? = "default" @@ -106,53 +106,10 @@ class WifiInteractorImplTest : SysuiTestCase() { } @Test - fun ssid_isPasspointAccessPoint_outputsPasspointName() = - testScope.runTest { - wifiRepository.setWifiNetwork( - WifiNetworkModel.Active( - networkId = 1, - level = 1, - isPasspointAccessPoint = true, - passpointProviderFriendlyName = "friendly", - ) - ) - - var latest: String? = null - val job = underTest.ssid.onEach { latest = it }.launchIn(this) - runCurrent() - - assertThat(latest).isEqualTo("friendly") - - job.cancel() - } - - @Test - fun ssid_isOnlineSignUpForPasspoint_outputsPasspointName() = - testScope.runTest { - wifiRepository.setWifiNetwork( - WifiNetworkModel.Active( - networkId = 1, - level = 1, - isOnlineSignUpForPasspointAccessPoint = true, - passpointProviderFriendlyName = "friendly", - ) - ) - - var latest: String? = null - val job = underTest.ssid.onEach { latest = it }.launchIn(this) - runCurrent() - - assertThat(latest).isEqualTo("friendly") - - job.cancel() - } - - @Test fun ssid_unknownSsid_outputsNull() = testScope.runTest { wifiRepository.setWifiNetwork( WifiNetworkModel.Active( - networkId = 1, level = 1, ssid = WifiManager.UNKNOWN_SSID, ) @@ -172,7 +129,6 @@ class WifiInteractorImplTest : SysuiTestCase() { testScope.runTest { wifiRepository.setWifiNetwork( WifiNetworkModel.Active( - networkId = 1, level = 1, ssid = "MyAwesomeWifiNetwork", ) @@ -234,11 +190,9 @@ class WifiInteractorImplTest : SysuiTestCase() { testScope.runTest { val wifiNetwork = WifiNetworkModel.Active( - networkId = 45, isValidated = true, level = 3, ssid = "AB", - passpointProviderFriendlyName = "friendly" ) wifiRepository.setWifiNetwork(wifiNetwork) @@ -346,7 +300,6 @@ class WifiInteractorImplTest : SysuiTestCase() { wifiRepository.setWifiNetwork( WifiNetworkModel.Active( ssid = "ssid 2", - networkId = 1, level = 2, ) ) @@ -367,7 +320,6 @@ class WifiInteractorImplTest : SysuiTestCase() { wifiRepository.setWifiNetwork( WifiNetworkModel.Active( ssid = "ssid 2", - networkId = 1, level = 2, ) ) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt index dc24cf7d3484..72a45b9b5452 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt @@ -115,9 +115,7 @@ class WifiViewModelTest : SysuiTestCase() { val latestKeyguard by collectLastValue(keyguard.wifiIcon) val latestQs by collectLastValue(qs.wifiIcon) - wifiRepository.setWifiNetwork( - WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 1) - ) + wifiRepository.setWifiNetwork(WifiNetworkModel.Active(isValidated = true, level = 1)) assertThat(latestHome).isInstanceOf(WifiIcon.Visible::class.java) assertThat(latestHome).isEqualTo(latestKeyguard) @@ -132,7 +130,6 @@ class WifiViewModelTest : SysuiTestCase() { // Even WHEN the network has a valid hotspot type wifiRepository.setWifiNetwork( WifiNetworkModel.Active( - NETWORK_ID, isValidated = true, level = 1, hotspotDeviceType = WifiNetworkModel.HotspotDeviceType.LAPTOP, @@ -194,9 +191,7 @@ class WifiViewModelTest : SysuiTestCase() { whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true) createAndSetViewModel() - wifiRepository.setWifiNetwork( - WifiNetworkModel.Active(NETWORK_ID, ssid = null, level = 1) - ) + wifiRepository.setWifiNetwork(WifiNetworkModel.Active(ssid = null, level = 1)) val activityIn by collectLastValue(underTest.isActivityInViewVisible) val activityOut by collectLastValue(underTest.isActivityOutViewVisible) @@ -219,9 +214,7 @@ class WifiViewModelTest : SysuiTestCase() { whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true) createAndSetViewModel() - wifiRepository.setWifiNetwork( - WifiNetworkModel.Active(NETWORK_ID, ssid = null, level = 1) - ) + wifiRepository.setWifiNetwork(WifiNetworkModel.Active(ssid = null, level = 1)) val activityIn by collectLastValue(underTest.isActivityInViewVisible) val activityOut by collectLastValue(underTest.isActivityOutViewVisible) @@ -470,8 +463,6 @@ class WifiViewModelTest : SysuiTestCase() { } companion object { - private const val NETWORK_ID = 2 - private val ACTIVE_VALID_WIFI_NETWORK = - WifiNetworkModel.Active(NETWORK_ID, ssid = "AB", level = 1) + private val ACTIVE_VALID_WIFI_NETWORK = WifiNetworkModel.Active(ssid = "AB", level = 1) } } diff --git a/packages/SystemUI/res/layout/notification_template_en_route_contracted.xml b/packages/SystemUI/res/layout/notification_template_en_route_contracted.xml index 59cfeccbeb36..e7a40d129d50 100644 --- a/packages/SystemUI/res/layout/notification_template_en_route_contracted.xml +++ b/packages/SystemUI/res/layout/notification_template_en_route_contracted.xml @@ -16,7 +16,7 @@ <com.android.systemui.statusbar.notification.row.ui.view.EnRouteView xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/status_bar_latest_event_content" + android:id="@*android:id/status_bar_latest_event_content" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1" diff --git a/packages/SystemUI/res/layout/notification_template_en_route_expanded.xml b/packages/SystemUI/res/layout/notification_template_en_route_expanded.xml new file mode 100644 index 000000000000..ca6d66a370bd --- /dev/null +++ b/packages/SystemUI/res/layout/notification_template_en_route_expanded.xml @@ -0,0 +1,86 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2014 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<com.android.systemui.statusbar.notification.row.ui.view.EnRouteView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@*android:id/status_bar_latest_event_content" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:clipChildren="false" + android:tag="big" + > + + <LinearLayout + android:id="@*android:id/notification_action_list_margin_target" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="@*android:dimen/notification_content_margin" + android:orientation="vertical" + > + + <FrameLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:layout_gravity="top" + > + + <include layout="@*android:layout/notification_template_header" /> + + <LinearLayout + android:id="@*android:id/notification_main_column" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginStart="@*android:dimen/notification_content_margin_start" + android:layout_marginEnd="@*android:dimen/notification_content_margin_end" + android:layout_marginTop="@*android:dimen/notification_content_margin_top" + android:orientation="vertical" + > + + <include layout="@*android:layout/notification_template_part_line1" /> + + <include layout="@*android:layout/notification_template_text_multiline" /> + + <include + android:layout_width="match_parent" + android:layout_height="@*android:dimen/notification_progress_bar_height" + android:layout_marginTop="@*android:dimen/notification_progress_margin_top" + layout="@*android:layout/notification_template_progress" + /> + </LinearLayout> + + <include layout="@*android:layout/notification_template_right_icon" /> + </FrameLayout> + + <ViewStub + android:layout="@*android:layout/notification_material_reply_text" + android:id="@*android:id/notification_material_reply_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + /> + + <include + layout="@*android:layout/notification_template_smart_reply_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginStart="@*android:dimen/notification_content_margin_start" + android:layout_marginEnd="@*android:dimen/notification_content_margin_end" + android:layout_marginTop="@*android:dimen/notification_content_margin" + /> + + <include layout="@*android:layout/notification_material_action_list" /> + </LinearLayout> +</com.android.systemui.statusbar.notification.row.ui.view.EnRouteView> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 141d03599867..e1808fa7532d 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -2002,10 +2002,10 @@ <!-- Shadow for dream overlay status bar complications --> <dimen name="dream_overlay_status_bar_key_text_shadow_dx">0.5dp</dimen> <dimen name="dream_overlay_status_bar_key_text_shadow_dy">0.5dp</dimen> - <dimen name="dream_overlay_status_bar_key_text_shadow_radius">1dp</dimen> + <dimen name="dream_overlay_status_bar_key_text_shadow_radius">3dp</dimen> <dimen name="dream_overlay_status_bar_ambient_text_shadow_dx">0.5dp</dimen> <dimen name="dream_overlay_status_bar_ambient_text_shadow_dy">0.5dp</dimen> - <dimen name="dream_overlay_status_bar_ambient_text_shadow_radius">2dp</dimen> + <dimen name="dream_overlay_status_bar_ambient_text_shadow_radius">3dp</dimen> <dimen name="dream_overlay_icon_inset_dimen">0dp</dimen> <!-- Default device corner radius, used for assist UI --> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java index dd84bc6989a4..92e5432ad243 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java @@ -271,7 +271,8 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, keyguardSecurityCallback, mLatencyTracker, mFalsingCollector, emergencyButtonController, mMessageAreaControllerFactory, - mDevicePostureController, mFeatureFlags, mSelectedUserInteractor); + mDevicePostureController, mFeatureFlags, mSelectedUserInteractor, + mMSDLPlayer); } else if (keyguardInputView instanceof KeyguardPasswordView) { return new KeyguardPasswordViewController((KeyguardPasswordView) keyguardInputView, mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java index caa74780538e..f74d93e1d88d 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java @@ -36,6 +36,7 @@ import com.android.internal.widget.LockPatternView.Cell; import com.android.internal.widget.LockscreenCredential; import com.android.keyguard.EmergencyButtonController.EmergencyButtonCallback; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; +import com.android.systemui.bouncer.ui.helper.BouncerHapticHelper; import com.android.systemui.classifier.FalsingClassifier; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.flags.FeatureFlags; @@ -43,6 +44,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; + import java.util.HashMap; import java.util.List; import java.util.Map; @@ -67,6 +70,7 @@ public class KeyguardPatternViewController private LockPatternView mLockPatternView; private CountDownTimer mCountdownTimer; private AsyncTask<?, ?, ?> mPendingLockCheck; + private MSDLPlayer mMSDLPlayer; private EmergencyButtonCallback mEmergencyButtonCallback = new EmergencyButtonCallback() { @Override @@ -75,6 +79,10 @@ public class KeyguardPatternViewController } }; + private final LockPatternView.ExternalHapticsPlayer mExternalHapticsPlayer = () -> { + BouncerHapticHelper.INSTANCE.playPatternDotFeedback(mMSDLPlayer, mView); + }; + /** * Useful for clearing out the wrong pattern after a delay */ @@ -166,6 +174,10 @@ public class KeyguardPatternViewController boolean isValidPattern) { boolean dismissKeyguard = mSelectedUserInteractor.getSelectedUserId() == userId; if (matched) { + BouncerHapticHelper.INSTANCE.playMSDLAuthenticationFeedback( + /* authenticationSucceeded= */true, + /* player =*/mMSDLPlayer + ); getKeyguardSecurityCallback().reportUnlockAttempt(userId, true, 0); if (dismissKeyguard) { mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Correct); @@ -173,6 +185,10 @@ public class KeyguardPatternViewController getKeyguardSecurityCallback().dismiss(true, userId, SecurityMode.Pattern); } } else { + BouncerHapticHelper.INSTANCE.playMSDLAuthenticationFeedback( + /* authenticationSucceeded= */false, + /* player =*/mMSDLPlayer + ); mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong); if (isValidPattern) { getKeyguardSecurityCallback().reportUnlockAttempt(userId, false, timeoutMs); @@ -200,7 +216,7 @@ public class KeyguardPatternViewController EmergencyButtonController emergencyButtonController, KeyguardMessageAreaController.Factory messageAreaControllerFactory, DevicePostureController postureController, FeatureFlags featureFlags, - SelectedUserInteractor selectedUserInteractor) { + SelectedUserInteractor selectedUserInteractor, MSDLPlayer msdlPlayer) { super(view, securityMode, keyguardSecurityCallback, emergencyButtonController, messageAreaControllerFactory, featureFlags, selectedUserInteractor); mKeyguardUpdateMonitor = keyguardUpdateMonitor; @@ -212,6 +228,7 @@ public class KeyguardPatternViewController featureFlags.isEnabled(LOCKSCREEN_ENABLE_LANDSCAPE)); mLockPatternView = mView.findViewById(R.id.lockPatternView); mPostureController = postureController; + mMSDLPlayer = msdlPlayer; } @Override @@ -249,6 +266,7 @@ public class KeyguardPatternViewController if (deadline != 0) { handleAttemptLockout(deadline); } + mLockPatternView.setExternalHapticsPlayer(mExternalHapticsPlayer); } @Override @@ -262,6 +280,7 @@ public class KeyguardPatternViewController cancelBtn.setOnClickListener(null); } mPostureController.removeCallback(mPostureCallback); + mLockPatternView.setExternalHapticsPlayer(null); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java index d08653c3cf1b..60edaae21bc0 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java @@ -52,7 +52,6 @@ import androidx.annotation.VisibleForTesting; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; -import com.android.internal.logging.UiEventLogger; import com.android.settingslib.bluetooth.BluetoothCallback; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.HapClientProfile; @@ -105,7 +104,8 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, private final AudioManager mAudioManager; private final LocalBluetoothProfileManager mProfileManager; private final HapClientProfile mHapClientProfile; - private final UiEventLogger mUiEventLogger; + private final HearingDevicesUiEventLogger mUiEventLogger; + private final int mLaunchSourceId; private HearingDevicesListAdapter mDeviceListAdapter; private HearingDevicesPresetsController mPresetsController; private Context mApplicationContext; @@ -153,20 +153,22 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, public interface Factory { /** Create a {@link HearingDevicesDialogDelegate} instance */ HearingDevicesDialogDelegate create( - boolean showPairNewDevice); + boolean showPairNewDevice, + @HearingDevicesUiEventLogger.LaunchSourceId int launchSource); } @AssistedInject public HearingDevicesDialogDelegate( @Application Context applicationContext, @Assisted boolean showPairNewDevice, + @Assisted @HearingDevicesUiEventLogger.LaunchSourceId int launchSourceId, SystemUIDialog.Factory systemUIDialogFactory, ActivityStarter activityStarter, DialogTransitionAnimator dialogTransitionAnimator, @Nullable LocalBluetoothManager localBluetoothManager, @Main Handler handler, AudioManager audioManager, - UiEventLogger uiEventLogger) { + HearingDevicesUiEventLogger uiEventLogger) { mApplicationContext = applicationContext; mShowPairNewDevice = showPairNewDevice; mSystemUIDialogFactory = systemUIDialogFactory; @@ -178,6 +180,7 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, mProfileManager = localBluetoothManager.getProfileManager(); mHapClientProfile = mProfileManager.getHapClientProfile(); mUiEventLogger = uiEventLogger; + mLaunchSourceId = launchSourceId; } @Override @@ -191,7 +194,7 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, @Override public void onDeviceItemGearClicked(@NonNull DeviceItem deviceItem, @NonNull View view) { - mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_GEAR_CLICK); + mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_GEAR_CLICK, mLaunchSourceId); dismissDialogIfExists(); Intent intent = new Intent(ACTION_BLUETOOTH_DEVICE_DETAILS); Bundle bundle = new Bundle(); @@ -207,15 +210,17 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, CachedBluetoothDevice cachedBluetoothDevice = deviceItem.getCachedBluetoothDevice(); switch (deviceItem.getType()) { case ACTIVE_MEDIA_BLUETOOTH_DEVICE, CONNECTED_BLUETOOTH_DEVICE -> { - mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_DISCONNECT); + mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_DISCONNECT, + mLaunchSourceId); cachedBluetoothDevice.disconnect(); } case AVAILABLE_MEDIA_BLUETOOTH_DEVICE -> { - mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_SET_ACTIVE); + mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_SET_ACTIVE, + mLaunchSourceId); cachedBluetoothDevice.setActive(); } case SAVED_BLUETOOTH_DEVICE -> { - mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_CONNECT); + mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_CONNECT, mLaunchSourceId); cachedBluetoothDevice.connect(); } } @@ -275,7 +280,7 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, if (mLocalBluetoothManager == null) { return; } - mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_DIALOG_SHOW); + mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_DIALOG_SHOW, mLaunchSourceId); mPairButton = dialog.requireViewById(R.id.pair_new_device_button); mDeviceList = dialog.requireViewById(R.id.device_list); mPresetSpinner = dialog.requireViewById(R.id.preset_spinner); @@ -363,7 +368,8 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, mPresetSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { - mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_PRESET_SELECT); + mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_PRESET_SELECT, + mLaunchSourceId); mPresetsController.selectPreset( mPresetsController.getAllPresetInfo().get(position).getIndex()); } @@ -381,7 +387,7 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, private void setupPairNewDeviceButton(SystemUIDialog dialog, @Visibility int visibility) { if (visibility == VISIBLE) { mPairButton.setOnClickListener(v -> { - mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_PAIR); + mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_PAIR, mLaunchSourceId); dismissDialogIfExists(); final Intent intent = new Intent(Settings.ACTION_HEARING_DEVICE_PAIRING_SETTINGS); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); @@ -485,7 +491,8 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, final String name = intent.getComponent() != null ? intent.getComponent().flattenToString() : intent.getPackage() + "/" + intent.getAction(); - mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_RELATED_TOOL_CLICK, 0, name); + mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_RELATED_TOOL_CLICK, + mLaunchSourceId, name); dismissDialogIfExists(); mActivityStarter.postStartActivityDismissingKeyguard(intent, /* delay= */ 0, mDialogTransitionAnimator.createActivityTransitionController(view)); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManager.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManager.java index bc4cb45582ff..3d24177a8029 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManager.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManager.java @@ -70,8 +70,10 @@ public class HearingDevicesDialogManager { * Shows the dialog. * * @param expandable {@link Expandable} from which the dialog is shown. + * @param launchSourceId the id indicates where the dialog is launched from. */ - public void showDialog(Expandable expandable) { + public void showDialog(Expandable expandable, + @HearingDevicesUiEventLogger.LaunchSourceId int launchSourceId) { if (mDialog != null) { if (DEBUG) { Log.d(TAG, "HearingDevicesDialog already showing. Destroy it first."); @@ -91,7 +93,8 @@ public class HearingDevicesDialogManager { }); pairedHearingDeviceCheckTask.addListener(() -> { try { - mDialog = mDialogFactory.create(!pairedHearingDeviceCheckTask.get()).createDialog(); + mDialog = mDialogFactory.create(!pairedHearingDeviceCheckTask.get(), + launchSourceId).createDialog(); if (expandable != null) { DialogTransitionAnimator.Controller controller = diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogReceiver.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogReceiver.java index 6a34d1969fe5..02e65fd9031b 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogReceiver.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogReceiver.java @@ -16,6 +16,8 @@ package com.android.systemui.accessibility.hearingaid; +import static com.android.systemui.accessibility.hearingaid.HearingDevicesUiEventLogger.LAUNCH_SOURCE_A11Y; + import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -46,7 +48,7 @@ public class HearingDevicesDialogReceiver extends BroadcastReceiver { } if (ACTION.equals(intent.getAction())) { - mDialogManager.showDialog(/* view= */ null); + mDialogManager.showDialog(/* expandable= */ null, LAUNCH_SOURCE_A11Y); } } } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.java deleted file mode 100644 index 3fbe56eccef2..000000000000 --- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.accessibility.hearingaid; - -import com.android.internal.logging.UiEvent; -import com.android.internal.logging.UiEventLogger; - -public enum HearingDevicesUiEvent implements UiEventLogger.UiEventEnum { - - @UiEvent(doc = "Hearing devices dialog is shown") - HEARING_DEVICES_DIALOG_SHOW(1848), - @UiEvent(doc = "Pair new device") - HEARING_DEVICES_PAIR(1849), - @UiEvent(doc = "Connect to the device") - HEARING_DEVICES_CONNECT(1850), - @UiEvent(doc = "Disconnect from the device") - HEARING_DEVICES_DISCONNECT(1851), - @UiEvent(doc = "Set the device as active device") - HEARING_DEVICES_SET_ACTIVE(1852), - @UiEvent(doc = "Click on the device gear to enter device detail page") - HEARING_DEVICES_GEAR_CLICK(1853), - @UiEvent(doc = "Select a preset from preset spinner") - HEARING_DEVICES_PRESET_SELECT(1854), - @UiEvent(doc = "Click on related tool") - HEARING_DEVICES_RELATED_TOOL_CLICK(1856); - - private final int mId; - - HearingDevicesUiEvent(int id) { - mId = id; - } - - @Override - public int getId() { - return mId; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.kt b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.kt new file mode 100644 index 000000000000..9e77b02be495 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.kt @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.accessibility.hearingaid + +import com.android.internal.logging.UiEvent +import com.android.internal.logging.UiEventLogger + +enum class HearingDevicesUiEvent(private val id: Int) : UiEventLogger.UiEventEnum { + + @UiEvent(doc = "Hearing devices dialog is shown") HEARING_DEVICES_DIALOG_SHOW(1848), + @UiEvent(doc = "Pair new device") HEARING_DEVICES_PAIR(1849), + @UiEvent(doc = "Connect to the device") HEARING_DEVICES_CONNECT(1850), + @UiEvent(doc = "Disconnect from the device") HEARING_DEVICES_DISCONNECT(1851), + @UiEvent(doc = "Set the device as active device") HEARING_DEVICES_SET_ACTIVE(1852), + @UiEvent(doc = "Click on the device gear to enter device detail page") + HEARING_DEVICES_GEAR_CLICK(1853), + @UiEvent(doc = "Select a preset from preset spinner") HEARING_DEVICES_PRESET_SELECT(1854), + @UiEvent(doc = "Click on related tool") HEARING_DEVICES_RELATED_TOOL_CLICK(1856); + + override fun getId(): Int = this.id +} diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEventLogger.kt b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEventLogger.kt new file mode 100644 index 000000000000..0b32cfc9742b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEventLogger.kt @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.accessibility.hearingaid + +import android.annotation.IntDef +import com.android.internal.logging.UiEventLogger +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject + +@SysUISingleton +class HearingDevicesUiEventLogger @Inject constructor(private val uiEventLogger: UiEventLogger) { + + /** Logs the given event */ + fun log(event: UiEventLogger.UiEventEnum, launchSourceId: Int) { + log(event, launchSourceId, null) + } + + fun log(event: UiEventLogger.UiEventEnum, launchSourceId: Int, pkgName: String?) { + uiEventLogger.log(event, launchSourceId, pkgName) + } + + /** + * The possible launch source of hearing devices dialog + * + * @hide + */ + @IntDef(LAUNCH_SOURCE_UNKNOWN, LAUNCH_SOURCE_A11Y, LAUNCH_SOURCE_QS_TILE) + annotation class LaunchSourceId + + companion object { + const val LAUNCH_SOURCE_UNKNOWN = 0 + const val LAUNCH_SOURCE_A11Y = 1 // launch from AccessibilityManagerService + const val LAUNCH_SOURCE_QS_TILE = 2 + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java index 970fdeabcaf8..69ab976c5301 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java @@ -78,6 +78,8 @@ import com.android.systemui.res.R; import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.util.concurrency.DelayableExecutor; +import com.google.android.msdl.domain.MSDLPlayer; + import kotlin.Lazy; import kotlinx.coroutines.CoroutineScope; @@ -157,6 +159,8 @@ public class AuthContainerView extends LinearLayout private final @Background DelayableExecutor mBackgroundExecutor; + private final MSDLPlayer mMSDLPlayer; + // Non-null only if the dialog is in the act of dismissing and has not sent the reason yet. @Nullable @AuthDialogCallback.DismissedReason private Integer mPendingCallbackReason; // HAT received from LockSettingsService when credential is verified. @@ -292,7 +296,8 @@ public class AuthContainerView extends LinearLayout @NonNull Provider<CredentialViewModel> credentialViewModelProvider, @NonNull @Background DelayableExecutor bgExecutor, @NonNull VibratorHelper vibratorHelper, - Lazy<ViewCapture> lazyViewCapture) { + Lazy<ViewCapture> lazyViewCapture, + @NonNull MSDLPlayer msdlPlayer) { super(config.mContext); mConfig = config; @@ -309,6 +314,7 @@ public class AuthContainerView extends LinearLayout .getDimension(R.dimen.biometric_dialog_animation_translation_offset); mLinearOutSlowIn = Interpolators.LINEAR_OUT_SLOW_IN; mBiometricCallback = new BiometricCallback(); + mMSDLPlayer = msdlPlayer; final BiometricModalities biometricModalities = new BiometricModalities( Utils.findFirstSensorProperties(fpProps, mConfig.mSensorIds), @@ -379,7 +385,7 @@ public class AuthContainerView extends LinearLayout getJankListener(mLayout, TRANSIT, BiometricViewSizeBinder.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS), mBackgroundView, mBiometricCallback, mApplicationCoroutineScope, - vibratorHelper); + vibratorHelper, mMSDLPlayer); } @VisibleForTesting diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index 097ab72522d6..b39aae94ed4e 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -89,6 +89,8 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.concurrency.Execution; +import com.google.android.msdl.domain.MSDLPlayer; + import dagger.Lazy; import kotlin.Unit; @@ -183,6 +185,7 @@ public class AuthController implements private final @Background DelayableExecutor mBackgroundExecutor; private final DisplayInfo mCachedDisplayInfo = new DisplayInfo(); @NonNull private final VibratorHelper mVibratorHelper; + @NonNull private final MSDLPlayer mMSDLPlayer; private final kotlin.Lazy<ViewCapture> mLazyViewCapture; @@ -742,7 +745,8 @@ public class AuthController implements @Background DelayableExecutor bgExecutor, @NonNull UdfpsUtils udfpsUtils, @NonNull VibratorHelper vibratorHelper, - Lazy<ViewCapture> daggerLazyViewCapture) { + Lazy<ViewCapture> daggerLazyViewCapture, + @NonNull MSDLPlayer msdlPlayer) { mContext = context; mExecution = execution; mUserManager = userManager; @@ -764,6 +768,7 @@ public class AuthController implements mUdfpsUtils = udfpsUtils; mApplicationCoroutineScope = applicationCoroutineScope; mVibratorHelper = vibratorHelper; + mMSDLPlayer = msdlPlayer; mLogContextInteractor = logContextInteractor; mPromptSelectorInteractor = promptSelectorInteractorProvider; @@ -1327,7 +1332,7 @@ public class AuthController implements wakefulnessLifecycle, userManager, lockPatternUtils, mInteractionJankMonitor, mPromptSelectorInteractor, viewModel, mCredentialViewModelProvider, bgExecutor, mVibratorHelper, - mLazyViewCapture); + mLazyViewCapture, mMSDLPlayer); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt index 0b440ad81fb5..e7e8d8f80cda 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt @@ -25,7 +25,6 @@ import android.hardware.biometrics.BiometricPrompt import android.hardware.biometrics.Flags import android.hardware.face.FaceManager import android.util.Log -import android.view.HapticFeedbackConstants import android.view.MotionEvent import android.view.View import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO @@ -59,6 +58,7 @@ import com.android.systemui.common.ui.view.onTouchListener import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.res.R import com.android.systemui.statusbar.VibratorHelper +import com.google.android.msdl.domain.MSDLPlayer import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.flow.combine @@ -83,6 +83,7 @@ object BiometricViewBinder { legacyCallback: Spaghetti.Callback, applicationScope: CoroutineScope, vibratorHelper: VibratorHelper, + msdlPlayer: MSDLPlayer, ): Spaghetti { val accessibilityManager = view.context.getSystemService(AccessibilityManager::class.java)!! @@ -434,21 +435,27 @@ object BiometricViewBinder { // Play haptics launch { viewModel.hapticsToPlay.collect { haptics -> - if (haptics.hapticFeedbackConstant != HapticFeedbackConstants.NO_HAPTICS) { - if (haptics.flag != null) { - vibratorHelper.performHapticFeedback( - view, - haptics.hapticFeedbackConstant, - haptics.flag, - ) - } else { - vibratorHelper.performHapticFeedback( - view, - haptics.hapticFeedbackConstant, - ) + when (haptics) { + is PromptViewModel.HapticsToPlay.HapticConstant -> { + if (haptics.flag != null) { + vibratorHelper.performHapticFeedback( + view, + haptics.constant, + haptics.flag, + ) + } else { + vibratorHelper.performHapticFeedback( + view, + haptics.constant, + ) + } + } + is PromptViewModel.HapticsToPlay.MSDL -> { + msdlPlayer.playToken(haptics.token, haptics.properties) } - viewModel.clearHaptics() + is PromptViewModel.HapticsToPlay.None -> {} } + viewModel.clearHaptics() } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt index 85c3ae3f214e..d69e87534cb6 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt @@ -18,13 +18,11 @@ package com.android.systemui.biometrics.ui.binder import android.animation.Animator import android.animation.AnimatorSet -import android.animation.ValueAnimator import android.graphics.Outline import android.graphics.Rect import android.transition.AutoTransition import android.transition.TransitionManager import android.util.TypedValue -import android.view.Surface import android.view.View import android.view.ViewGroup import android.view.ViewOutlineProvider @@ -413,13 +411,12 @@ object BiometricViewSizeBinder { ANIMATE_SMALL_TO_MEDIUM_DURATION_MS.toLong() ) - TransitionManager.beginDelayedTransition(view, autoTransition) - if (position.isLeft) { flipConstraintSet.applyTo(view) } else { mediumConstraintSet.applyTo(view) } + TransitionManager.beginDelayedTransition(view, autoTransition) } size.isMedium -> { if (position.isLeft) { @@ -428,14 +425,18 @@ object BiometricViewSizeBinder { mediumConstraintSet.applyTo(view) } } - size.isLarge && currentSize.isMedium -> { + size.isLarge -> { val autoTransition = AutoTransition() autoTransition.setDuration( - ANIMATE_MEDIUM_TO_LARGE_DURATION_MS.toLong() + if (currentSize.isSmall) { + ANIMATE_SMALL_TO_MEDIUM_DURATION_MS.toLong() + } else { + ANIMATE_MEDIUM_TO_LARGE_DURATION_MS.toLong() + } ) - TransitionManager.beginDelayedTransition(view, autoTransition) largeConstraintSet.applyTo(view) + TransitionManager.beginDelayedTransition(view, autoTransition) } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt index 4c2fe07f92bb..168ba11309cc 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt @@ -35,7 +35,9 @@ import android.util.Log import android.util.RotationUtils import android.view.HapticFeedbackConstants import android.view.MotionEvent +import com.android.keyguard.AuthInteractionProperties import com.android.launcher3.icons.IconProvider +import com.android.systemui.Flags.msdlFeedback import com.android.systemui.biometrics.UdfpsUtils import com.android.systemui.biometrics.Utils import com.android.systemui.biometrics.Utils.isSystem @@ -53,6 +55,8 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus import com.android.systemui.res.R import com.android.systemui.util.kotlin.combine +import com.google.android.msdl.data.model.MSDLToken +import com.google.android.msdl.domain.InteractionProperties import javax.inject.Inject import kotlinx.coroutines.Job import kotlinx.coroutines.coroutineScope @@ -245,8 +249,9 @@ constructor( private val _forceLargeSize = MutableStateFlow(false) private val _forceMediumSize = MutableStateFlow(false) - private val _hapticsToPlay = - MutableStateFlow(HapticsToPlay(HapticFeedbackConstants.NO_HAPTICS, /* flag= */ null)) + private val authInteractionProperties = AuthInteractionProperties() + private val _hapticsToPlay: MutableStateFlow<HapticsToPlay> = + MutableStateFlow(HapticsToPlay.None) /** Event fired to the view indicating a [HapticsToPlay] */ val hapticsToPlay = _hapticsToPlay.asStateFlow() @@ -939,26 +944,52 @@ constructor( } private fun vibrateOnSuccess() { - _hapticsToPlay.value = - HapticsToPlay( - HapticFeedbackConstants.BIOMETRIC_CONFIRM, - null, - ) + val haptics = + if (msdlFeedback()) { + HapticsToPlay.MSDL(MSDLToken.UNLOCK, authInteractionProperties) + } else { + HapticsToPlay.HapticConstant( + HapticFeedbackConstants.BIOMETRIC_CONFIRM, + flag = null, + ) + } + _hapticsToPlay.value = haptics } private fun vibrateOnError() { - _hapticsToPlay.value = - HapticsToPlay( - HapticFeedbackConstants.BIOMETRIC_REJECT, - null, - ) + val haptics = + if (msdlFeedback()) { + HapticsToPlay.MSDL(MSDLToken.FAILURE, authInteractionProperties) + } else { + HapticsToPlay.HapticConstant( + HapticFeedbackConstants.BIOMETRIC_REJECT, + flag = null, + ) + } + _hapticsToPlay.value = haptics } /** Clears the [hapticsToPlay] variable by setting its constant to the NO_HAPTICS default. */ fun clearHaptics() { - _hapticsToPlay.update { previous -> - HapticsToPlay(HapticFeedbackConstants.NO_HAPTICS, previous.flag) - } + _hapticsToPlay.update { HapticsToPlay.None } + } + + /** The state of haptic feedback to play. */ + sealed interface HapticsToPlay { + /** + * Haptics using [HapticFeedbackConstants]. It is composed by a [HapticFeedbackConstants] + * and a [HapticFeedbackConstants] flag. + */ + data class HapticConstant(val constant: Int, val flag: Int?) : HapticsToPlay + + /** + * Haptics using MSDL feedback. It is composed by a [MSDLToken] and optional + * [InteractionProperties] + */ + data class MSDL(val token: MSDLToken, val properties: InteractionProperties?) : + HapticsToPlay + + data object None : HapticsToPlay } companion object { @@ -1095,9 +1126,3 @@ enum class FingerprintStartMode { val isStarted: Boolean get() = this == Normal || this == Delayed } - -/** - * The state of haptic feedback to play. It is composed by a [HapticFeedbackConstants] and a - * [HapticFeedbackConstants] flag. - */ -data class HapticsToPlay(val hapticFeedbackConstant: Int, val flag: Int?) diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractor.kt index f36ef6630a48..8b5a09b3d9fd 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractor.kt @@ -34,10 +34,14 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.doze.DozeLogger +import com.android.systemui.scene.domain.interactor.SceneInteractor +import com.android.systemui.scene.shared.flag.SceneContainerFlag +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository import com.android.systemui.telephony.domain.interactor.TelephonyInteractor import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.EmergencyDialerConstants +import dagger.Lazy import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow @@ -69,6 +73,7 @@ constructor( private val emergencyDialerIntentFactory: EmergencyDialerIntentFactory, private val metricsLogger: MetricsLogger, private val dozeLogger: DozeLogger, + private val sceneInteractor: Lazy<SceneInteractor>, ) { /** The bouncer action button. If `null`, the button should not be shown. */ val actionButton: Flow<BouncerActionButtonModel?> = @@ -158,14 +163,17 @@ constructor( } private fun prepareToPerformAction() { - // TODO(b/308001302): Trigger occlusion and resetting bouncer state. + if (SceneContainerFlag.isEnabled) { + sceneInteractor.get().changeScene(Scenes.Lockscreen, "Bouncer action button clicked") + } + metricsLogger.action(MetricsEvent.ACTION_EMERGENCY_CALL) activityTaskManager.stopSystemLockTaskMode() } @SuppressLint("MissingPermission") private fun returnToCall() { - telecomManager?.showInCallScreen(/* showDialpad = */ false) + telecomManager?.showInCallScreen(/* showDialpad= */ false) } private val <T> Flow<T>.asUnitFlow: Flow<Unit> diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerHapticHelper.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerHapticHelper.kt new file mode 100644 index 000000000000..1faacff996ca --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerHapticHelper.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.bouncer.ui.helper + +import android.view.HapticFeedbackConstants +import android.view.View +import com.android.keyguard.AuthInteractionProperties +import com.android.systemui.Flags +//noinspection CleanArchitectureDependencyViolation: Data layer only referenced for this enum class +import com.google.android.msdl.data.model.MSDLToken +import com.google.android.msdl.domain.MSDLPlayer + +/** A helper object to deliver haptic feedback in bouncer interactions. */ +object BouncerHapticHelper { + + private val authInteractionProperties = AuthInteractionProperties() + + /** + * Deliver MSDL feedback as a result of authenticating through a bouncer. + * + * @param[authenticationSucceeded] Whether the authentication was successful or not. + * @param[player] The [MSDLPlayer] that delivers the correct feedback. + */ + fun playMSDLAuthenticationFeedback( + authenticationSucceeded: Boolean, + player: MSDLPlayer?, + ) { + if (player == null || !Flags.msdlFeedback()) { + return + } + + val token = + if (authenticationSucceeded) { + MSDLToken.UNLOCK + } else { + MSDLToken.FAILURE + } + player.playToken(token, authInteractionProperties) + } + + /** + * Deliver feedback when dragging through cells in the pattern bouncer. This function can play + * MSDL feedback using a [MSDLPlayer], or fallback to a default haptic feedback using the + * [View.performHapticFeedback] API and a [View]. + * + * @param[player] [MSDLPlayer] for MSDL feedback. + * @param[view] A [View] for default haptic feedback using [View.performHapticFeedback] + */ + fun playPatternDotFeedback(player: MSDLPlayer?, view: View?) { + if (player == null || !Flags.msdlFeedback()) { + view?.performHapticFeedback( + HapticFeedbackConstants.VIRTUAL_KEY, + HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING, + ) + } else { + player.playToken(MSDLToken.DRAG_INDICATOR) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java index e1ba93c75aad..83d4091802d7 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java @@ -34,8 +34,6 @@ import com.android.internal.logging.MetricsLogger; import com.android.systemui.classifier.FalsingDataProvider.SessionListener; import com.android.systemui.classifier.HistoryTracker.BeliefListener; import com.android.systemui.dagger.qualifiers.TestHarness; -import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -396,6 +394,7 @@ public class BrightLineFalsingManager implements FalsingManager { || mDataProvider.isA11yAction() || mDataProvider.isFromTrackpad() || mDataProvider.isFromKeyboard() + || !mDataProvider.isTouchScreenSource() || mDataProvider.isUnfolded(); } diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java index 2eca02c2b0d1..962ab998ddb1 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java @@ -21,6 +21,7 @@ import static com.android.systemui.dock.DockManager.DockEventListener; import android.hardware.SensorManager; import android.hardware.biometrics.BiometricSourceType; import android.util.Log; +import android.view.InputDevice; import android.view.KeyEvent; import android.view.MotionEvent; @@ -28,6 +29,7 @@ import androidx.annotation.VisibleForTesting; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; +import com.android.systemui.Flags; import com.android.systemui.communal.domain.interactor.CommunalInteractor; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; @@ -343,7 +345,9 @@ class FalsingCollectorImpl implements FalsingCollector { // will be ignored by the collector until another MotionEvent.ACTION_DOWN is passed in. // avoidGesture must be called immediately following the MotionEvent.ACTION_DOWN, before // any other events are processed, otherwise the whole gesture will be recorded. - if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) { + // + // We should only delay processing of these events for touchscreen sources + if (ev.getActionMasked() == MotionEvent.ACTION_DOWN && isTouchscreenSource(ev)) { // Make a copy of ev, since it will be recycled after we exit this method. mPendingDownEvent = MotionEvent.obtain(ev); mAvoidGesture = false; @@ -410,6 +414,22 @@ class FalsingCollectorImpl implements FalsingCollector { mFalsingDataProvider.onA11yAction(); } + /** + * returns {@code true} if the device supports Touchscreen, {@code false} otherwise. Defaults to + * {@code true} if the device is {@code null} + */ + private boolean isTouchscreenSource(MotionEvent ev) { + if (!Flags.nonTouchscreenDevicesBypassFalsing()) { + return true; + } + InputDevice device = ev.getDevice(); + if (device != null) { + return device.supportsSource(InputDevice.SOURCE_TOUCHSCREEN); + } else { + return true; + } + } + private boolean shouldSessionBeActive() { return mScreenOn && (mState == StatusBarState.KEYGUARD) diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java index 15017011134b..769976ef5058 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java @@ -20,11 +20,13 @@ import static com.android.systemui.classifier.FalsingModule.IS_FOLDABLE_DEVICE; import android.hardware.devicestate.DeviceStateManager.FoldStateListener; import android.util.DisplayMetrics; +import android.view.InputDevice; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.MotionEvent.PointerCoords; import android.view.MotionEvent.PointerProperties; +import com.android.systemui.Flags; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dock.DockManager; import com.android.systemui.statusbar.policy.BatteryController; @@ -281,6 +283,9 @@ public class FalsingDataProvider { } public boolean isFromTrackpad() { + if (Flags.nonTouchscreenDevicesBypassFalsing()) { + return false; + } if (mRecentMotionEvents.isEmpty()) { return false; } @@ -290,6 +295,25 @@ public class FalsingDataProvider { || classification == MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE; } + /** + * returns {@code true} if the device supports Touchscreen, {@code false} otherwise. Defaults to + * {@code true} if the device is {@code null} + */ + public boolean isTouchScreenSource() { + if (!Flags.nonTouchscreenDevicesBypassFalsing()) { + return true; + } + if (mRecentMotionEvents.isEmpty()) { + return true; + } + InputDevice device = mRecentMotionEvents.get(mRecentMotionEvents.size() - 1).getDevice(); + if (device != null) { + return device.supportsSource(InputDevice.SOURCE_TOUCHSCREEN); + } else { + return true; + } + } + private void recalculateData() { if (!mDirty) { return; diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt index b6ace81d18ba..9c4736a13b46 100644 --- a/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt +++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt @@ -27,6 +27,7 @@ import android.view.ViewConfiguration import android.view.accessibility.AccessibilityNodeInfo import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction import androidx.core.view.accessibility.AccessibilityNodeInfoCompat +import com.android.systemui.log.LongPressHandlingViewLogger import com.android.systemui.shade.TouchLogger import kotlin.math.pow import kotlin.math.sqrt @@ -42,6 +43,8 @@ class LongPressHandlingView( context: Context, attrs: AttributeSet?, longPressDuration: () -> Long, + allowedTouchSlop: Int = ViewConfiguration.getTouchSlop(), + logger: LongPressHandlingViewLogger? = null, ) : View( context, @@ -97,6 +100,8 @@ class LongPressHandlingView( }, onSingleTapDetected = { listener?.onSingleTapDetected(this@LongPressHandlingView) }, longPressDuration = longPressDuration, + allowedTouchSlop = allowedTouchSlop, + logger = logger, ) } diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandler.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandler.kt index d3fc610bc52e..4e38a4913fe6 100644 --- a/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandler.kt @@ -17,7 +17,7 @@ package com.android.systemui.common.ui.view -import android.view.ViewConfiguration +import com.android.systemui.log.LongPressHandlingViewLogger import kotlinx.coroutines.DisposableHandle /** Encapsulates logic to handle complex touch interactions with a [LongPressHandlingView]. */ @@ -35,6 +35,14 @@ class LongPressHandlingViewInteractionHandler( private val onSingleTapDetected: () -> Unit, /** Time for the touch to be considered a long-press in ms */ var longPressDuration: () -> Long, + /** + * Default touch slop that is allowed, if the movement between [MotionEventModel.Down] and + * [MotionEventModel.Up] is more than [allowedTouchSlop] then the touch is not processed as + * single tap or a long press. + */ + val allowedTouchSlop: Int, + /** Optional logger that can be passed in to log touch events */ + val logger: LongPressHandlingViewLogger? = null, ) { sealed class MotionEventModel { object Other : MotionEventModel() @@ -70,22 +78,26 @@ class LongPressHandlingViewInteractionHandler( true } is MotionEventModel.Move -> { - if (event.distanceMoved > ViewConfiguration.getTouchSlop()) { + if (event.distanceMoved > allowedTouchSlop) { + logger?.cancelingLongPressDueToTouchSlop(event.distanceMoved, allowedTouchSlop) cancelScheduledLongPress() } false } is MotionEventModel.Up -> { + logger?.onUpEvent(event.distanceMoved, allowedTouchSlop, event.gestureDuration) cancelScheduledLongPress() if ( - event.distanceMoved <= ViewConfiguration.getTouchSlop() && + event.distanceMoved <= allowedTouchSlop && event.gestureDuration < longPressDuration() ) { + logger?.dispatchingSingleTap() dispatchSingleTap() } false } is MotionEventModel.Cancel -> { + logger?.motionEventCancelled() cancelScheduledLongPress() false } @@ -97,15 +109,18 @@ class LongPressHandlingViewInteractionHandler( x: Int, y: Int, ) { + val duration = longPressDuration() + logger?.schedulingLongPress(duration) scheduledLongPressHandle = postDelayed( { + logger?.longPressTriggered() dispatchLongPress( x = x, y = y, ) }, - longPressDuration(), + duration, ) } diff --git a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt index 3105527eb9b1..43855d971a2a 100644 --- a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt @@ -19,6 +19,7 @@ package com.android.systemui.education.domain.interactor import android.hardware.input.InputManager import android.hardware.input.InputManager.KeyGestureEventListener import android.hardware.input.KeyGestureEvent +import android.os.SystemProperties import com.android.systemui.CoreStartable import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.contextualeducation.GestureType @@ -35,7 +36,10 @@ import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import java.time.Clock import java.util.concurrent.Executor import javax.inject.Inject -import kotlin.time.Duration.Companion.hours +import kotlin.time.Duration +import kotlin.time.Duration.Companion.days +import kotlin.time.DurationUnit +import kotlin.time.toDuration import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow @@ -58,7 +62,21 @@ constructor( companion object { const val TAG = "KeyboardTouchpadEduInteractor" const val MAX_SIGNAL_COUNT: Int = 2 - val usageSessionDuration = 72.hours + const val MAX_EDUCATION_SHOW_COUNT: Int = 2 + val usageSessionDuration = + getDurationForConfig("persist.contextual_edu.usage_session_sec", 3.days) + val minIntervalBetweenEdu = + getDurationForConfig("persist.contextual_edu.edu_interval_sec", 7.days) + + private fun getDurationForConfig( + systemPropertyKey: String, + defaultDuration: Duration + ): Duration = + SystemProperties.getLong( + systemPropertyKey, + /* defaultValue= */ defaultDuration.inWholeSeconds + ) + .toDuration(DurationUnit.SECONDS) } private val _educationTriggered = MutableStateFlow<EducationInfo?>(null) @@ -128,10 +146,20 @@ constructor( } private fun isEducationNeeded(model: GestureEduModel): Boolean { - // Todo: b/354884305 - add complete education logic to show education in correct scenarios + val lessThanMaxEduCount = model.educationShownCount < MAX_EDUCATION_SHOW_COUNT val noShortcutTriggered = model.lastShortcutTriggeredTime == null val signalCountReached = model.signalCount >= MAX_SIGNAL_COUNT - return noShortcutTriggered && signalCountReached + val isPreviousEduOlderThanMinInterval = + if (model.educationShownCount == 1) { + model.lastEducationTime + ?.plusSeconds(minIntervalBetweenEdu.inWholeSeconds) + ?.isBefore(clock.instant()) ?: true + } else true + + return lessThanMaxEduCount && + noShortcutTriggered && + signalCountReached && + isPreviousEduOlderThanMinInterval } private fun isUsageSessionExpired(model: GestureEduModel): Boolean { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java index 1bc91cac1a84..67625d04b467 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java @@ -81,6 +81,7 @@ import com.android.systemui.flags.FeatureFlags; import com.android.systemui.keyguard.domain.interactor.KeyguardDismissInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardEnabledInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; +import com.android.systemui.keyguard.domain.interactor.KeyguardStateCallbackInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardWakeDirectlyToGoneInteractor; import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindParamsApplier; import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindViewBinder; @@ -126,6 +127,7 @@ public class KeyguardService extends Service { private final Lazy<DeviceEntryInteractor> mDeviceEntryInteractorLazy; private final Executor mMainExecutor; private final Lazy<KeyguardStateCallbackStartable> mKeyguardStateCallbackStartableLazy; + private final KeyguardStateCallbackInteractor mKeyguardStateCallbackInteractor; private static RemoteAnimationTarget[] wrap(TransitionInfo info, boolean wallpapers, SurfaceControl.Transaction t, ArrayMap<SurfaceControl, SurfaceControl> leashMap, @@ -350,7 +352,8 @@ public class KeyguardService extends Service { Lazy<KeyguardStateCallbackStartable> keyguardStateCallbackStartableLazy, KeyguardWakeDirectlyToGoneInteractor keyguardWakeDirectlyToGoneInteractor, KeyguardDismissInteractor keyguardDismissInteractor, - Lazy<DeviceEntryInteractor> deviceEntryInteractorLazy) { + Lazy<DeviceEntryInteractor> deviceEntryInteractorLazy, + KeyguardStateCallbackInteractor keyguardStateCallbackInteractor) { super(); mKeyguardViewMediator = keyguardViewMediator; mKeyguardLifecyclesDispatcher = keyguardLifecyclesDispatcher; @@ -363,6 +366,7 @@ public class KeyguardService extends Service { mSceneInteractorLazy = sceneInteractorLazy; mMainExecutor = mainExecutor; mKeyguardStateCallbackStartableLazy = keyguardStateCallbackStartableLazy; + mKeyguardStateCallbackInteractor = keyguardStateCallbackInteractor; mDeviceEntryInteractorLazy = deviceEntryInteractorLazy; if (KeyguardWmStateRefactor.isEnabled()) { @@ -455,6 +459,8 @@ public class KeyguardService extends Service { checkPermission(); if (SceneContainerFlag.isEnabled()) { mKeyguardStateCallbackStartableLazy.get().addCallback(callback); + } else if (KeyguardWmStateRefactor.isEnabled()) { + mKeyguardStateCallbackInteractor.addCallback(callback); } else { mKeyguardViewMediator.addStateMonitorCallback(callback); } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index d38c9520eb7a..3b1569d7f79b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -2288,6 +2288,10 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } private void updateInputRestrictedLocked() { + if (KeyguardWmStateRefactor.isEnabled()) { + return; + } + boolean inputRestricted = isInputRestricted(); if (mInputRestricted != inputRestricted) { mInputRestricted = inputRestricted; diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt index db5a63bbf446..58c8a0456241 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt @@ -73,7 +73,7 @@ constructor( if (SceneContainerFlag.isEnabled) return listenForGoneToAodOrDozing() listenForGoneToDreaming() - listenForGoneToLockscreenOrHub() + listenForGoneToLockscreenOrHubOrOccluded() listenForGoneToOccluded() listenForGoneToDreamingLockscreenHosted() } @@ -89,22 +89,19 @@ constructor( */ private fun listenForGoneToOccluded() { scope.launch("$TAG#listenForGoneToOccluded") { - keyguardInteractor.showDismissibleKeyguard - .filterRelevantKeyguardState() - .sample(keyguardInteractor.isKeyguardOccluded, ::Pair) - .collect { (_, isKeyguardOccluded) -> - if (isKeyguardOccluded) { - startTransitionTo( - KeyguardState.OCCLUDED, - ownerReason = "Dismissible keyguard with occlusion" - ) - } + keyguardInteractor.showDismissibleKeyguard.filterRelevantKeyguardState().collect { + if (keyguardInteractor.isKeyguardOccluded.value) { + startTransitionTo( + KeyguardState.OCCLUDED, + ownerReason = "Dismissible keyguard with occlusion" + ) } + } } } // Primarily for when the user chooses to lock down the device - private fun listenForGoneToLockscreenOrHub() { + private fun listenForGoneToLockscreenOrHubOrOccluded() { if (KeyguardWmStateRefactor.isEnabled) { scope.launch("$TAG#listenForGoneToLockscreenOrHub") { biometricSettingsRepository.isCurrentUserInLockdown @@ -137,7 +134,7 @@ constructor( } } } else { - scope.launch("$TAG#listenForGoneToLockscreenOrHub") { + scope.launch("$TAG#listenForGoneToLockscreenOrHubOrOccluded") { keyguardInteractor.isKeyguardShowing .filterRelevantKeyguardStateAnd { isKeyguardShowing -> isKeyguardShowing } .sample(communalSceneInteractor.isIdleOnCommunalNotEditMode, ::Pair) @@ -145,6 +142,8 @@ constructor( val to = if (isIdleOnCommunal) { KeyguardState.GLANCEABLE_HUB + } else if (keyguardInteractor.isKeyguardOccluded.value) { + KeyguardState.OCCLUDED } else { KeyguardState.LOCKSCREEN } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardStateCallbackInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardStateCallbackInteractor.kt new file mode 100644 index 000000000000..420fbd4ae48d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardStateCallbackInteractor.kt @@ -0,0 +1,93 @@ +/* + * 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.os.DeadObjectException +import android.os.RemoteException +import com.android.internal.policy.IKeyguardStateCallback +import com.android.systemui.CoreStartable +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.keyguard.KeyguardWmStateRefactor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.scene.shared.flag.SceneContainerFlag +import com.android.systemui.user.domain.interactor.SelectedUserInteractor +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import javax.inject.Inject + +/** + * Updates KeyguardStateCallbacks provided to KeyguardService with KeyguardTransitionInteractor + * state. + * + * This borrows heavily from [KeyguardStateCallbackStartable], which requires Flexiglass. This class + * can be removed after Flexiglass launches. + */ +@SysUISingleton +class KeyguardStateCallbackInteractor +@Inject +constructor( + @Application private val applicationScope: CoroutineScope, + @Background private val backgroundDispatcher: CoroutineDispatcher, + private val selectedUserInteractor: SelectedUserInteractor, + private val keyguardTransitionInteractor: KeyguardTransitionInteractor, +) : CoreStartable { + private val callbacks = mutableListOf<IKeyguardStateCallback>() + + override fun start() { + if (!KeyguardWmStateRefactor.isEnabled || SceneContainerFlag.isEnabled) { + return + } + + applicationScope.launch { + combine( + selectedUserInteractor.selectedUser, + keyguardTransitionInteractor.currentKeyguardState, + ::Pair + ).collectLatest { (selectedUser, currentState) -> + val iterator = callbacks.iterator() + withContext(backgroundDispatcher) { + while (iterator.hasNext()) { + val callback = iterator.next() + try { + callback.onShowingStateChanged( + currentState != KeyguardState.GONE, + selectedUser + ) + callback.onInputRestrictedStateChanged( + currentState != KeyguardState.GONE) + } catch (e: RemoteException) { + if (e is DeadObjectException) { + iterator.remove() + } + } + } + } + } + } + } + + fun addCallback(callback: IKeyguardStateCallback) { + KeyguardWmStateRefactor.isUnexpectedlyInLegacyMode() + callbacks.add(callback) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt index d9c48fa7e581..25b8fd32e82a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt @@ -29,6 +29,7 @@ constructor( private val auditLogger: KeyguardTransitionAuditLogger, private val bootInteractor: KeyguardTransitionBootInteractor, private val statusBarDisableFlagsInteractor: StatusBarDisableFlagsInteractor, + private val keyguardStateCallbackInteractor: KeyguardStateCallbackInteractor, ) : CoreStartable { override fun start() { @@ -55,6 +56,7 @@ constructor( auditLogger.start() bootInteractor.start() statusBarDisableFlagsInteractor.start() + keyguardStateCallbackInteractor.start() } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StatusBarDisableFlagsInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StatusBarDisableFlagsInteractor.kt index e00e33df62eb..43aab355d6d1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StatusBarDisableFlagsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StatusBarDisableFlagsInteractor.kt @@ -39,12 +39,14 @@ import com.android.systemui.navigation.domain.interactor.NavigationInteractor import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.power.shared.model.WakeSleepReason import com.android.systemui.power.shared.model.WakefulnessModel +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.user.domain.interactor.SelectedUserInteractor import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -75,62 +77,66 @@ constructor( private val disableToken: IBinder = Binder() private val disableFlagsForUserId = - combine( - selectedUserInteractor.selectedUser, - keyguardTransitionInteractor.startedKeyguardTransitionStep.map { it.to }, - deviceConfigInteractor.property( - namespace = DeviceConfig.NAMESPACE_SYSTEMUI, - name = SystemUiDeviceConfigFlags.NAV_BAR_HANDLE_SHOW_OVER_LOCKSCREEN, - default = true, - ), - navigationInteractor.isGesturalMode, - authenticationInteractor.authenticationMethod, - powerInteractor.detailedWakefulness, - ) { values -> - val selectedUserId = values[0] as Int - val startedState = values[1] as KeyguardState - val isShowHomeOverLockscreen = values[2] as Boolean - val isGesturalMode = values[3] as Boolean - val authenticationMethod = values[4] as AuthenticationMethodModel - val wakefulnessModel = values[5] as WakefulnessModel - val isOccluded = startedState == KeyguardState.OCCLUDED + if (!KeyguardWmStateRefactor.isEnabled || SceneContainerFlag.isEnabled) { + flowOf(Pair(0, StatusBarManager.DISABLE_NONE)) + } else { + combine( + selectedUserInteractor.selectedUser, + keyguardTransitionInteractor.startedKeyguardTransitionStep.map { it.to }, + deviceConfigInteractor.property( + namespace = DeviceConfig.NAMESPACE_SYSTEMUI, + name = SystemUiDeviceConfigFlags.NAV_BAR_HANDLE_SHOW_OVER_LOCKSCREEN, + default = true, + ), + navigationInteractor.isGesturalMode, + authenticationInteractor.authenticationMethod, + powerInteractor.detailedWakefulness, + ) { values -> + val selectedUserId = values[0] as Int + val startedState = values[1] as KeyguardState + val isShowHomeOverLockscreen = values[2] as Boolean + val isGesturalMode = values[3] as Boolean + val authenticationMethod = values[4] as AuthenticationMethodModel + val wakefulnessModel = values[5] as WakefulnessModel + val isOccluded = startedState == KeyguardState.OCCLUDED - val hideHomeAndRecentsForBouncer = - startedState == KeyguardState.PRIMARY_BOUNCER || - startedState == KeyguardState.ALTERNATE_BOUNCER - val isKeyguardShowing = startedState != KeyguardState.GONE - val isPowerGestureIntercepted = - with(wakefulnessModel) { - isAwake() && - powerButtonLaunchGestureTriggered && - lastSleepReason == WakeSleepReason.POWER_BUTTON - } + val hideHomeAndRecentsForBouncer = + startedState == KeyguardState.PRIMARY_BOUNCER || + startedState == KeyguardState.ALTERNATE_BOUNCER + val isKeyguardShowing = startedState != KeyguardState.GONE + val isPowerGestureIntercepted = + with(wakefulnessModel) { + isAwake() && + powerButtonLaunchGestureTriggered && + lastSleepReason == WakeSleepReason.POWER_BUTTON + } - var flags = StatusBarManager.DISABLE_NONE + var flags = StatusBarManager.DISABLE_NONE - if (hideHomeAndRecentsForBouncer || (isKeyguardShowing && !isOccluded)) { - if (!isShowHomeOverLockscreen || !isGesturalMode) { - flags = flags or StatusBarManager.DISABLE_HOME + if (hideHomeAndRecentsForBouncer || (isKeyguardShowing && !isOccluded)) { + if (!isShowHomeOverLockscreen || !isGesturalMode) { + flags = flags or StatusBarManager.DISABLE_HOME + } + flags = flags or StatusBarManager.DISABLE_RECENT } - flags = flags or StatusBarManager.DISABLE_RECENT - } - if ( - isPowerGestureIntercepted && - isOccluded && - authenticationMethod.isSecure && - deviceEntryFaceAuthInteractor.isFaceAuthEnabledAndEnrolled() - ) { - flags = flags or StatusBarManager.DISABLE_RECENT - } + if ( + isPowerGestureIntercepted && + isOccluded && + authenticationMethod.isSecure && + deviceEntryFaceAuthInteractor.isFaceAuthEnabledAndEnrolled() + ) { + flags = flags or StatusBarManager.DISABLE_RECENT + } - selectedUserId to flags - } - .distinctUntilChanged() + selectedUserId to flags + } + .distinctUntilChanged() + } @SuppressLint("WrongConstant", "NonInjectedService") override fun start() { - if (!KeyguardWmStateRefactor.isEnabled) { + if (!KeyguardWmStateRefactor.isEnabled || SceneContainerFlag.isEnabled) { return } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt index 91a7f7fc66bd..76962732ad01 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt @@ -42,6 +42,7 @@ import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerDependencies import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerUdfpsIconViewModel import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerWindowViewModel import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.log.LongPressHandlingViewLogger import com.android.systemui.res.R import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scrim.ScrimView @@ -191,6 +192,7 @@ constructor( optionallyAddUdfpsViews( view = view, + logger = alternateBouncerDependencies.logger, udfpsIconViewModel = alternateBouncerDependencies.udfpsIconViewModel, udfpsA11yOverlayViewModel = alternateBouncerDependencies.udfpsAccessibilityOverlayViewModel, @@ -248,6 +250,7 @@ constructor( private fun optionallyAddUdfpsViews( view: ConstraintLayout, + logger: LongPressHandlingViewLogger, udfpsIconViewModel: AlternateBouncerUdfpsIconViewModel, udfpsA11yOverlayViewModel: Lazy<AlternateBouncerUdfpsAccessibilityOverlayViewModel>, ) { @@ -276,7 +279,7 @@ constructor( var udfpsView = view.getViewById(udfpsViewId) if (udfpsView == null) { udfpsView = - DeviceEntryIconView(view.context, null).apply { + DeviceEntryIconView(view.context, null, logger = logger).apply { id = udfpsViewId contentDescription = context.resources.getString( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt index 4d6577c0423a..b951b736abf2 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt @@ -19,6 +19,7 @@ package com.android.systemui.keyguard.ui.binder import android.annotation.SuppressLint import android.content.res.ColorStateList +import android.util.Log import android.util.StateSet import android.view.HapticFeedbackConstants import android.view.View @@ -83,6 +84,11 @@ object DeviceEntryIconViewBinder { if ( !isA11yAction && falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY) ) { + Log.d( + TAG, + "Long press rejected because it is not a11yAction " + + "and it is a falseLongTap" + ) return } vibratorHelper.performHapticFeedback( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt index 3e6d5da38257..8d2e939da032 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt @@ -23,6 +23,7 @@ import android.util.AttributeSet import android.util.StateSet import android.view.Gravity import android.view.View +import android.view.ViewConfiguration import android.view.ViewGroup import android.view.accessibility.AccessibilityNodeInfo import android.widget.FrameLayout @@ -31,6 +32,7 @@ import androidx.core.view.accessibility.AccessibilityNodeInfoCompat import com.airbnb.lottie.LottieCompositionFactory import com.airbnb.lottie.LottieDrawable import com.android.systemui.common.ui.view.LongPressHandlingView +import com.android.systemui.log.LongPressHandlingViewLogger import com.android.systemui.res.R class DeviceEntryIconView @@ -39,8 +41,17 @@ constructor( context: Context, attrs: AttributeSet?, defStyleAttrs: Int = 0, + logger: LongPressHandlingViewLogger? = null, ) : FrameLayout(context, attrs, defStyleAttrs) { - val longPressHandlingView: LongPressHandlingView = LongPressHandlingView(context, attrs) + + val longPressHandlingView: LongPressHandlingView = + LongPressHandlingView( + context = context, + attrs = attrs, + longPressDuration = { ViewConfiguration.getLongPressTimeout().toLong() }, + allowedTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(), + logger = logger, + ) val iconView: ImageView = ImageView(context, attrs).apply { id = R.id.device_entry_icon_fg } val bgView: ImageView = ImageView(context, attrs).apply { id = R.id.device_entry_icon_bg } val aodFpDrawable: LottieDrawable = LottieDrawable() diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt index 51230dd0a47c..782d37b1929c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt @@ -42,6 +42,9 @@ import com.android.systemui.keyguard.ui.view.DeviceEntryIconView import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryBackgroundViewModel import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryForegroundViewModel import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LongPressHandlingViewLogger +import com.android.systemui.log.dagger.LongPressTouchLog import com.android.systemui.plugins.FalsingManager import com.android.systemui.res.R import com.android.systemui.shade.NotificationPanelView @@ -69,6 +72,7 @@ constructor( private val deviceEntryBackgroundViewModel: Lazy<DeviceEntryBackgroundViewModel>, private val falsingManager: Lazy<FalsingManager>, private val vibratorHelper: Lazy<VibratorHelper>, + @LongPressTouchLog private val logBuffer: LogBuffer, ) : KeyguardSection() { private val deviceEntryIconViewId = R.id.device_entry_icon_view private var disposableHandle: DisposableHandle? = null @@ -88,7 +92,16 @@ constructor( val view = if (DeviceEntryUdfpsRefactor.isEnabled) { - DeviceEntryIconView(context, null).apply { id = deviceEntryIconViewId } + DeviceEntryIconView( + context, + null, + logger = + LongPressHandlingViewLogger( + logBuffer = logBuffer, + TAG + ) + ) + .apply { id = deviceEntryIconViewId } } else { // KeyguardBottomAreaRefactor.isEnabled or MigrateClocksToBlueprint.isEnabled LockIconView(context, null).apply { id = R.id.lock_icon_view } @@ -258,4 +271,8 @@ constructor( } } } + + companion object { + private const val TAG = "DefaultDeviceEntrySection" + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerDependencies.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerDependencies.kt index b432417802c9..9f8e9c575a75 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerDependencies.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerDependencies.kt @@ -18,6 +18,9 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.deviceentry.ui.viewmodel.AlternateBouncerUdfpsAccessibilityOverlayViewModel import com.android.systemui.keyguard.ui.SwipeUpAnywhereGestureHandler +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LongPressHandlingViewLogger +import com.android.systemui.log.dagger.LongPressTouchLog import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.statusbar.gesture.TapGestureDetector import dagger.Lazy @@ -37,4 +40,11 @@ constructor( Lazy<AlternateBouncerUdfpsAccessibilityOverlayViewModel>, val messageAreaViewModel: AlternateBouncerMessageAreaViewModel, val powerInteractor: PowerInteractor, -) + @LongPressTouchLog private val touchLogBuffer: LogBuffer, +) { + val logger: LongPressHandlingViewLogger = + LongPressHandlingViewLogger(logBuffer = touchLogBuffer, TAG) + companion object { + private const val TAG = "AlternateBouncer" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/log/LongPressHandlingViewLogger.kt b/packages/SystemUI/src/com/android/systemui/log/LongPressHandlingViewLogger.kt new file mode 100644 index 000000000000..4ff81184d045 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/LongPressHandlingViewLogger.kt @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.log + +import com.android.systemui.log.core.LogLevel.DEBUG +import com.google.errorprone.annotations.CompileTimeConstant + +data class LongPressHandlingViewLogger +constructor( + private val logBuffer: LogBuffer, + @CompileTimeConstant private val tag: String = "LongPressHandlingViewLogger" +) { + fun schedulingLongPress(delay: Long) { + logBuffer.log( + tag, + DEBUG, + { long1 = delay }, + { "on MotionEvent.Down: scheduling long press activation after $long1 ms" } + ) + } + + fun longPressTriggered() { + logBuffer.log(tag, DEBUG, "long press event detected and dispatched") + } + + fun motionEventCancelled() { + logBuffer.log(tag, DEBUG, "Long press may be cancelled due to MotionEventModel.Cancel") + } + + fun dispatchingSingleTap() { + logBuffer.log(tag, DEBUG, "Dispatching single tap instead of long press") + } + + fun onUpEvent(distanceMoved: Float, touchSlop: Int, gestureDuration: Long) { + logBuffer.log( + tag, + DEBUG, + { + double1 = distanceMoved.toDouble() + int1 = touchSlop + long1 = gestureDuration + }, + { + "on MotionEvent.Up: distanceMoved: $double1, " + + "allowedTouchSlop: $int1, " + + "eventDuration: $long1" + } + ) + } + + fun cancelingLongPressDueToTouchSlop(distanceMoved: Float, allowedTouchSlop: Int) { + logBuffer.log( + tag, + DEBUG, + { + double1 = distanceMoved.toDouble() + int1 = allowedTouchSlop + }, + { + "on MotionEvent.Motion: May cancel long press due to movement: " + + "distanceMoved: $double1, " + + "allowedTouchSlop: $int1 " + } + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java index 19906fdc4d5f..498c34c03f2d 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -717,4 +717,12 @@ public class LogModule { public static LogBuffer provideVolumeLogBuffer(LogBufferFactory factory) { return factory.create("VolumeLog", 50); } + + /** Provides a {@link LogBuffer} for use by long touch event handlers. */ + @Provides + @SysUISingleton + @LongPressTouchLog + public static LogBuffer providesLongPressTouchLog(LogBufferFactory factory) { + return factory.create("LongPressViewLog", 200); + } } diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LongPressTouchLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/LongPressTouchLog.kt new file mode 100644 index 000000000000..1163d74b62a9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LongPressTouchLog.kt @@ -0,0 +1,25 @@ +/* + * 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.log.dagger + +import javax.inject.Qualifier + +/** Log buffer for logging touch/long press events */ +@Qualifier +@MustBeDocumented +@Retention(AnnotationRetention.RUNTIME) +annotation class LongPressTouchLog diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt index 24c57bea8bec..4ad437c50d61 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt @@ -76,6 +76,7 @@ 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.MediaData import com.android.systemui.media.controls.shared.model.MediaDeviceData +import com.android.systemui.media.controls.shared.model.MediaNotificationAction import com.android.systemui.media.controls.shared.model.SmartspaceMediaData import com.android.systemui.media.controls.shared.model.SmartspaceMediaDataProvider import com.android.systemui.media.controls.ui.view.MediaViewHolder @@ -943,7 +944,7 @@ class LegacyMediaDataManagerImpl( desc.subtitle, desc.title, artworkIcon, - listOf(mediaAction), + listOf(), listOf(0), MediaButton(playOrPause = mediaAction), packageName, @@ -1074,13 +1075,13 @@ class LegacyMediaDataManagerImpl( } // Control buttons - // If flag is enabled and controller has a PlaybackState, create actions from session info + // If controller has a PlaybackState, create actions from session info // Otherwise, use the notification actions - var actionIcons: List<MediaAction> = emptyList() + var actionIcons: List<MediaNotificationAction> = emptyList() var actionsToShowCollapsed: List<Int> = emptyList() val semanticActions = createActionsFromState(sbn.packageName, mediaController, sbn.user) if (semanticActions == null) { - val actions = createActionsFromNotification(context, activityStarter, sbn) + val actions = createActionsFromNotification(context, sbn) actionIcons = actions.first actionsToShowCollapsed = actions.second } @@ -1464,7 +1465,7 @@ class LegacyMediaDataManagerImpl( val updated = data.copy( token = null, - actions = actions, + actions = listOf(), semanticActions = MediaButton(playOrPause = resumeAction), actionsToShowInCompact = listOf(0), active = false, 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 bcf748e7573f..f2825d0465ad 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 @@ -33,6 +33,7 @@ import com.android.systemui.media.controls.domain.pipeline.LegacyMediaDataManage 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.MediaNotificationAction import com.android.systemui.plugins.ActivityStarter import com.android.systemui.res.R import com.android.systemui.statusbar.NotificationMediaManager.isConnectingState @@ -217,11 +218,10 @@ private fun includesAction(stateActions: Long, @PlaybackState.Actions action: Lo /** Generate action buttons based on notification actions */ fun createActionsFromNotification( context: Context, - activityStarter: ActivityStarter, sbn: StatusBarNotification -): Pair<List<MediaAction>, List<Int>> { +): Pair<List<MediaNotificationAction>, List<Int>> { val notif = sbn.notification - val actionIcons: MutableList<MediaAction> = ArrayList() + val actionIcons: MutableList<MediaNotificationAction> = ArrayList() val actions = notif.actions var actionsToShowCollapsed = notif.extras.getIntArray(Notification.EXTRA_COMPACT_ACTIONS)?.toMutableList() @@ -250,25 +250,6 @@ fun createActionsFromNotification( continue } - val runnable = - action.actionIntent?.let { actionIntent -> - Runnable { - when { - actionIntent.isActivity -> - activityStarter.startPendingIntentDismissingKeyguard( - action.actionIntent - ) - action.isAuthenticationRequired -> - activityStarter.dismissKeyguardThenExecute( - { sendPendingIntent(action.actionIntent) }, - {}, - true - ) - else -> sendPendingIntent(actionIntent) - } - } - } - val themeText = com.android.settingslib.Utils.getColorAttr( context, @@ -285,13 +266,53 @@ fun createActionsFromNotification( .setTint(themeText) .loadDrawable(context) - val mediaAction = MediaAction(mediaActionIcon, runnable, action.title, null) + val mediaAction = + MediaNotificationAction( + action.isAuthenticationRequired, + action.actionIntent, + mediaActionIcon, + action.title + ) actionIcons.add(mediaAction) } } return Pair(actionIcons, actionsToShowCollapsed) } +/** + * Converts [MediaNotificationAction] list into [MediaAction] list + * + * @param actions list of [MediaNotificationAction] + * @param activityStarter starter for activities + * @return list of [MediaAction] + */ +fun getNotificationActions( + actions: List<MediaNotificationAction>, + activityStarter: ActivityStarter +): List<MediaAction> { + return actions.map { action -> + val runnable = + action.actionIntent?.let { actionIntent -> + Runnable { + when { + actionIntent.isActivity -> + activityStarter.startPendingIntentDismissingKeyguard( + action.actionIntent + ) + action.isAuthenticationRequired -> + activityStarter.dismissKeyguardThenExecute( + { sendPendingIntent(action.actionIntent) }, + {}, + true + ) + else -> sendPendingIntent(actionIntent) + } + } + } + MediaAction(action.icon, runnable, action.contentDescription, background = null) + } +} + private fun sendPendingIntent(intent: PendingIntent): Boolean { return try { intent.send( diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt index f9fef8eac815..53cc15b8c588 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt @@ -54,10 +54,10 @@ 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.MediaData import com.android.systemui.media.controls.shared.model.MediaDeviceData +import com.android.systemui.media.controls.shared.model.MediaNotificationAction import com.android.systemui.media.controls.util.MediaControllerFactory import com.android.systemui.media.controls.util.MediaDataUtils import com.android.systemui.media.controls.util.MediaFlags -import com.android.systemui.plugins.ActivityStarter import com.android.systemui.res.R import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState import com.android.systemui.statusbar.notification.row.HybridGroupManager @@ -80,7 +80,6 @@ constructor( @Application val context: Context, @Main val mainDispatcher: CoroutineDispatcher, @Background val backgroundScope: CoroutineScope, - private val activityStarter: ActivityStarter, private val mediaControllerFactory: MediaControllerFactory, private val mediaFlags: MediaFlags, private val imageLoader: ImageLoader, @@ -209,15 +208,14 @@ constructor( val device: MediaDeviceData? = getDeviceInfoForRemoteCast(key, sbn) // Control buttons - // If flag is enabled and controller has a PlaybackState, create actions from session - // info + // If controller has a PlaybackState, create actions from session info // Otherwise, use the notification actions - var actionIcons: List<MediaAction> = emptyList() + var actionIcons: List<MediaNotificationAction> = emptyList() var actionsToShowCollapsed: List<Int> = emptyList() val semanticActions = createActionsFromState(sbn.packageName, mediaController, sbn.user) logD(TAG) { "Semantic actions: $semanticActions" } if (semanticActions == null) { - val actions = createActionsFromNotification(context, activityStarter, sbn) + val actions = createActionsFromNotification(context, sbn) actionIcons = actions.first actionsToShowCollapsed = actions.second logD(TAG) { "[!!] Semantic actions: $semanticActions" } @@ -329,7 +327,7 @@ constructor( artist = desc.subtitle, song = desc.title, artworkIcon = artworkIcon, - actionIcons = listOf(mediaAction), + actionIcons = listOf(), actionsToShowInCompact = listOf(0), semanticActions = MediaButton(playOrPause = mediaAction), token = token, @@ -514,7 +512,7 @@ constructor( val artist: CharSequence?, val song: CharSequence?, val artworkIcon: Icon?, - val actionIcons: List<MediaAction>, + val actionIcons: List<MediaNotificationAction>, val actionsToShowInCompact: List<Int>, val semanticActions: MediaButton?, val token: MediaSession.Token?, 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 4555810ee0ef..5f0a9f82b9ae 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,12 +71,14 @@ 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.MediaLogger 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 import com.android.systemui.media.controls.shared.model.MediaButton import com.android.systemui.media.controls.shared.model.MediaData import com.android.systemui.media.controls.shared.model.MediaDeviceData +import com.android.systemui.media.controls.shared.model.MediaNotificationAction import com.android.systemui.media.controls.shared.model.SmartspaceMediaData import com.android.systemui.media.controls.shared.model.SmartspaceMediaDataProvider import com.android.systemui.media.controls.ui.view.MediaViewHolder @@ -149,6 +151,7 @@ class MediaDataProcessor( private val keyguardUpdateMonitor: KeyguardUpdateMonitor, private val mediaDataRepository: MediaDataRepository, private val mediaDataLoader: dagger.Lazy<MediaDataLoader>, + private val mediaLogger: MediaLogger, ) : CoreStartable, BcSmartspaceDataPlugin.SmartspaceTargetListener { companion object { @@ -228,6 +231,7 @@ class MediaDataProcessor( keyguardUpdateMonitor: KeyguardUpdateMonitor, mediaDataRepository: MediaDataRepository, mediaDataLoader: dagger.Lazy<MediaDataLoader>, + mediaLogger: MediaLogger, ) : this( context, applicationScope, @@ -253,6 +257,7 @@ class MediaDataProcessor( keyguardUpdateMonitor, mediaDataRepository, mediaDataLoader, + mediaLogger, ) private val appChangeReceiver = @@ -794,7 +799,7 @@ class MediaDataProcessor( desc.subtitle, desc.title, artworkIcon, - listOf(mediaAction), + listOf(), listOf(0), MediaButton(playOrPause = mediaAction), packageName, @@ -832,12 +837,48 @@ class MediaDataProcessor( return@withContext } - val currentEntry = mediaDataRepository.mediaEntries.value[key] - val instanceId = currentEntry?.instanceId ?: logger.getNewInstanceId() - val createdTimestampMillis = currentEntry?.createdTimestampMillis ?: 0L - val resumeAction: Runnable? = currentEntry?.resumeAction - val hasCheckedForResume = currentEntry?.hasCheckedForResume == true - val active = currentEntry?.active ?: true + val mediaController = mediaControllerFactory.create(result.token!!) + val oldEntry = mediaDataRepository.mediaEntries.value[key] + val instanceId = oldEntry?.instanceId ?: logger.getNewInstanceId() + val createdTimestampMillis = oldEntry?.createdTimestampMillis ?: 0L + val resumeAction: Runnable? = oldEntry?.resumeAction + val hasCheckedForResume = oldEntry?.hasCheckedForResume == true + val active = oldEntry?.active ?: true + + val mediaData = + MediaData( + userId = sbn.normalizedUserId, + initialized = true, + app = result.appName, + appIcon = result.appIcon, + artist = result.artist, + song = result.song, + artwork = result.artworkIcon, + actions = result.actionIcons, + actionsToShowInCompact = result.actionsToShowInCompact, + semanticActions = result.semanticActions, + packageName = sbn.packageName, + token = result.token, + clickIntent = result.clickIntent, + device = result.device, + active = active, + resumeAction = resumeAction, + playbackLocation = result.playbackLocation, + notificationKey = key, + hasCheckedForResume = hasCheckedForResume, + isPlaying = result.isPlaying, + isClearable = !sbn.isOngoing, + lastActive = lastActive, + createdTimestampMillis = createdTimestampMillis, + instanceId = instanceId, + appUid = result.appUid, + isExplicit = result.isExplicit, + ) + + if (isSameMediaData(context, mediaController, mediaData, oldEntry)) { + mediaLogger.logDuplicateMediaNotification(key) + return@withContext + } // We need to log the correct media added. if (isNewlyActiveEntry) { @@ -848,7 +889,7 @@ class MediaDataProcessor( instanceId, result.playbackLocation ) - } else if (result.playbackLocation != currentEntry?.playbackLocation) { + } else if (result.playbackLocation != oldEntry?.playbackLocation) { logger.logPlaybackLocationChange( result.appUid, sbn.packageName, @@ -857,40 +898,7 @@ class MediaDataProcessor( ) } - withContext(mainDispatcher) { - onMediaDataLoaded( - key, - oldKey, - MediaData( - userId = sbn.normalizedUserId, - initialized = true, - app = result.appName, - appIcon = result.appIcon, - artist = result.artist, - song = result.song, - artwork = result.artworkIcon, - actions = result.actionIcons, - actionsToShowInCompact = result.actionsToShowInCompact, - semanticActions = result.semanticActions, - packageName = sbn.packageName, - token = result.token, - clickIntent = result.clickIntent, - device = result.device, - active = active, - resumeAction = resumeAction, - playbackLocation = result.playbackLocation, - notificationKey = key, - hasCheckedForResume = hasCheckedForResume, - isPlaying = result.isPlaying, - isClearable = !sbn.isOngoing, - lastActive = lastActive, - createdTimestampMillis = createdTimestampMillis, - instanceId = instanceId, - appUid = result.appUid, - isExplicit = result.isExplicit, - ) - ) - } + withContext(mainDispatcher) { onMediaDataLoaded(key, oldKey, mediaData) } } @Deprecated("Cleanup when media_load_metadata_via_media_data_loader is cleaned up") @@ -1001,13 +1009,13 @@ class MediaDataProcessor( } // Control buttons - // If flag is enabled and controller has a PlaybackState, create actions from session info + // If controller has a PlaybackState, create actions from session info // Otherwise, use the notification actions - var actionIcons: List<MediaAction> = emptyList() + var actionIcons: List<MediaNotificationAction> = emptyList() var actionsToShowCollapsed: List<Int> = emptyList() val semanticActions = createActionsFromState(sbn.packageName, mediaController, sbn.user) if (semanticActions == null) { - val actions = createActionsFromNotification(context, activityStarter, sbn) + val actions = createActionsFromNotification(context, sbn) actionIcons = actions.first actionsToShowCollapsed = actions.second } @@ -1022,57 +1030,72 @@ class MediaDataProcessor( else MediaData.PLAYBACK_CAST_LOCAL val isPlaying = mediaController.playbackState?.let { isPlayingState(it.state) } - val currentEntry = mediaDataRepository.mediaEntries.value.get(key) - val instanceId = currentEntry?.instanceId ?: logger.getNewInstanceId() + val oldEntry = mediaDataRepository.mediaEntries.value.get(key) + val instanceId = oldEntry?.instanceId ?: logger.getNewInstanceId() val appUid = appInfo?.uid ?: Process.INVALID_UID + val lastActive = systemClock.elapsedRealtime() + val createdTimestampMillis = oldEntry?.createdTimestampMillis ?: 0L + val resumeAction: Runnable? = mediaDataRepository.mediaEntries.value[key]?.resumeAction + val hasCheckedForResume = + mediaDataRepository.mediaEntries.value[key]?.hasCheckedForResume == true + val active = mediaDataRepository.mediaEntries.value[key]?.active ?: true + var mediaData = + MediaData( + sbn.normalizedUserId, + true, + appName, + smallIcon, + artist, + song, + artWorkIcon, + actionIcons, + actionsToShowCollapsed, + semanticActions, + sbn.packageName, + token, + notif.contentIntent, + device, + active, + resumeAction = resumeAction, + playbackLocation = playbackLocation, + notificationKey = key, + hasCheckedForResume = hasCheckedForResume, + isPlaying = isPlaying, + isClearable = !sbn.isOngoing, + lastActive = lastActive, + createdTimestampMillis = createdTimestampMillis, + instanceId = instanceId, + appUid = appUid, + isExplicit = isExplicit, + smartspaceId = SmallHash.hash(appUid + systemClock.currentTimeMillis().toInt()), + ) + + if (isSameMediaData(context, mediaController, mediaData, oldEntry)) { + mediaLogger.logDuplicateMediaNotification(key) + return + } + if (isNewlyActiveEntry) { logSingleVsMultipleMediaAdded(appUid, sbn.packageName, instanceId) logger.logActiveMediaAdded(appUid, sbn.packageName, instanceId, playbackLocation) - } else if (playbackLocation != currentEntry?.playbackLocation) { + } else if (playbackLocation != oldEntry?.playbackLocation) { logger.logPlaybackLocationChange(appUid, sbn.packageName, instanceId, playbackLocation) } - val lastActive = systemClock.elapsedRealtime() - val createdTimestampMillis = currentEntry?.createdTimestampMillis ?: 0L foregroundExecutor.execute { - val resumeAction: Runnable? = mediaDataRepository.mediaEntries.value[key]?.resumeAction - val hasCheckedForResume = + val oldResumeAction: Runnable? = + mediaDataRepository.mediaEntries.value[key]?.resumeAction + val oldHasCheckedForResume = mediaDataRepository.mediaEntries.value[key]?.hasCheckedForResume == true - val active = mediaDataRepository.mediaEntries.value[key]?.active ?: true - onMediaDataLoaded( - key, - oldKey, - MediaData( - sbn.normalizedUserId, - true, - appName, - smallIcon, - artist, - song, - artWorkIcon, - actionIcons, - actionsToShowCollapsed, - semanticActions, - sbn.packageName, - token, - notif.contentIntent, - device, - active, - resumeAction = resumeAction, - playbackLocation = playbackLocation, - notificationKey = key, - hasCheckedForResume = hasCheckedForResume, - isPlaying = isPlaying, - isClearable = !sbn.isOngoing, - lastActive = lastActive, - createdTimestampMillis = createdTimestampMillis, - instanceId = instanceId, - appUid = appUid, - isExplicit = isExplicit, - smartspaceId = SmallHash.hash(appUid + systemClock.currentTimeMillis().toInt()), + val oldActive = mediaDataRepository.mediaEntries.value[key]?.active ?: true + mediaData = + mediaData.copy( + resumeAction = oldResumeAction, + hasCheckedForResume = oldHasCheckedForResume, + active = oldActive ) - ) + onMediaDataLoaded(key, oldKey, mediaData) } } @@ -1402,7 +1425,7 @@ class MediaDataProcessor( val updated = data.copy( token = null, - actions = actions, + actions = listOf(), semanticActions = MediaButton(playOrPause = resumeAction), actionsToShowInCompact = listOf(0), active = false, diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaProcessingHelper.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaProcessingHelper.kt new file mode 100644 index 000000000000..55d7b1d498e0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaProcessingHelper.kt @@ -0,0 +1,179 @@ +/* + * 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.media.controls.domain.pipeline + +import android.annotation.WorkerThread +import android.app.PendingIntent +import android.content.Context +import android.graphics.drawable.Icon +import android.media.session.MediaController +import android.media.session.PlaybackState +import android.util.Log +import com.android.systemui.Flags.mediaControlsPostsOptimization +import com.android.systemui.biometrics.Utils.toBitmap +import com.android.systemui.media.controls.shared.model.MediaData + +private const val TAG = "MediaProcessingHelper" + +/** + * Compares [new] media data to [old] media data. + * + * @param context Context + * @param newController media controller of the new media data. + * @param new new media data. + * @param old old media data. + * @return whether new and old contain same data + */ +fun isSameMediaData( + context: Context, + newController: MediaController, + new: MediaData, + old: MediaData? +): Boolean { + if (old == null || !mediaControlsPostsOptimization()) return false + + return new.userId == old.userId && + new.app == old.app && + new.artist == old.artist && + new.song == old.song && + new.packageName == old.packageName && + new.isExplicit == old.isExplicit && + new.appUid == old.appUid && + new.notificationKey == old.notificationKey && + new.isPlaying == old.isPlaying && + new.isClearable == old.isClearable && + new.playbackLocation == old.playbackLocation && + new.device == old.device && + new.initialized == old.initialized && + new.resumption == old.resumption && + new.token == old.token && + new.resumeProgress == old.resumeProgress && + areClickIntentsEqual(new.clickIntent, old.clickIntent) && + areActionsEqual(context, newController, new, old) && + areIconsEqual(context, new.artwork, old.artwork) && + areIconsEqual(context, new.appIcon, old.appIcon) +} + +/** Returns whether actions lists are equal. */ +fun areCustomActionListsEqual( + first: List<PlaybackState.CustomAction>?, + second: List<PlaybackState.CustomAction>? +): Boolean { + // Same object, or both null + if (first === second) { + return true + } + + // Only one null, or different number of actions + if ((first == null || second == null) || (first.size != second.size)) { + return false + } + + // Compare individual actions + first.asSequence().zip(second.asSequence()).forEach { (firstAction, secondAction) -> + if (!areCustomActionsEqual(firstAction, secondAction)) { + return false + } + } + return true +} + +private fun areCustomActionsEqual( + firstAction: PlaybackState.CustomAction, + secondAction: PlaybackState.CustomAction +): Boolean { + if ( + firstAction.action != secondAction.action || + firstAction.name != secondAction.name || + firstAction.icon != secondAction.icon + ) { + return false + } + + if ((firstAction.extras == null) != (secondAction.extras == null)) { + return false + } + if (firstAction.extras != null) { + firstAction.extras.keySet().forEach { key -> + if (firstAction.extras[key] != secondAction.extras[key]) { + return false + } + } + } + return true +} + +@WorkerThread +private fun areIconsEqual(context: Context, new: Icon?, old: Icon?): Boolean { + if (new == old) return true + if (new == null || old == null || new.type != old.type) return false + return if (new.type == Icon.TYPE_BITMAP || new.type == Icon.TYPE_ADAPTIVE_BITMAP) { + if (new.bitmap.isRecycled || old.bitmap.isRecycled) { + Log.e(TAG, "Cannot compare recycled bitmap") + return false + } + new.bitmap.sameAs(old.bitmap) + } else { + val newDrawable = new.loadDrawable(context) + val oldDrawable = old.loadDrawable(context) + + return newDrawable?.toBitmap()?.sameAs(oldDrawable?.toBitmap()) ?: false + } +} + +private fun areActionsEqual( + context: Context, + newController: MediaController, + new: MediaData, + old: MediaData +): Boolean { + val oldState = MediaController(context, old.token!!).playbackState + return if ( + new.semanticActions == null && + old.semanticActions == null && + new.actions.size == old.actions.size + ) { + var same = true + new.actions.asSequence().zip(old.actions.asSequence()).forEach { + if ( + it.first.actionIntent?.intent?.filterEquals(it.second.actionIntent?.intent) != + true || + it.first.icon != it.second.icon || + it.first.contentDescription != it.second.contentDescription + ) { + same = false + return@forEach + } + } + same + } else if (new.semanticActions != null && old.semanticActions != null) { + oldState?.actions == newController.playbackState?.actions && + areCustomActionListsEqual( + oldState?.customActions, + newController.playbackState?.customActions + ) + } else { + false + } +} + +private fun areClickIntentsEqual(newIntent: PendingIntent?, oldIntent: PendingIntent?): Boolean { + if ((newIntent == null && oldIntent == null) || newIntent === oldIntent) return true + if (newIntent == null || oldIntent == null) return false + + return newIntent.intent?.filterEquals(oldIntent.intent) == true +} diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListener.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListener.kt index fc319036d67e..275f1eecd4db 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListener.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListener.kt @@ -16,12 +16,14 @@ package com.android.systemui.media.controls.domain.pipeline +import android.annotation.WorkerThread import android.media.session.MediaController import android.media.session.MediaSession import android.media.session.PlaybackState import android.os.SystemProperties import com.android.internal.annotations.VisibleForTesting import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.media.controls.shared.model.MediaData import com.android.systemui.media.controls.shared.model.SmartspaceMediaData @@ -32,6 +34,7 @@ import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState import com.android.systemui.statusbar.SysuiStatusBarStateController import com.android.systemui.util.concurrency.DelayableExecutor import com.android.systemui.util.time.SystemClock +import java.util.concurrent.Executor import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -49,6 +52,8 @@ class MediaTimeoutListener @Inject constructor( private val mediaControllerFactory: MediaControllerFactory, + @Background private val bgExecutor: Executor, + @Main private val uiExecutor: Executor, @Main private val mainExecutor: DelayableExecutor, private val logger: MediaTimeoutLogger, statusBarStateController: SysuiStatusBarStateController, @@ -147,19 +152,21 @@ constructor( } reusedListener?.let { - val wasPlaying = it.isPlaying() - logger.logUpdateListener(key, wasPlaying) - it.setMediaData(data) - it.key = key - mediaListeners[key] = it - if (wasPlaying != it.isPlaying()) { - // If a player becomes active because of a migration, we'll need to broadcast - // its state. Doing it now would lead to reentrant callbacks, so let's wait - // until we're done. - mainExecutor.execute { - if (mediaListeners[key]?.isPlaying() == true) { - logger.logDelayedUpdate(key) - timeoutCallback.invoke(key, false /* timedOut */) + bgExecutor.execute { + val wasPlaying = it.isPlaying() + logger.logUpdateListener(key, wasPlaying) + it.setMediaData(data) + it.key = key + mediaListeners[key] = it + if (wasPlaying != it.isPlaying()) { + // If a player becomes active because of a migration, we'll need to broadcast + // its state. Doing it now would lead to reentrant callbacks, so let's wait + // until we're done. + mainExecutor.execute { + if (mediaListeners[key]?.isPlaying() == true) { + logger.logDelayedUpdate(key) + timeoutCallback.invoke(key, false /* timedOut */) + } } } } @@ -217,18 +224,20 @@ constructor( private set fun Int.isPlaying() = isPlayingState(this) + fun isPlaying() = lastState?.state?.isPlaying() ?: false init { - setMediaData(data) + bgExecutor.execute { setMediaData(data) } } fun destroy() { - mediaController?.unregisterCallback(this) + bgExecutor.execute { mediaController?.unregisterCallback(this) } cancellation?.run() destroyed = true } + @WorkerThread fun setMediaData(data: MediaData) { sessionToken = data.token destroyed = false @@ -258,7 +267,7 @@ constructor( if (resumption == true) { // Some apps create a session when MBS is queried. We should unregister the // controller since it will no longer be valid, but don't cancel the timeout - mediaController?.unregisterCallback(this) + bgExecutor.execute { mediaController?.unregisterCallback(this) } } else { // For active controls, if the session is destroyed, clean up everything since we // will need to recreate it if this key is updated later @@ -284,7 +293,7 @@ constructor( if ((!actionsSame || !playingStateSame) && state != null && dispatchEvents) { logger.logStateCallback(key) - stateCallback.invoke(key, state) + uiExecutor.execute { stateCallback.invoke(key, state) } } if (playingStateSame && !resumptionChanged) { @@ -313,7 +322,7 @@ constructor( expireMediaTimeout(key, "playback started - $state, $key") timedOut = false if (dispatchEvents) { - timeoutCallback(key, timedOut) + uiExecutor.execute { timeoutCallback(key, timedOut) } } } } @@ -337,60 +346,13 @@ constructor( } } - private fun areCustomActionListsEqual( - first: List<PlaybackState.CustomAction>?, - second: List<PlaybackState.CustomAction>? - ): Boolean { - // Same object, or both null - if (first === second) { - return true - } - - // Only one null, or different number of actions - if ((first == null || second == null) || (first.size != second.size)) { - return false - } - - // Compare individual actions - first.asSequence().zip(second.asSequence()).forEach { (firstAction, secondAction) -> - if (!areCustomActionsEqual(firstAction, secondAction)) { - return false - } - } - return true - } - - private fun areCustomActionsEqual( - firstAction: PlaybackState.CustomAction, - secondAction: PlaybackState.CustomAction - ): Boolean { - if ( - firstAction.action != secondAction.action || - firstAction.name != secondAction.name || - firstAction.icon != secondAction.icon - ) { - return false - } - - if ((firstAction.extras == null) != (secondAction.extras == null)) { - return false - } - if (firstAction.extras != null) { - firstAction.extras.keySet().forEach { key -> - if (firstAction.extras.get(key) != secondAction.extras.get(key)) { - return false - } - } - } - return true - } - /** Listens to changes in recommendation card data and schedules a timeout for its expiration */ private inner class RecommendationListener(var key: String, data: SmartspaceMediaData) { private var timedOut = false var destroyed = false var expiration = Long.MAX_VALUE private set + var cancellation: Runnable? = null private set diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt index 245f6f8bf2f6..130868dc3c1c 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt @@ -32,6 +32,7 @@ import com.android.systemui.animation.Expandable import com.android.systemui.bluetooth.BroadcastDialogController import com.android.systemui.media.controls.data.repository.MediaFilterRepository import com.android.systemui.media.controls.domain.pipeline.MediaDataProcessor +import com.android.systemui.media.controls.domain.pipeline.getNotificationActions import com.android.systemui.media.controls.shared.model.MediaControlModel import com.android.systemui.media.controls.shared.model.MediaData import com.android.systemui.media.controls.util.MediaSmartspaceLogger @@ -102,7 +103,7 @@ constructor( artwork = artwork, deviceData = device, semanticActionButtons = semanticActions, - notificationActionButtons = actions, + notificationActionButtons = getNotificationActions(data.actions, activityStarter), actionsToShowInCollapsed = actionsToShowInCompact, isDismissible = isClearable, isResume = resumption, diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaLogger.kt index 2b710b5a67b7..7d20e170d8bc 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaLogger.kt @@ -114,6 +114,15 @@ class MediaLogger @Inject constructor(@MediaLog private val buffer: LogBuffer) { ) } + fun logDuplicateMediaNotification(key: String) { + buffer.log( + TAG, + LogLevel.DEBUG, + { str1 = key }, + { "duplicate media notification $str1 posted" } + ) + } + companion object { private const val TAG = "MediaLog" } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaData.kt index 40b34779151d..aed86090ef01 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaData.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaData.kt @@ -39,7 +39,7 @@ data class MediaData( /** Album artwork. */ val artwork: Icon? = null, /** List of generic action buttons for the media player, based on notification actions */ - val actions: List<MediaAction> = emptyList(), + val actions: List<MediaNotificationAction> = emptyList(), /** Same as above, but shown on smaller versions of the player, like in QQS or keyguard. */ val actionsToShowInCompact: List<Int> = emptyList(), /** @@ -162,6 +162,14 @@ data class MediaAction( val rebindId: Int? = null ) +/** State of a media action from notification. */ +data class MediaNotificationAction( + val isAuthenticationRequired: Boolean, + val actionIntent: PendingIntent?, + val icon: Drawable?, + val contentDescription: CharSequence? +) + /** State of the media device. */ data class MediaDeviceData @JvmOverloads diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java index 87610cf774a3..8bec46abd504 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java @@ -21,6 +21,7 @@ import static android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS; import static com.android.settingslib.flags.Flags.legacyLeAudioSharing; import static com.android.systemui.Flags.communalHub; import static com.android.systemui.Flags.mediaLockscreenLaunchAnimation; +import static com.android.systemui.media.controls.domain.pipeline.MediaActionsKt.getNotificationActions; import static com.android.systemui.media.controls.shared.model.SmartspaceMediaDataKt.NUM_REQUIRED_RECOMMENDATIONS; import android.animation.Animator; @@ -1170,7 +1171,7 @@ public class MediaControlPanel { // Set all the generic buttons List<Integer> actionsWhenCollapsed = data.getActionsToShowInCompact(); - List<MediaAction> actions = data.getActions(); + List<MediaAction> actions = getNotificationActions(data.getActions(), mActivityStarter); int i = 0; for (; i < actions.size() && i < genericButtons.size(); i++) { boolean showInCompact = actionsWhenCollapsed.contains(i); diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java index 6bd880d56bbb..89d76f0038ee 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java @@ -29,6 +29,7 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_T import static java.util.stream.Collectors.joining; import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.ActivityManager; import android.content.ComponentName; import android.content.Context; @@ -1331,21 +1332,20 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack } } - public void setBackAnimation(BackAnimation backAnimation) { + public void setBackAnimation(@Nullable BackAnimation backAnimation) { mBackAnimation = backAnimation; - mBackAnimation.setPilferPointerCallback(() -> { - pilferPointers(); - }); - mBackAnimation.setTopUiRequestCallback( - (requestTopUi, tag) -> mUiThreadContext.getExecutor().execute(() -> - mNotificationShadeWindowController.setRequestTopUi(requestTopUi, tag))); - updateBackAnimationThresholds(); - if (mLightBarControllerProvider.get() != null) { - mBackAnimation.setStatusBarCustomizer((appearance) -> { - mUiThreadContext.getExecutor().execute(() -> - mLightBarControllerProvider.get() - .customizeStatusBarAppearance(appearance)); - }); + if (backAnimation != null) { + backAnimation.setPilferPointerCallback(this::pilferPointers); + backAnimation.setTopUiRequestCallback( + (requestTopUi, tag) -> mUiThreadContext.getExecutor().execute(() -> + mNotificationShadeWindowController.setRequestTopUi(requestTopUi, tag))); + updateBackAnimationThresholds(); + if (mLightBarControllerProvider.get() != null) { + mBackAnimation.setStatusBarCustomizer((appearance) -> + mUiThreadContext.getExecutor().execute(() -> + mLightBarControllerProvider.get() + .customizeStatusBarAppearance(appearance))); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HearingDevicesTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HearingDevicesTile.java index b96e83d43e32..f723ff264e0c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HearingDevicesTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HearingDevicesTile.java @@ -16,6 +16,8 @@ package com.android.systemui.qs.tiles; +import static com.android.systemui.accessibility.hearingaid.HearingDevicesUiEventLogger.LAUNCH_SOURCE_QS_TILE; + import android.content.Intent; import android.os.Handler; import android.os.Looper; @@ -96,7 +98,7 @@ public class HearingDevicesTile extends QSTileImpl<BooleanState> { @Override protected void handleClick(@Nullable Expandable expandable) { - mUiHandler.post(() -> mDialogManager.showDialog(expandable)); + mUiHandler.post(() -> mDialogManager.showDialog(expandable, LAUNCH_SOURCE_QS_TILE)); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt index a3feb2b09da3..d89e73d2c69f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt @@ -43,12 +43,14 @@ import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor import com.android.systemui.qs.tileimpl.QSTileImpl -import com.android.systemui.recordissue.IssueRecordingService +import com.android.systemui.recordissue.IssueRecordingService.Companion.getStartIntent +import com.android.systemui.recordissue.IssueRecordingService.Companion.getStopIntent import com.android.systemui.recordissue.IssueRecordingState import com.android.systemui.recordissue.RecordIssueDialogDelegate import com.android.systemui.recordissue.RecordIssueModule.Companion.TILE_SPEC import com.android.systemui.recordissue.TraceurMessageSender import com.android.systemui.res.R +import com.android.systemui.screenrecord.RecordingController import com.android.systemui.screenrecord.RecordingService import com.android.systemui.settings.UserContextProvider import com.android.systemui.statusbar.phone.KeyguardDismissUtil @@ -56,6 +58,9 @@ import com.android.systemui.statusbar.policy.KeyguardStateController import java.util.concurrent.Executor import javax.inject.Inject +const val DELAY_MS: Long = 0 +const val INTERVAL_MS: Long = 1000 + class RecordIssueTile @Inject constructor( @@ -77,6 +82,7 @@ constructor( @Background private val bgExecutor: Executor, private val issueRecordingState: IssueRecordingState, private val delegateFactory: RecordIssueDialogDelegate.Factory, + private val recordingController: RecordingController, ) : QSTileImpl<QSTile.BooleanState>( host, @@ -132,23 +138,25 @@ constructor( } private fun startIssueRecordingService() = - PendingIntent.getForegroundService( - userContextProvider.userContext, - RecordingService.REQUEST_CODE, - IssueRecordingService.getStartIntent(userContextProvider.userContext), - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE - ) - .send(BroadcastOptions.makeBasic().apply { isInteractive = true }.toBundle()) + recordingController.startCountdown( + DELAY_MS, + INTERVAL_MS, + pendingServiceIntent(getStartIntent(userContextProvider.userContext)), + pendingServiceIntent(getStopIntent(userContextProvider.userContext)) + ) private fun stopIssueRecordingService() = - PendingIntent.getService( - userContextProvider.userContext, - RecordingService.REQUEST_CODE, - IssueRecordingService.getStopIntent(userContextProvider.userContext), - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE - ) + pendingServiceIntent(getStopIntent(userContextProvider.userContext)) .send(BroadcastOptions.makeBasic().apply { isInteractive = true }.toBundle()) + private fun pendingServiceIntent(action: Intent) = + PendingIntent.getService( + userContextProvider.userContext, + RecordingService.REQUEST_CODE, + action, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + private fun showPrompt(expandable: Expandable?) { val dialog: AlertDialog = delegateFactory diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingUserActionInteractor.kt index 4971fefc8f57..0c8a3750f6fe 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingUserActionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingUserActionInteractor.kt @@ -19,6 +19,7 @@ package com.android.systemui.qs.tiles.impl.irecording import android.app.AlertDialog import android.app.BroadcastOptions import android.app.PendingIntent +import android.content.Intent import android.util.Log import com.android.internal.jank.InteractionJankMonitor import com.android.systemui.animation.DialogCuj @@ -27,12 +28,16 @@ import com.android.systemui.animation.Expandable import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.plugins.ActivityStarter import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor +import com.android.systemui.qs.tiles.DELAY_MS +import com.android.systemui.qs.tiles.INTERVAL_MS import com.android.systemui.qs.tiles.base.interactor.QSTileInput import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction -import com.android.systemui.recordissue.IssueRecordingService +import com.android.systemui.recordissue.IssueRecordingService.Companion.getStartIntent +import com.android.systemui.recordissue.IssueRecordingService.Companion.getStopIntent import com.android.systemui.recordissue.RecordIssueDialogDelegate import com.android.systemui.recordissue.RecordIssueModule.Companion.TILE_SPEC +import com.android.systemui.screenrecord.RecordingController import com.android.systemui.screenrecord.RecordingService import com.android.systemui.settings.UserContextProvider import com.android.systemui.statusbar.phone.KeyguardDismissUtil @@ -53,6 +58,7 @@ constructor( private val panelInteractor: PanelInteractor, private val userContextProvider: UserContextProvider, private val delegateFactory: RecordIssueDialogDelegate.Factory, + private val recordingController: RecordingController, ) : QSTileUserActionInteractor<IssueRecordingModel> { override suspend fun handleInput(input: QSTileInput<IssueRecordingModel>) { @@ -95,20 +101,22 @@ constructor( } private fun startIssueRecordingService() = - PendingIntent.getForegroundService( - userContextProvider.userContext, - RecordingService.REQUEST_CODE, - IssueRecordingService.getStartIntent(userContextProvider.userContext), - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE - ) - .send(BroadcastOptions.makeBasic().apply { isInteractive = true }.toBundle()) + recordingController.startCountdown( + DELAY_MS, + INTERVAL_MS, + pendingServiceIntent(getStartIntent(userContextProvider.userContext)), + pendingServiceIntent(getStopIntent(userContextProvider.userContext)) + ) private fun stopIssueRecordingService() = - PendingIntent.getService( - userContextProvider.userContext, - RecordingService.REQUEST_CODE, - IssueRecordingService.getStopIntent(userContextProvider.userContext), - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE - ) + pendingServiceIntent(getStopIntent(userContextProvider.userContext)) .send(BroadcastOptions.makeBasic().apply { isInteractive = true }.toBundle()) + + private fun pendingServiceIntent(action: Intent) = + PendingIntent.getService( + userContextProvider.userContext, + RecordingService.REQUEST_CODE, + action, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) } 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 c2d112edd65d..483373d8fb6d 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,13 +20,16 @@ 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.Icon import com.android.systemui.common.shared.model.asIcon import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.qs.tiles.ModesTile 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.statusbar.policy.domain.interactor.ZenModeInteractor import com.android.systemui.statusbar.policy.domain.model.ActiveZenModes +import com.android.systemui.statusbar.policy.domain.model.ZenModeInfo import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow @@ -61,28 +64,39 @@ constructor( suspend fun getCurrentTileModel() = buildTileData(zenModeInteractor.getActiveModes()) private fun buildTileData(activeModes: ActiveZenModes): ModesTileModel { - 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 - + val tileIcon = getTileIcon(activeModes.mainMode) return ModesTileModel( isActivated = activeModes.isAnyActive(), - icon = (mainModeDrawable ?: context.getDrawable(modesIconResId)!!).asIcon(), - iconResId = iconResId, + icon = tileIcon.icon, + iconResId = tileIcon.resId, activeModes = activeModes.modeNames ) } else { return ModesTileModel( isActivated = activeModes.isAnyActive(), - icon = context.getDrawable(modesIconResId)!!.asIcon(), - iconResId = modesIconResId, + icon = context.getDrawable(ModesTile.ICON_RES_ID)!!.asIcon(), + iconResId = ModesTile.ICON_RES_ID, activeModes = activeModes.modeNames ) } } + private data class TileIcon(val icon: Icon.Loaded, val resId: Int?) + + private fun getTileIcon(activeMode: ZenModeInfo?): TileIcon { + return if (activeMode != null) { + // ZenIconKey.resPackage is null if its resId is a system icon. + if (activeMode.icon.key.resPackage == null) { + TileIcon(activeMode.icon.drawable.asIcon(), activeMode.icon.key.resId) + } else { + TileIcon(activeMode.icon.drawable.asIcon(), null) + } + } else { + TileIcon(context.getDrawable(ModesTile.ICON_RES_ID)!!.asIcon(), ModesTile.ICON_RES_ID) + } + } + 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/ui/ModesTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt index 7f571b135fc8..69da3134314b 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 @@ -36,9 +36,7 @@ constructor( ) : QSTileDataToStateMapper<ModesTileModel> { override fun map(config: QSTileConfig, data: ModesTileModel): QSTileState = QSTileState.build(resources, theme, config.uiConfig) { - if (!android.app.Flags.modesUiIcons()) { - iconRes = data.iconResId - } + iconRes = data.iconResId icon = { data.icon } activationState = if (data.isActivated) { diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt index 3d6d00eb3cc0..a5f4a8959569 100644 --- a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt +++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt @@ -23,8 +23,6 @@ import android.content.Intent import android.content.res.Resources import android.net.Uri import android.os.Handler -import android.os.UserHandle -import android.provider.Settings import android.util.Log import com.android.internal.logging.UiEventLogger import com.android.systemui.animation.DialogTransitionAnimator @@ -50,11 +48,11 @@ constructor( notificationManager: NotificationManager, userContextProvider: UserContextProvider, keyguardDismissUtil: KeyguardDismissUtil, - private val dialogTransitionAnimator: DialogTransitionAnimator, - private val panelInteractor: PanelInteractor, - private val traceurMessageSender: TraceurMessageSender, + dialogTransitionAnimator: DialogTransitionAnimator, + panelInteractor: PanelInteractor, + traceurMessageSender: TraceurMessageSender, private val issueRecordingState: IssueRecordingState, - private val iActivityManager: IActivityManager, + iActivityManager: IActivityManager, ) : RecordingService( controller, @@ -66,6 +64,18 @@ constructor( keyguardDismissUtil ) { + private val commandHandler = + IssueRecordingServiceCommandHandler( + bgExecutor, + dialogTransitionAnimator, + panelInteractor, + traceurMessageSender, + issueRecordingState, + iActivityManager, + notificationManager, + userContextProvider, + ) + override fun getTag(): String = TAG override fun getChannelId(): String = CHANNEL_ID @@ -76,10 +86,7 @@ constructor( Log.d(getTag(), "handling action: ${intent?.action}") when (intent?.action) { ACTION_START -> { - bgExecutor.execute { - traceurMessageSender.startTracing(issueRecordingState.traceConfig) - } - issueRecordingState.isRecording = true + commandHandler.handleStartCommand() if (!issueRecordingState.recordScreen) { // If we don't want to record the screen, the ACTION_SHOW_START_NOTIF action // will circumvent the RecordingService's screen recording start code. @@ -87,41 +94,13 @@ constructor( } } ACTION_STOP, - ACTION_STOP_NOTIF -> { - // ViewCapture needs to save it's data before it is disabled, or else the data will - // be lost. This is expected to change in the near future, and when that happens - // this line should be removed. - bgExecutor.execute { - if (issueRecordingState.traceConfig.longTrace) { - Settings.Global.putInt( - contentResolver, - NOTIFY_SESSION_ENDED_SETTING, - DISABLED - ) - } - traceurMessageSender.stopTracing() - } - issueRecordingState.isRecording = false - } + ACTION_STOP_NOTIF -> commandHandler.handleStopCommand(contentResolver) ACTION_SHARE -> { - bgExecutor.execute { - mNotificationManager.cancelAsUser( - null, - intent.getIntExtra(EXTRA_NOTIFICATION_ID, mNotificationId), - UserHandle(mUserContextTracker.userContext.userId) - ) - - val screenRecording = intent.getParcelableExtra(EXTRA_PATH, Uri::class.java) - if (issueRecordingState.takeBugreport) { - iActivityManager.requestBugReportWithExtraAttachment(screenRecording) - } else { - traceurMessageSender.shareTraces(applicationContext, screenRecording) - } - } - - dialogTransitionAnimator.disableAllCurrentDialogsExitAnimations() - panelInteractor.collapsePanels() - + commandHandler.handleShareCommand( + intent.getIntExtra(EXTRA_NOTIFICATION_ID, mNotificationId), + intent.getParcelableExtra(EXTRA_PATH, Uri::class.java), + this + ) // Unlike all other actions, action_share has different behavior for the screen // recording qs tile than it does for the record issue qs tile. Return sticky to // avoid running any of the base class' code for this action. @@ -135,8 +114,6 @@ constructor( companion object { private const val TAG = "IssueRecordingService" private const val CHANNEL_ID = "issue_record" - private const val NOTIFY_SESSION_ENDED_SETTING = "should_notify_trace_session_ended" - private const val DISABLED = 0 /** * Get an intent to stop the issue recording service. diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingServiceCommandHandler.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingServiceCommandHandler.kt new file mode 100644 index 000000000000..32de0f353502 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingServiceCommandHandler.kt @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.recordissue + +import android.app.IActivityManager +import android.app.NotificationManager +import android.content.ContentResolver +import android.content.Context +import android.net.Uri +import android.os.UserHandle +import android.provider.Settings +import com.android.systemui.animation.DialogTransitionAnimator +import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor +import com.android.systemui.settings.UserContextProvider +import java.util.concurrent.Executor + +private const val NOTIFY_SESSION_ENDED_SETTING = "should_notify_trace_session_ended" +private const val DISABLED = 0 + +/** + * This class exists to unit test the business logic encapsulated in IssueRecordingService. Android + * specifically calls out that there is no supported way to test IntentServices here: + * https://developer.android.com/training/testing/other-components/services + */ +class IssueRecordingServiceCommandHandler( + private val bgExecutor: Executor, + private val dialogTransitionAnimator: DialogTransitionAnimator, + private val panelInteractor: PanelInteractor, + private val traceurMessageSender: TraceurMessageSender, + private val issueRecordingState: IssueRecordingState, + private val iActivityManager: IActivityManager, + private val notificationManager: NotificationManager, + private val userContextProvider: UserContextProvider, +) { + + fun handleStartCommand() { + bgExecutor.execute { traceurMessageSender.startTracing(issueRecordingState.traceConfig) } + issueRecordingState.isRecording = true + } + + fun handleStopCommand(contentResolver: ContentResolver) { + bgExecutor.execute { + if (issueRecordingState.traceConfig.longTrace) { + Settings.Global.putInt(contentResolver, NOTIFY_SESSION_ENDED_SETTING, DISABLED) + } + traceurMessageSender.stopTracing() + } + issueRecordingState.isRecording = false + } + + fun handleShareCommand(notificationId: Int, screenRecording: Uri?, context: Context) { + bgExecutor.execute { + notificationManager.cancelAsUser( + null, + notificationId, + UserHandle(userContextProvider.userContext.userId) + ) + + if (issueRecordingState.takeBugreport) { + iActivityManager.requestBugReportWithExtraAttachment(screenRecording) + } else { + traceurMessageSender.shareTraces(context, screenRecording) + } + } + + dialogTransitionAnimator.disableAllCurrentDialogsExitAnimations() + panelInteractor.collapsePanels() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt index e251c9edb57f..98907b037d85 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt @@ -22,7 +22,9 @@ import android.app.StatusBarManager import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.SceneKey import com.android.internal.logging.UiEventLogger +import com.android.keyguard.AuthInteractionProperties import com.android.systemui.CoreStartable +import com.android.systemui.Flags import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor @@ -73,6 +75,8 @@ import com.android.systemui.util.kotlin.pairwise import com.android.systemui.util.kotlin.sample import com.android.systemui.util.printSection import com.android.systemui.util.println +import com.google.android.msdl.data.model.MSDLToken +import com.google.android.msdl.domain.MSDLPlayer import dagger.Lazy import java.io.PrintWriter import java.util.Optional @@ -139,10 +143,13 @@ constructor( private val statusBarStateController: SysuiStatusBarStateController, private val alternateBouncerInteractor: AlternateBouncerInteractor, private val vibratorHelper: VibratorHelper, + private val msdlPlayer: MSDLPlayer, ) : CoreStartable { private val centralSurfaces: CentralSurfaces? get() = centralSurfacesOptLazy.get().getOrNull() + private val authInteractionProperties = AuthInteractionProperties() + override fun start() { if (SceneContainerFlag.isEnabled) { sceneLogger.logFrameworkEnabled(isEnabled = true) @@ -541,9 +548,16 @@ constructor( deviceEntryHapticsInteractor.playSuccessHaptic .sample(sceneInteractor.currentScene) .collect { currentScene -> - vibratorHelper.vibrateAuthSuccess( - "$TAG, $currentScene device-entry::success" - ) + if (Flags.msdlFeedback()) { + msdlPlayer.playToken( + MSDLToken.UNLOCK, + authInteractionProperties, + ) + } else { + vibratorHelper.vibrateAuthSuccess( + "$TAG, $currentScene device-entry::success" + ) + } } } @@ -551,9 +565,16 @@ constructor( deviceEntryHapticsInteractor.playErrorHaptic .sample(sceneInteractor.currentScene) .collect { currentScene -> - vibratorHelper.vibrateAuthError( - "$TAG, $currentScene device-entry::error" - ) + if (Flags.msdlFeedback()) { + msdlPlayer.playToken( + MSDLToken.FAILURE, + authInteractionProperties, + ) + } else { + vibratorHelper.vibrateAuthError( + "$TAG, $currentScene device-entry::error" + ) + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt index ed590c37c384..553d1f51a198 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt @@ -103,11 +103,8 @@ internal constructor( override val userContentResolver: ContentResolver get() = userContext.contentResolver - override val userInfo: UserInfo - get() { - val user = userId - return userProfiles.first { it.id == user } - } + override var userInfo: UserInfo by SynchronizedDelegate(UserInfo(context.userId, "", 0)) + protected set /** * Returns a [List<UserInfo>] of all profiles associated with the current user. @@ -187,6 +184,7 @@ internal constructor( userHandle = handle userContext = ctx userProfiles = profiles.map { UserInfo(it) } + userInfo = profiles.first { it.id == user } } return ctx to profiles } diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java index 830649be2a98..4ed4af647fdf 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java @@ -42,6 +42,7 @@ import android.util.Log; import android.util.MathUtils; import android.view.MotionEvent; import android.view.VelocityTracker; +import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.WindowInsets; @@ -463,6 +464,9 @@ public class QuickSettingsControllerImpl implements QuickSettingsController, Dum mJavaAdapter.alwaysCollectFlow( mCommunalTransitionViewModelLazy.get().isUmoOnCommunal(), this::setShouldUpdateSquishinessOnMedia); + mJavaAdapter.alwaysCollectFlow( + mShadeInteractor.isAnyExpanded(), + this::onAnyExpandedChanged); } private void initNotificationStackScrollLayoutController() { @@ -482,6 +486,10 @@ public class QuickSettingsControllerImpl implements QuickSettingsController, Dum } } + private void onAnyExpandedChanged(boolean isAnyExpanded) { + mQsFrame.setVisibility(isAnyExpanded ? View.VISIBLE : View.INVISIBLE); + } + private void onNotificationScrolled(int newScrollPosition) { updateExpansionEnabledAmbient(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java index 64dfc6c9eb3c..735b4c34f86f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java @@ -54,6 +54,8 @@ public class VibratorHelper { VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK); private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES = VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK); + private static final VibrationAttributes COMMUNICATION_REQUEST_VIBRATION_ATTRIBUTES = + VibrationAttributes.createForUsage(VibrationAttributes.USAGE_COMMUNICATION_REQUEST); private final Executor mExecutor; @@ -151,7 +153,7 @@ public class VibratorHelper { vibrate(Process.myUid(), "com.android.systemui", BIOMETRIC_SUCCESS_VIBRATION_EFFECT, reason, - HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES); + COMMUNICATION_REQUEST_VIBRATION_ATTRIBUTES); } /** @@ -160,7 +162,7 @@ public class VibratorHelper { public void vibrateAuthError(String reason) { vibrate(Process.myUid(), "com.android.systemui", BIOMETRIC_ERROR_VIBRATION_EFFECT, reason, - HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES); + COMMUNICATION_REQUEST_VIBRATION_ATTRIBUTES); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationSettingsRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationSettingsRepositoryModule.kt index af21e75da37e..d36412cf193e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationSettingsRepositoryModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationSettingsRepositoryModule.kt @@ -16,17 +16,23 @@ package com.android.systemui.statusbar.notification.data +import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.settings.SecureSettingsRepositoryModule import com.android.systemui.settings.SystemSettingsRepositoryModule import com.android.systemui.shared.notifications.data.repository.NotificationSettingsRepository import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository import com.android.systemui.shared.settings.data.repository.SystemSettingsRepository +import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionLogger import dagger.Module import dagger.Provides +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch @Module(includes = [SecureSettingsRepositoryModule::class, SystemSettingsRepositoryModule::class]) object NotificationSettingsRepositoryModule { @@ -42,6 +48,19 @@ object NotificationSettingsRepositoryModule { backgroundScope, backgroundDispatcher, secureSettingsRepository, - systemSettingsRepository - ) + systemSettingsRepository) + + @Provides + @IntoMap + @ClassKey(NotificationSettingsRepository::class) + @SysUISingleton + fun provideCoreStartable( + @Application applicationScope: CoroutineScope, + repository: NotificationSettingsRepository, + logger: VisualInterruptionDecisionLogger + ) = CoreStartable { + applicationScope.launch { + repository.isCooldownEnabled.collect { value -> logger.logCooldownSetting(value) } + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionLogger.kt index b83259dce298..37ac7c4330af 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionLogger.kt @@ -102,6 +102,15 @@ constructor(@NotificationInterruptLog val buffer: LogBuffer) { { "AvalancheSuppressor: $str1" } ) } + + fun logCooldownSetting(isEnabled: Boolean) { + buffer.log( + TAG, + INFO, + { bool1 = isEnabled }, + { "Cooldown enabled: $isEnabled" } + ) + } } private const val TAG = "VisualInterruptionDecisionProvider" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationViewInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationViewInflater.kt index 2c462b7329b4..77c4130482c8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationViewInflater.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationViewInflater.kt @@ -198,12 +198,22 @@ constructor( parentView, /* attachToRoot= */ false ) as EnRouteView - InflatedContentViewHolder(newView) { EnRouteViewBinder.bindWhileAttached(newView, createViewModel()) } } - RichOngoingNotificationViewType.Expanded, + RichOngoingNotificationViewType.Expanded -> { + val newView = + LayoutInflater.from(systemUiContext) + .inflate( + R.layout.notification_template_en_route_expanded, + parentView, + /* attachToRoot= */ false + ) as EnRouteView + InflatedContentViewHolder(newView) { + EnRouteViewBinder.bindWhileAttached(newView, createViewModel()) + } + } RichOngoingNotificationViewType.HeadsUp -> NullContentView } } 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 7c0178436268..8ff1ab640442 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 @@ -3691,6 +3691,10 @@ public class NotificationStackScrollLayout if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) { switch (event.getAction()) { case MotionEvent.ACTION_SCROLL: { + // If scene container is active, NSSL should not control its own scrolling. + if (SceneContainerFlag.isEnabled()) { + return false; + } if (!mIsBeingDragged) { final float vscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL); if (vscroll != 0) { 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 43f9af6016f1..dd4b0005b034 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -992,7 +992,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } else { showBouncerOrKeyguard(hideBouncerWhenShowing, isFalsingReset); } - if (!SceneContainerFlag.isEnabled() && hideBouncerWhenShowing) { + if (!SceneContainerFlag.isEnabled() && hideBouncerWhenShowing && isBouncerShowing()) { hideAlternateBouncer(true); mDismissCallbackRegistry.notifyDismissCancelled(); mPrimaryBouncerInteractor.setDismissAction(null, null); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt index 85bbe7e53493..d6013192f55e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt @@ -100,7 +100,14 @@ fun Intent.toNetworkNameModel(separator: String): NetworkNameModel? { val showSpn = getBooleanExtra(EXTRA_SHOW_SPN, false) val spn = if (statusBarSwitchToSpnFromDataSpn()) { - getStringExtra(EXTRA_SPN) + // Context: b/358669494. Use DATA_SPN if it exists, since that allows carriers to + // customize the display name. Otherwise, fall back to the SPN + val dataSpn = getStringExtra(EXTRA_DATA_SPN) + if (dataSpn.isNullOrEmpty()) { + getStringExtra(EXTRA_SPN) + } else { + dataSpn + } } else { getStringExtra(EXTRA_DATA_SPN) } @@ -109,10 +116,8 @@ fun Intent.toNetworkNameModel(separator: String): NetworkNameModel? { val plmn = getStringExtra(EXTRA_PLMN) val str = StringBuilder() - val strData = StringBuilder() if (showPlmn && plmn != null) { str.append(plmn) - strData.append(plmn) } if (showSpn && spn != null) { if (str.isNotEmpty()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt index 152d181bfc16..7163e67eaa5d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt @@ -101,29 +101,20 @@ constructor( private fun FakeWifiEventModel.Wifi.toWifiNetworkModel(): WifiNetworkModel = WifiNetworkModel.Active( - networkId = DEMO_NET_ID, isValidated = validated ?: true, level = level ?: 0, ssid = ssid ?: DEMO_NET_SSID, hotspotDeviceType = hotspotDeviceType, - - // These fields below aren't supported in demo mode, since they aren't needed to satisfy - // the interface. - isPasspointAccessPoint = false, - isOnlineSignUpForPasspointAccessPoint = false, - passpointProviderFriendlyName = null, ) private fun FakeWifiEventModel.CarrierMerged.toCarrierMergedModel(): WifiNetworkModel = WifiNetworkModel.CarrierMerged( - networkId = DEMO_NET_ID, subscriptionId = subscriptionId, level = level, numberOfLevels = numberOfLevels, ) companion object { - private const val DEMO_NET_ID = 1234 private const val DEMO_NET_SSID = "Demo SSID" } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt index 7df4b2cdb217..b6e73e0f4b9e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt @@ -250,7 +250,6 @@ constructor( WifiNetworkModel.Invalid(CARRIER_MERGED_INVALID_SUB_ID_REASON) } else { WifiNetworkModel.CarrierMerged( - networkId = NETWORK_ID, subscriptionId = this.subscriptionId, level = this.level, // WifiManager APIs to calculate the signal level start from 0, so @@ -261,7 +260,17 @@ constructor( } private fun WifiEntry.convertNormalToModel(): WifiNetworkModel { - if (this.level == WIFI_LEVEL_UNREACHABLE || this.level !in WIFI_LEVEL_MIN..WIFI_LEVEL_MAX) { + // WifiEntry instance values aren't guaranteed to be stable between method calls because + // WifiPickerTracker is continuously updating the same object. Save the level in a local + // variable so that checking the level validity here guarantees that the level will still be + // valid when we create the `WifiNetworkModel.Active` instance later. Otherwise, the level + // could be valid here but become invalid later, and `WifiNetworkModel.Active` will throw + // an exception. See b/362384551. + val currentLevel = this.level + if ( + currentLevel == WIFI_LEVEL_UNREACHABLE || + currentLevel !in WIFI_LEVEL_MIN..WIFI_LEVEL_MAX + ) { // If our level means the network is unreachable or the level is otherwise invalid, we // don't have an active network. return WifiNetworkModel.Inactive @@ -275,19 +284,10 @@ constructor( } return WifiNetworkModel.Active( - networkId = NETWORK_ID, isValidated = this.hasInternetAccess(), - level = this.level, + level = currentLevel, ssid = this.title, hotspotDeviceType = hotspotDeviceType, - // With WifiTrackerLib, [WifiEntry.title] will appropriately fetch the SSID for - // typical wifi networks *and* passpoint/OSU APs. So, the AP-specific values can - // always be false/null in this repository. - // TODO(b/292534484): Remove these fields from the wifi network model once this - // repository is fully enabled. - isPasspointAccessPoint = false, - isOnlineSignUpForPasspointAccessPoint = false, - passpointProviderFriendlyName = null, ) } @@ -428,19 +428,5 @@ constructor( val ACTIVITY_DEFAULT = DataActivityModel(hasActivityIn = false, hasActivityOut = false) private const val TAG = "WifiTrackerLibInputLog" - - /** - * [WifiNetworkModel.Active.networkId] is only used at the repository layer. It's used by - * [WifiRepositoryImpl], which tracks the ID in order to correctly apply the framework - * callbacks within the repository. - * - * Since this class does not need to manually apply framework callbacks and since the - * network ID is not used beyond the repository, it's safe to use an invalid ID in this - * repository. - * - * The [WifiNetworkModel.Active.networkId] field should be deleted once we've fully migrated - * to [WifiRepositoryImpl]. - */ - private const val NETWORK_ID = -1 } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt index 110e3390e722..c0b0c4acb51a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt @@ -83,8 +83,6 @@ constructor( is WifiNetworkModel.CarrierMerged -> null is WifiNetworkModel.Active -> when { - info.isPasspointAccessPoint || info.isOnlineSignUpForPasspointAccessPoint -> - info.passpointProviderFriendlyName info.hasValidSsid() -> info.ssid else -> null } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt index 7078a2e1728c..39842fb39e24 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt @@ -24,6 +24,7 @@ import com.android.systemui.log.table.Diffable import com.android.systemui.log.table.TableRowLogger import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository import com.android.wifitrackerlib.HotspotNetworkEntry.DeviceType +import com.android.wifitrackerlib.WifiEntry /** Provides information about the current wifi network. */ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { @@ -38,6 +39,7 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { */ object Unavailable : WifiNetworkModel() { override fun toString() = "WifiNetwork.Unavailable" + override fun logDiffs(prevVal: WifiNetworkModel, row: TableRowLogger) { if (prevVal is Unavailable) { return @@ -48,16 +50,12 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { override fun logFull(row: TableRowLogger) { row.logChange(COL_NETWORK_TYPE, TYPE_UNAVAILABLE) - row.logChange(COL_NETWORK_ID, NETWORK_ID_DEFAULT) row.logChange(COL_SUB_ID, SUB_ID_DEFAULT) row.logChange(COL_VALIDATED, false) row.logChange(COL_LEVEL, LEVEL_DEFAULT) row.logChange(COL_NUM_LEVELS, NUM_LEVELS_DEFAULT) row.logChange(COL_SSID, null) row.logChange(COL_HOTSPOT, null) - row.logChange(COL_PASSPOINT_ACCESS_POINT, false) - row.logChange(COL_ONLINE_SIGN_UP, false) - row.logChange(COL_PASSPOINT_NAME, null) } } @@ -67,6 +65,7 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { val invalidReason: String, ) : WifiNetworkModel() { override fun toString() = "WifiNetwork.Invalid[$invalidReason]" + override fun logDiffs(prevVal: WifiNetworkModel, row: TableRowLogger) { if (prevVal !is Invalid) { logFull(row) @@ -80,16 +79,12 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { override fun logFull(row: TableRowLogger) { row.logChange(COL_NETWORK_TYPE, "$TYPE_UNAVAILABLE $invalidReason") - row.logChange(COL_NETWORK_ID, NETWORK_ID_DEFAULT) row.logChange(COL_SUB_ID, SUB_ID_DEFAULT) row.logChange(COL_VALIDATED, false) row.logChange(COL_LEVEL, LEVEL_DEFAULT) row.logChange(COL_NUM_LEVELS, NUM_LEVELS_DEFAULT) row.logChange(COL_SSID, null) row.logChange(COL_HOTSPOT, null) - row.logChange(COL_PASSPOINT_ACCESS_POINT, false) - row.logChange(COL_ONLINE_SIGN_UP, false) - row.logChange(COL_PASSPOINT_NAME, null) } } @@ -108,16 +103,12 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { override fun logFull(row: TableRowLogger) { row.logChange(COL_NETWORK_TYPE, TYPE_INACTIVE) - row.logChange(COL_NETWORK_ID, NETWORK_ID_DEFAULT) row.logChange(COL_SUB_ID, SUB_ID_DEFAULT) row.logChange(COL_VALIDATED, false) row.logChange(COL_LEVEL, LEVEL_DEFAULT) row.logChange(COL_NUM_LEVELS, NUM_LEVELS_DEFAULT) row.logChange(COL_SSID, null) row.logChange(COL_HOTSPOT, null) - row.logChange(COL_PASSPOINT_ACCESS_POINT, false) - row.logChange(COL_ONLINE_SIGN_UP, false) - row.logChange(COL_PASSPOINT_NAME, null) } } @@ -129,14 +120,6 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { */ data class CarrierMerged( /** - * The [android.net.Network.netId] we received from - * [android.net.ConnectivityManager.NetworkCallback] in association with this wifi network. - * - * Importantly, **not** [android.net.wifi.WifiInfo.getNetworkId]. - */ - val networkId: Int, - - /** * The subscription ID that this connection represents. * * Comes from [android.net.wifi.WifiInfo.getSubscriptionId]. @@ -154,7 +137,8 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { ) : WifiNetworkModel() { init { require(level in MIN_VALID_LEVEL..numberOfLevels) { - "0 <= wifi level <= $numberOfLevels required; level was $level" + "CarrierMerged: $MIN_VALID_LEVEL <= wifi level <= $numberOfLevels required; " + + "level was $level" } require(subscriptionId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { "subscription ID cannot be invalid" @@ -167,9 +151,6 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { return } - if (prevVal.networkId != networkId) { - row.logChange(COL_NETWORK_ID, networkId) - } if (prevVal.subscriptionId != subscriptionId) { row.logChange(COL_SUB_ID, subscriptionId) } @@ -183,29 +164,17 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { override fun logFull(row: TableRowLogger) { row.logChange(COL_NETWORK_TYPE, TYPE_CARRIER_MERGED) - row.logChange(COL_NETWORK_ID, networkId) row.logChange(COL_SUB_ID, subscriptionId) row.logChange(COL_VALIDATED, true) row.logChange(COL_LEVEL, level) row.logChange(COL_NUM_LEVELS, numberOfLevels) row.logChange(COL_SSID, null) row.logChange(COL_HOTSPOT, null) - row.logChange(COL_PASSPOINT_ACCESS_POINT, false) - row.logChange(COL_ONLINE_SIGN_UP, false) - row.logChange(COL_PASSPOINT_NAME, null) } } /** Provides information about an active wifi network. */ data class Active( - /** - * The [android.net.Network.netId] we received from - * [android.net.ConnectivityManager.NetworkCallback] in association with this wifi network. - * - * Importantly, **not** [android.net.wifi.WifiInfo.getNetworkId]. - */ - val networkId: Int, - /** See [android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED]. */ val isValidated: Boolean = false, @@ -220,19 +189,11 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { * isn't a hotspot connection. */ val hotspotDeviceType: HotspotDeviceType = WifiNetworkModel.HotspotDeviceType.NONE, - - /** See [android.net.wifi.WifiInfo.isPasspointAp]. */ - val isPasspointAccessPoint: Boolean = false, - - /** See [android.net.wifi.WifiInfo.isOsuAp]. */ - val isOnlineSignUpForPasspointAccessPoint: Boolean = false, - - /** See [android.net.wifi.WifiInfo.passpointProviderFriendlyName]. */ - val passpointProviderFriendlyName: String? = null, ) : WifiNetworkModel() { init { require(level in MIN_VALID_LEVEL..MAX_VALID_LEVEL) { - "0 <= wifi level <= 4 required; level was $level" + "Active: $MIN_VALID_LEVEL <= wifi level <= $MAX_VALID_LEVEL required; " + + "level was $level" } } @@ -247,9 +208,6 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { return } - if (prevVal.networkId != networkId) { - row.logChange(COL_NETWORK_ID, networkId) - } if (prevVal.isValidated != isValidated) { row.logChange(COL_VALIDATED, isValidated) } @@ -262,68 +220,25 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { if (prevVal.hotspotDeviceType != hotspotDeviceType) { row.logChange(COL_HOTSPOT, hotspotDeviceType.name) } - - // TODO(b/238425913): The passpoint-related values are frequently never used, so it - // would be great to not log them when they're not used. - if (prevVal.isPasspointAccessPoint != isPasspointAccessPoint) { - row.logChange(COL_PASSPOINT_ACCESS_POINT, isPasspointAccessPoint) - } - if ( - prevVal.isOnlineSignUpForPasspointAccessPoint != - isOnlineSignUpForPasspointAccessPoint - ) { - row.logChange(COL_ONLINE_SIGN_UP, isOnlineSignUpForPasspointAccessPoint) - } - if (prevVal.passpointProviderFriendlyName != passpointProviderFriendlyName) { - row.logChange(COL_PASSPOINT_NAME, passpointProviderFriendlyName) - } } override fun logFull(row: TableRowLogger) { row.logChange(COL_NETWORK_TYPE, TYPE_ACTIVE) - row.logChange(COL_NETWORK_ID, networkId) row.logChange(COL_SUB_ID, null) row.logChange(COL_VALIDATED, isValidated) row.logChange(COL_LEVEL, level) row.logChange(COL_NUM_LEVELS, null) row.logChange(COL_SSID, ssid) row.logChange(COL_HOTSPOT, hotspotDeviceType.name) - row.logChange(COL_PASSPOINT_ACCESS_POINT, isPasspointAccessPoint) - row.logChange(COL_ONLINE_SIGN_UP, isOnlineSignUpForPasspointAccessPoint) - row.logChange(COL_PASSPOINT_NAME, passpointProviderFriendlyName) - } - - override fun toString(): String { - // Only include the passpoint-related values in the string if we have them. (Most - // networks won't have them so they'll be mostly clutter.) - val passpointString = - if ( - isPasspointAccessPoint || - isOnlineSignUpForPasspointAccessPoint || - passpointProviderFriendlyName != null - ) { - ", isPasspointAp=$isPasspointAccessPoint, " + - "isOnlineSignUpForPasspointAp=$isOnlineSignUpForPasspointAccessPoint, " + - "passpointName=$passpointProviderFriendlyName" - } else { - "" - } - - return "WifiNetworkModel.Active(networkId=$networkId, isValidated=$isValidated, " + - "level=$level, ssid=$ssid$passpointString)" } companion object { - // TODO(b/292534484): Use [com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MAX] instead - // once the migration to WifiTrackerLib is complete. - @VisibleForTesting internal const val MAX_VALID_LEVEL = 4 + @VisibleForTesting internal const val MAX_VALID_LEVEL = WifiEntry.WIFI_LEVEL_MAX } } companion object { - // TODO(b/292534484): Use [com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MIN] instead - // once the migration to WifiTrackerLib is complete. - @VisibleForTesting internal const val MIN_VALID_LEVEL = 0 + @VisibleForTesting internal const val MIN_VALID_LEVEL = WifiEntry.WIFI_LEVEL_MIN } /** @@ -367,18 +282,13 @@ const val TYPE_INACTIVE = "Inactive" const val TYPE_ACTIVE = "Active" const val COL_NETWORK_TYPE = "type" -const val COL_NETWORK_ID = "networkId" const val COL_SUB_ID = "subscriptionId" const val COL_VALIDATED = "isValidated" const val COL_LEVEL = "level" const val COL_NUM_LEVELS = "maxLevel" const val COL_SSID = "ssid" const val COL_HOTSPOT = "hotspot" -const val COL_PASSPOINT_ACCESS_POINT = "isPasspointAccessPoint" -const val COL_ONLINE_SIGN_UP = "isOnlineSignUpForPasspointAccessPoint" -const val COL_PASSPOINT_NAME = "passpointProviderFriendlyName" val LEVEL_DEFAULT: String? = null val NUM_LEVELS_DEFAULT: String? = null -val NETWORK_ID_DEFAULT: String? = null val SUB_ID_DEFAULT: String? = null diff --git a/packages/SystemUI/src/com/android/systemui/telephony/domain/interactor/TelephonyInteractor.kt b/packages/SystemUI/src/com/android/systemui/telephony/domain/interactor/TelephonyInteractor.kt index 4b0e5d188ffa..6d99183dec33 100644 --- a/packages/SystemUI/src/com/android/systemui/telephony/domain/interactor/TelephonyInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/telephony/domain/interactor/TelephonyInteractor.kt @@ -29,11 +29,12 @@ import kotlinx.coroutines.flow.StateFlow class TelephonyInteractor @Inject constructor( - repository: TelephonyRepository, + private val repository: TelephonyRepository, ) { @Annotation.CallState val callState: Flow<Int> = repository.callState val isInCall: StateFlow<Boolean> = repository.isInCall - val hasTelephonyRadio: Boolean = repository.hasTelephonyRadio + val hasTelephonyRadio: Boolean + get() = repository.hasTelephonyRadio } diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java index 8934d8f8a954..d9e72bf592a0 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java @@ -1305,13 +1305,12 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) { final int stream = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, STREAM_UNKNOWN); - final int level = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1); final int oldLevel = intent .getIntExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, -1); if (D.BUG) Log.d(TAG, "onReceive VOLUME_CHANGED_ACTION stream=" + stream - + " level=" + level + " oldLevel=" + oldLevel); + + " oldLevel=" + oldLevel); if (stream != STREAM_UNKNOWN) { - changed = updateStreamLevelW(stream, level); + changed |= onVolumeChangedW(stream, 0); } } else if (action.equals(AudioManager.STREAM_DEVICES_CHANGED_ACTION)) { final int stream = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java index a8ab922a90b9..d85b77413338 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java @@ -446,14 +446,10 @@ public class CarrierTextManagerTest extends SysuiTestCase { assertFalse(mWifiRepository.isWifiConnectedWithValidSsid()); mWifiRepository.setWifiNetwork( new WifiNetworkModel.Active( - /* networkId= */ 0, /* isValidated= */ false, /* level= */ 0, /* ssid= */ "", - /* hotspotDeviceType= */ WifiNetworkModel.HotspotDeviceType.NONE, - /* isPasspointAccessPoint= */ false, - /* isOnlineSignUpForPasspointAccessPoint= */ false, - /* passpointProviderFriendlyName= */ null)); + /* hotspotDeviceType= */ WifiNetworkModel.HotspotDeviceType.NONE)); assertTrue(mWifiRepository.isWifiConnectedWithValidSsid()); mKeyguardUpdateMonitor.mServiceStates = new HashMap<>(); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt index 43a780357027..c42e25b20e0d 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt @@ -29,7 +29,7 @@ import com.android.internal.logging.MetricsLogger import com.android.internal.widget.LockPatternUtils import com.android.systemui.Flags import com.android.systemui.SysuiTestCase -import com.android.systemui.haptics.msdl.FakeMSDLPlayer +import com.android.systemui.haptics.msdl.fakeMSDLPlayer import com.android.systemui.haptics.msdl.msdlPlayer import com.android.systemui.shade.ShadeController import com.android.systemui.statusbar.policy.ConfigurationController @@ -72,7 +72,7 @@ class EmergencyButtonControllerTest : SysuiTestCase() { val mainExecutor = FakeExecutor(fakeSystemClock) val backgroundExecutor = FakeExecutor(fakeSystemClock) private val kosmos = testKosmos() - private val msdlPlayer: FakeMSDLPlayer = kosmos.msdlPlayer + private val msdlPlayer = kosmos.fakeMSDLPlayer lateinit var underTest: EmergencyButtonController diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java index d3b7d2207854..662815ee7cbe 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java @@ -52,7 +52,6 @@ import android.widget.Spinner; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import com.android.internal.logging.UiEventLogger; import com.android.settingslib.bluetooth.BluetoothEventManager; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; @@ -92,6 +91,7 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase { @Rule public MockitoRule mockito = MockitoJUnit.rule(); + private static final int TEST_LAUNCH_SOURCE_ID = 1; private static final String DEVICE_ADDRESS = "AA:BB:CC:DD:EE:FF"; private static final String DEVICE_NAME = "test_name"; private static final String TEST_PKG = "pkg"; @@ -124,7 +124,7 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase { @Mock private AudioManager mAudioManager; @Mock - private UiEventLogger mUiEventLogger; + private HearingDevicesUiEventLogger mUiEventLogger; @Mock private CachedBluetoothDevice mCachedDevice; @Mock @@ -182,7 +182,8 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase { anyInt(), any()); assertThat(intentCaptor.getValue().getAction()).isEqualTo( Settings.ACTION_HEARING_DEVICE_PAIRING_SETTINGS); - verify(mUiEventLogger).log(HearingDevicesUiEvent.HEARING_DEVICES_PAIR); + verify(mUiEventLogger).log(HearingDevicesUiEvent.HEARING_DEVICES_PAIR, + TEST_LAUNCH_SOURCE_ID); } @Test @@ -196,7 +197,8 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase { anyInt(), any()); assertThat(intentCaptor.getValue().getAction()).isEqualTo( HearingDevicesDialogDelegate.ACTION_BLUETOOTH_DEVICE_DETAILS); - verify(mUiEventLogger).log(HearingDevicesUiEvent.HEARING_DEVICES_GEAR_CLICK); + verify(mUiEventLogger).log(HearingDevicesUiEvent.HEARING_DEVICES_GEAR_CLICK, + TEST_LAUNCH_SOURCE_ID); } @Test @@ -207,7 +209,8 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase { mDialogDelegate.onDeviceItemClicked(mHearingDeviceItem, new View(mContext)); verify(mCachedDevice).disconnect(); - verify(mUiEventLogger).log(HearingDevicesUiEvent.HEARING_DEVICES_DISCONNECT); + verify(mUiEventLogger).log(HearingDevicesUiEvent.HEARING_DEVICES_DISCONNECT, + TEST_LAUNCH_SOURCE_ID); } @Test @@ -304,6 +307,7 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase { mDialogDelegate = new HearingDevicesDialogDelegate( mContext, true, + TEST_LAUNCH_SOURCE_ID, mDialogFactory, mActivityStarter, mDialogTransitionAnimator, @@ -327,6 +331,7 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase { mDialogDelegate = new HearingDevicesDialogDelegate( mContext, false, + TEST_LAUNCH_SOURCE_ID, mDialogFactory, mActivityStarter, mDialogTransitionAnimator, diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt index 1e2369034bf7..7889b3cd6cc3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt @@ -61,10 +61,12 @@ import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel import com.android.systemui.display.data.repository.FakeDisplayRepository +import com.android.systemui.haptics.msdl.msdlPlayer import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.res.R import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.statusbar.events.ANIMATING_OUT +import com.android.systemui.testKosmos import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.time.FakeSystemClock @@ -145,6 +147,9 @@ open class AuthContainerViewTest : SysuiTestCase() { private val credentialViewModel = CredentialViewModel(mContext, bpCredentialInteractor) private val defaultLogoIcon = context.getDrawable(R.drawable.ic_android) + private val kosmos = testKosmos() + private val msdlPlayer = kosmos.msdlPlayer + private var authContainer: TestAuthContainerView? = null @Before @@ -668,7 +673,8 @@ open class AuthContainerViewTest : SysuiTestCase() { { credentialViewModel }, fakeExecutor, vibrator, - lazyViewCapture + lazyViewCapture, + msdlPlayer, ) { override fun postOnAnimation(runnable: Runnable) { runnable.run() diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt index 4fc41669b2c9..55fd3440ea07 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt @@ -37,12 +37,15 @@ import android.hardware.biometrics.PromptVerticalListContentView import android.hardware.face.FaceSensorPropertiesInternal import android.hardware.fingerprint.FingerprintSensorProperties import android.hardware.fingerprint.FingerprintSensorPropertiesInternal +import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.view.HapticFeedbackConstants import android.view.MotionEvent import android.view.Surface import androidx.test.filters.SmallTest import com.android.app.activityTaskManager +import com.android.keyguard.AuthInteractionProperties +import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.AuthController import com.android.systemui.biometrics.Utils.toBitmap @@ -72,6 +75,7 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.res.R import com.android.systemui.util.mockito.withArgCaptor +import com.google.android.msdl.data.model.MSDLToken import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.first @@ -124,6 +128,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa private val defaultLogoDescriptionFromActivityInfo = "Test Coke App" private val logoDescriptionFromApp = "Test Cake App" private val packageNameForLogoWithOverrides = "should.use.overridden.logo" + private val authInteractionProperties = AuthInteractionProperties() /** Prompt panel size padding */ private val smallHorizontalGuidelinePadding = @@ -707,31 +712,66 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } @Test + @DisableFlags(Flags.FLAG_MSDL_FEEDBACK) fun set_haptic_on_confirm_when_confirmation_required_otherwise_on_authenticated() = runGenericTest { val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false) kosmos.promptViewModel.showAuthenticated(testCase.authenticatedModality, 1_000L) - val confirmHaptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay) - assertThat(confirmHaptics?.hapticFeedbackConstant) - .isEqualTo( - if (expectConfirmation) HapticFeedbackConstants.NO_HAPTICS - else HapticFeedbackConstants.BIOMETRIC_CONFIRM - ) - assertThat(confirmHaptics?.flag).isNull() + val hapticsPreConfirm by collectLastValue(kosmos.promptViewModel.hapticsToPlay) + if (expectConfirmation) { + assertThat(hapticsPreConfirm).isEqualTo(PromptViewModel.HapticsToPlay.None) + } else { + val confirmHaptics = + hapticsPreConfirm as PromptViewModel.HapticsToPlay.HapticConstant + assertThat(confirmHaptics.constant) + .isEqualTo(HapticFeedbackConstants.BIOMETRIC_CONFIRM) + assertThat(confirmHaptics.flag).isNull() + } if (expectConfirmation) { kosmos.promptViewModel.confirmAuthenticated() } - val confirmedHaptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay) - assertThat(confirmedHaptics?.hapticFeedbackConstant) + val hapticsPostConfirm by collectLastValue(kosmos.promptViewModel.hapticsToPlay) + val confirmedHaptics = + hapticsPostConfirm as PromptViewModel.HapticsToPlay.HapticConstant + assertThat(confirmedHaptics.constant) .isEqualTo(HapticFeedbackConstants.BIOMETRIC_CONFIRM) - assertThat(confirmedHaptics?.flag).isNull() + assertThat(confirmedHaptics.flag).isNull() + } + + @Test + @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) + fun set_msdl_haptic_on_confirm_when_confirmation_required_otherwise_on_authenticated() = + runGenericTest { + val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false) + + kosmos.promptViewModel.showAuthenticated(testCase.authenticatedModality, 1_000L) + + val hapticsPreConfirm by collectLastValue(kosmos.promptViewModel.hapticsToPlay) + + if (expectConfirmation) { + assertThat(hapticsPreConfirm).isEqualTo(PromptViewModel.HapticsToPlay.None) + } else { + val confirmHaptics = hapticsPreConfirm as PromptViewModel.HapticsToPlay.MSDL + assertThat(confirmHaptics.token).isEqualTo(MSDLToken.UNLOCK) + assertThat(confirmHaptics.properties).isEqualTo(authInteractionProperties) + } + + if (expectConfirmation) { + kosmos.promptViewModel.confirmAuthenticated() + } + + val hapticsPostConfirm by collectLastValue(kosmos.promptViewModel.hapticsToPlay) + val confirmedHaptics = hapticsPostConfirm as PromptViewModel.HapticsToPlay.MSDL + assertThat(confirmedHaptics.token).isEqualTo(MSDLToken.UNLOCK) + assertThat(confirmedHaptics.properties).isEqualTo(authInteractionProperties) } @Test + @DisableFlags(Flags.FLAG_MSDL_FEEDBACK) fun playSuccessHaptic_SetsConfirmConstant() = runGenericTest { val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false) kosmos.promptViewModel.showAuthenticated(testCase.authenticatedModality, 1_000L) @@ -740,20 +780,48 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa kosmos.promptViewModel.confirmAuthenticated() } - val currentHaptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay) - assertThat(currentHaptics?.hapticFeedbackConstant) - .isEqualTo(HapticFeedbackConstants.BIOMETRIC_CONFIRM) - assertThat(currentHaptics?.flag).isNull() + val haptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay) + val currentHaptics = haptics as PromptViewModel.HapticsToPlay.HapticConstant + assertThat(currentHaptics.constant).isEqualTo(HapticFeedbackConstants.BIOMETRIC_CONFIRM) + assertThat(currentHaptics.flag).isNull() + } + + @Test + @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) + fun playSuccessHaptic_SetsUnlockMSDLFeedback() = runGenericTest { + val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false) + kosmos.promptViewModel.showAuthenticated(testCase.authenticatedModality, 1_000L) + + if (expectConfirmation) { + kosmos.promptViewModel.confirmAuthenticated() + } + + val haptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay) + val currentHaptics = haptics as PromptViewModel.HapticsToPlay.MSDL + assertThat(currentHaptics.token).isEqualTo(MSDLToken.UNLOCK) + assertThat(currentHaptics.properties).isEqualTo(authInteractionProperties) } @Test + @DisableFlags(Flags.FLAG_MSDL_FEEDBACK) fun playErrorHaptic_SetsRejectConstant() = runGenericTest { kosmos.promptViewModel.showTemporaryError("test", "messageAfterError", false) - val currentHaptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay) - assertThat(currentHaptics?.hapticFeedbackConstant) - .isEqualTo(HapticFeedbackConstants.BIOMETRIC_REJECT) - assertThat(currentHaptics?.flag).isNull() + val haptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay) + val currentHaptics = haptics as PromptViewModel.HapticsToPlay.HapticConstant + assertThat(currentHaptics.constant).isEqualTo(HapticFeedbackConstants.BIOMETRIC_REJECT) + assertThat(currentHaptics.flag).isNull() + } + + @Test + @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) + fun playErrorHaptic_SetsFailureMSDLFeedback() = runGenericTest { + kosmos.promptViewModel.showTemporaryError("test", "messageAfterError", false) + + val haptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay) + val currentHaptics = haptics as PromptViewModel.HapticsToPlay.MSDL + assertThat(currentHaptics.token).isEqualTo(MSDLToken.FAILURE) + assertThat(currentHaptics.properties).isEqualTo(authInteractionProperties) } // biometricprompt_sfps_fingerprint_authenticating reused across rotations @@ -855,6 +923,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } @Test + @DisableFlags(Flags.FLAG_MSDL_FEEDBACK) fun set_haptic_on_errors() = runGenericTest { kosmos.promptViewModel.showTemporaryError( "so sad", @@ -863,13 +932,30 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa hapticFeedback = true, ) - val haptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay) - assertThat(haptics?.hapticFeedbackConstant) - .isEqualTo(HapticFeedbackConstants.BIOMETRIC_REJECT) - assertThat(haptics?.flag).isNull() + val hapticsToPlay by collectLastValue(kosmos.promptViewModel.hapticsToPlay) + val haptics = hapticsToPlay as PromptViewModel.HapticsToPlay.HapticConstant + assertThat(haptics.constant).isEqualTo(HapticFeedbackConstants.BIOMETRIC_REJECT) + assertThat(haptics.flag).isNull() + } + + @Test + @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) + fun set_msdl_haptic_on_errors() = runGenericTest { + kosmos.promptViewModel.showTemporaryError( + "so sad", + messageAfterError = "", + authenticateAfterError = false, + hapticFeedback = true, + ) + + val hapticsToPlay by collectLastValue(kosmos.promptViewModel.hapticsToPlay) + val haptics = hapticsToPlay as PromptViewModel.HapticsToPlay.MSDL + assertThat(haptics.token).isEqualTo(MSDLToken.FAILURE) + assertThat(haptics.properties).isEqualTo(authInteractionProperties) } @Test + @DisableFlags(Flags.FLAG_MSDL_FEEDBACK) fun plays_haptic_on_errors_unless_skipped() = runGenericTest { kosmos.promptViewModel.showTemporaryError( "still sad", @@ -878,11 +964,26 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa hapticFeedback = false, ) - val haptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay) - assertThat(haptics?.hapticFeedbackConstant).isEqualTo(HapticFeedbackConstants.NO_HAPTICS) + val hapticsToPlay by collectLastValue(kosmos.promptViewModel.hapticsToPlay) + assertThat(hapticsToPlay).isEqualTo(PromptViewModel.HapticsToPlay.None) + } + + @Test + @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) + fun plays_msdl_haptic_on_errors_unless_skipped() = runGenericTest { + kosmos.promptViewModel.showTemporaryError( + "still sad", + messageAfterError = "", + authenticateAfterError = false, + hapticFeedback = false, + ) + + val hapticsToPlay by collectLastValue(kosmos.promptViewModel.hapticsToPlay) + assertThat(hapticsToPlay).isEqualTo(PromptViewModel.HapticsToPlay.None) } @Test + @DisableFlags(Flags.FLAG_MSDL_FEEDBACK) fun plays_haptic_on_error_after_auth_when_confirmation_needed() = runGenericTest { val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false) kosmos.promptViewModel.showAuthenticated(testCase.authenticatedModality, 0) @@ -894,15 +995,37 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa hapticFeedback = true, ) - val haptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay) + val hapticsToPlay by collectLastValue(kosmos.promptViewModel.hapticsToPlay) + val haptics = hapticsToPlay as PromptViewModel.HapticsToPlay.HapticConstant if (expectConfirmation) { - assertThat(haptics?.hapticFeedbackConstant) - .isEqualTo(HapticFeedbackConstants.BIOMETRIC_REJECT) - assertThat(haptics?.flag).isNull() + assertThat(haptics.constant).isEqualTo(HapticFeedbackConstants.BIOMETRIC_REJECT) + assertThat(haptics.flag).isNull() } else { - assertThat(haptics?.hapticFeedbackConstant) - .isEqualTo(HapticFeedbackConstants.BIOMETRIC_CONFIRM) + assertThat(haptics.constant).isEqualTo(HapticFeedbackConstants.BIOMETRIC_CONFIRM) + } + } + + @Test + @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) + fun plays_msdl_haptic_on_error_after_auth_when_confirmation_needed() = runGenericTest { + val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false) + kosmos.promptViewModel.showAuthenticated(testCase.authenticatedModality, 0) + + kosmos.promptViewModel.showTemporaryError( + "still sad", + messageAfterError = "", + authenticateAfterError = false, + hapticFeedback = true, + ) + + val hapticsToPlay by collectLastValue(kosmos.promptViewModel.hapticsToPlay) + val haptics = hapticsToPlay as PromptViewModel.HapticsToPlay.MSDL + if (expectConfirmation) { + assertThat(haptics.token).isEqualTo(MSDLToken.FAILURE) + } else { + assertThat(haptics.token).isEqualTo(MSDLToken.UNLOCK) } + assertThat(haptics.properties).isEqualTo(authInteractionProperties) } private suspend fun TestScope.showTemporaryErrors( diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java index a1bea0620528..637771790b28 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java @@ -104,6 +104,7 @@ public class BrightLineClassifierTest extends SysuiTestCase { when(mFalsingDataProvider.getRecentMotionEvents()).thenReturn(mMotionEventList); when(mKeyguardStateController.isShowing()).thenReturn(true); when(mFalsingDataProvider.isUnfolded()).thenReturn(false); + when(mFalsingDataProvider.isTouchScreenSource()).thenReturn(true); mBrightLineFalsingManager = new BrightLineFalsingManager(mFalsingDataProvider, mMetricsLogger, mClassifiers, mSingleTapClassfier, mLongTapClassifier, mDoubleTapClassifier, mHistoryTracker, mKeyguardStateController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt index 7cc91853a749..bfb8a57e6271 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt @@ -34,6 +34,7 @@ import com.android.systemui.flags.Flags import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryBackgroundViewModel import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryForegroundViewModel import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel +import com.android.systemui.log.logcatLogBuffer import com.android.systemui.plugins.FalsingManager import com.android.systemui.res.R import com.android.systemui.shade.NotificationPanelView @@ -86,6 +87,7 @@ class DefaultDeviceEntrySectionTest : SysuiTestCase() { { mock(DeviceEntryBackgroundViewModel::class.java) }, { falsingManager }, { mock(VibratorHelper::class.java) }, + logcatLogBuffer(), ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt index ad7a5b62b912..3c743744dd58 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt @@ -967,7 +967,8 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa assertThat(data.resumption).isTrue() assertThat(data.song).isEqualTo(SESSION_TITLE) assertThat(data.app).isEqualTo(APP_NAME) - assertThat(data.actions).hasSize(1) + // resume button is a semantic action. + assertThat(data.actions).hasSize(0) assertThat(data.semanticActions!!.playOrPause).isNotNull() assertThat(data.lastActive).isAtLeast(currentTime) verify(logger).logResumeMediaAdded(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId)) @@ -994,7 +995,8 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa assertThat(data.resumption).isTrue() assertThat(data.song).isEqualTo(SESSION_TITLE) assertThat(data.app).isEqualTo(APP_NAME) - assertThat(data.actions).hasSize(1) + // resume button is a semantic action. + assertThat(data.actions).hasSize(0) assertThat(data.semanticActions!!.playOrPause).isNotNull() assertThat(data.lastActive).isAtLeast(currentTime) assertThat(data.isExplicit).isTrue() 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 c0f503d7f1cb..4cf7de3d7a63 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 @@ -69,6 +69,8 @@ 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.domain.resume.MediaResumeListener import com.android.systemui.media.controls.domain.resume.ResumeMediaBrowser +import com.android.systemui.media.controls.shared.mediaLogger +import com.android.systemui.media.controls.shared.mockMediaLogger 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.MediaData @@ -140,7 +142,7 @@ private fun <T> anyObject(): T { @RunWith(ParameterizedAndroidJunit4::class) @EnableSceneContainer class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { - private val kosmos = testKosmos() + private val kosmos = testKosmos().apply { mediaLogger = mockMediaLogger } private val testDispatcher = kosmos.testDispatcher private val testScope = kosmos.testScope private val settings = kosmos.fakeSettings @@ -257,6 +259,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { keyguardUpdateMonitor = keyguardUpdateMonitor, mediaDataRepository = kosmos.mediaDataRepository, mediaDataLoader = { kosmos.mediaDataLoader }, + mediaLogger = kosmos.mediaLogger, ) mediaDataProcessor.start() testScope.runCurrent() @@ -984,7 +987,8 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { assertThat(data.resumption).isTrue() assertThat(data.song).isEqualTo(SESSION_TITLE) assertThat(data.app).isEqualTo(APP_NAME) - assertThat(data.actions).hasSize(1) + // resume button is a semantic action. + assertThat(data.actions).hasSize(0) assertThat(data.semanticActions!!.playOrPause).isNotNull() assertThat(data.lastActive).isAtLeast(currentTime) verify(logger).logResumeMediaAdded(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId)) @@ -1011,7 +1015,8 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { assertThat(data.resumption).isTrue() assertThat(data.song).isEqualTo(SESSION_TITLE) assertThat(data.app).isEqualTo(APP_NAME) - assertThat(data.actions).hasSize(1) + // resume button is a semantic action. + assertThat(data.actions).hasSize(0) assertThat(data.semanticActions!!.playOrPause).isNotNull() assertThat(data.lastActive).isAtLeast(currentTime) assertThat(data.isExplicit).isTrue() @@ -2476,6 +2481,55 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { assertThat(mediaDataCaptor.value.artwork).isNull() } + @Test + @EnableFlags(Flags.FLAG_MEDIA_CONTROLS_POSTS_OPTIMIZATION) + fun postDuplicateNotification_doesNotCallListeners() { + whenever(notificationLockscreenUserManager.isCurrentProfile(USER_ID)).thenReturn(true) + whenever(notificationLockscreenUserManager.isProfileAvailable(USER_ID)).thenReturn(true) + + mediaDataProcessor.addInternalListener(mediaDataFilter) + mediaDataFilter.mediaDataProcessor = mediaDataProcessor + addNotificationAndLoad() + reset(listener) + mediaDataProcessor.onNotificationAdded(KEY, mediaNotification) + + testScope.assertRunAllReady(foreground = 0, background = 1) + verify(listener, never()) + .onMediaDataLoaded( + eq(KEY), + eq(KEY), + capture(mediaDataCaptor), + eq(true), + eq(0), + eq(false) + ) + verify(kosmos.mediaLogger).logDuplicateMediaNotification(eq(KEY)) + } + + @Test + @DisableFlags(Flags.FLAG_MEDIA_CONTROLS_POSTS_OPTIMIZATION) + fun postDuplicateNotification_callsListeners() { + whenever(notificationLockscreenUserManager.isCurrentProfile(USER_ID)).thenReturn(true) + whenever(notificationLockscreenUserManager.isProfileAvailable(USER_ID)).thenReturn(true) + + mediaDataProcessor.addInternalListener(mediaDataFilter) + mediaDataFilter.mediaDataProcessor = mediaDataProcessor + addNotificationAndLoad() + reset(listener) + mediaDataProcessor.onNotificationAdded(KEY, mediaNotification) + testScope.assertRunAllReady(foreground = 1, background = 1) + verify(listener) + .onMediaDataLoaded( + eq(KEY), + eq(KEY), + capture(mediaDataCaptor), + eq(true), + eq(0), + eq(false) + ) + verify(kosmos.mediaLogger, never()).logDuplicateMediaNotification(eq(KEY)) + } + private fun TestScope.assertRunAllReady(foreground: Int = 0, background: Int = 0) { runCurrent() if (Flags.mediaLoadMetadataViaMediaDataLoader()) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt index c1bba4d4d60c..680df1584f89 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt @@ -72,7 +72,6 @@ class MediaTimeoutListenerTest : SysuiTestCase() { @Mock private lateinit var mediaController: MediaController @Mock private lateinit var logger: MediaTimeoutLogger @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController - private lateinit var executor: FakeExecutor @Mock private lateinit var timeoutCallback: (String, Boolean) -> Unit @Mock private lateinit var stateCallback: (String, PlaybackState) -> Unit @Mock private lateinit var sessionCallback: (String) -> Unit @@ -88,6 +87,9 @@ class MediaTimeoutListenerTest : SysuiTestCase() { private lateinit var resumeData: MediaData private lateinit var mediaTimeoutListener: MediaTimeoutListener private var clock = FakeSystemClock() + private lateinit var mainExecutor: FakeExecutor + private lateinit var bgExecutor: FakeExecutor + private lateinit var uiExecutor: FakeExecutor @Mock private lateinit var mediaFlags: MediaFlags @Mock private lateinit var smartspaceData: SmartspaceMediaData @@ -95,11 +97,15 @@ class MediaTimeoutListenerTest : SysuiTestCase() { fun setup() { whenever(mediaControllerFactory.create(any())).thenReturn(mediaController) whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(false) - executor = FakeExecutor(clock) + mainExecutor = FakeExecutor(clock) + bgExecutor = FakeExecutor(clock) + uiExecutor = FakeExecutor(clock) mediaTimeoutListener = MediaTimeoutListener( mediaControllerFactory, - executor, + bgExecutor, + uiExecutor, + mainExecutor, logger, statusBarStateController, clock, @@ -143,30 +149,31 @@ class MediaTimeoutListenerTest : SysuiTestCase() { whenever(playingState.state).thenReturn(PlaybackState.STATE_PLAYING) whenever(mediaController.playbackState).thenReturn(playingState) - mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData) + loadMediaData(KEY, null, mediaData) verify(mediaController).registerCallback(capture(mediaCallbackCaptor)) verify(logger).logPlaybackState(eq(KEY), eq(playingState)) // Ignores if same key clearInvocations(mediaController) - mediaTimeoutListener.onMediaDataLoaded(KEY, KEY, mediaData) + loadMediaData(KEY, KEY, mediaData) verify(mediaController, never()).registerCallback(anyObject()) } @Test fun testOnMediaDataLoaded_registersTimeout_whenPaused() { - mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData) + loadMediaData(KEY, null, mediaData) verify(mediaController).registerCallback(capture(mediaCallbackCaptor)) - assertThat(executor.numPending()).isEqualTo(1) + assertThat(mainExecutor.numPending()).isEqualTo(1) verify(timeoutCallback, never()).invoke(anyString(), anyBoolean()) verify(logger).logScheduleTimeout(eq(KEY), eq(false), eq(false)) - assertThat(executor.advanceClockToNext()).isEqualTo(PAUSED_MEDIA_TIMEOUT) + assertThat(mainExecutor.advanceClockToNext()).isEqualTo(PAUSED_MEDIA_TIMEOUT) } @Test fun testOnMediaDataRemoved_unregistersPlaybackListener() { - mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData) + loadMediaData(KEY, null, mediaData) mediaTimeoutListener.onMediaDataRemoved(KEY, false) + assertThat(bgExecutor.runAllReady()).isEqualTo(1) verify(mediaController).unregisterCallback(anyObject()) // Ignores duplicate requests @@ -178,50 +185,50 @@ class MediaTimeoutListenerTest : SysuiTestCase() { @Test fun testOnMediaDataRemoved_clearsTimeout() { // GIVEN media that is paused - mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData) - assertThat(executor.numPending()).isEqualTo(1) + loadMediaData(KEY, null, mediaData) + assertThat(mainExecutor.numPending()).isEqualTo(1) // WHEN the media is removed mediaTimeoutListener.onMediaDataRemoved(KEY, false) // THEN the timeout runnable is cancelled - assertThat(executor.numPending()).isEqualTo(0) + assertThat(mainExecutor.numPending()).isEqualTo(0) } @Test fun testOnMediaDataLoaded_migratesKeys() { val newKey = "NEWKEY" // From not playing - mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData) + loadMediaData(KEY, null, mediaData) clearInvocations(mediaController) // To playing val playingState = mock(android.media.session.PlaybackState::class.java) whenever(playingState.state).thenReturn(PlaybackState.STATE_PLAYING) whenever(mediaController.playbackState).thenReturn(playingState) - mediaTimeoutListener.onMediaDataLoaded(newKey, KEY, mediaData) + loadMediaData(newKey, KEY, mediaData) verify(mediaController).unregisterCallback(anyObject()) verify(mediaController).registerCallback(anyObject()) verify(logger).logMigrateListener(eq(KEY), eq(newKey), eq(true)) // Enqueues callback - assertThat(executor.numPending()).isEqualTo(1) + assertThat(mainExecutor.numPending()).isEqualTo(1) } @Test fun testOnMediaDataLoaded_migratesKeys_noTimeoutExtension() { val newKey = "NEWKEY" // From not playing - mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData) + loadMediaData(KEY, null, mediaData) clearInvocations(mediaController) // Migrate, still not playing val playingState = mock(android.media.session.PlaybackState::class.java) whenever(playingState.state).thenReturn(PlaybackState.STATE_PAUSED) whenever(mediaController.playbackState).thenReturn(playingState) - mediaTimeoutListener.onMediaDataLoaded(newKey, KEY, mediaData) + loadMediaData(newKey, KEY, mediaData) // The number of queued timeout tasks remains the same. The timeout task isn't cancelled nor // is another scheduled - assertThat(executor.numPending()).isEqualTo(1) + assertThat(mainExecutor.numPending()).isEqualTo(1) verify(logger).logUpdateListener(eq(newKey), eq(false)) } @@ -233,8 +240,8 @@ class MediaTimeoutListenerTest : SysuiTestCase() { mediaCallbackCaptor.value.onPlaybackStateChanged( PlaybackState.Builder().setState(PlaybackState.STATE_PAUSED, 0L, 0f).build() ) - assertThat(executor.numPending()).isEqualTo(1) - assertThat(executor.advanceClockToNext()).isEqualTo(PAUSED_MEDIA_TIMEOUT) + assertThat(mainExecutor.numPending()).isEqualTo(1) + assertThat(mainExecutor.advanceClockToNext()).isEqualTo(PAUSED_MEDIA_TIMEOUT) } @Test @@ -245,7 +252,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() { mediaCallbackCaptor.value.onPlaybackStateChanged( PlaybackState.Builder().setState(PlaybackState.STATE_PLAYING, 0L, 0f).build() ) - assertThat(executor.numPending()).isEqualTo(0) + assertThat(mainExecutor.numPending()).isEqualTo(0) verify(logger).logTimeoutCancelled(eq(KEY), any()) } @@ -257,7 +264,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() { mediaCallbackCaptor.value.onPlaybackStateChanged( PlaybackState.Builder().setState(PlaybackState.STATE_STOPPED, 0L, 0f).build() ) - assertThat(executor.numPending()).isEqualTo(1) + assertThat(mainExecutor.numPending()).isEqualTo(1) } @Test @@ -265,7 +272,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() { // Assuming we're have a pending timeout testOnPlaybackStateChanged_schedulesTimeout_whenPaused() - with(executor) { + with(mainExecutor) { advanceClockToNext() runAllReady() } @@ -274,7 +281,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() { @Test fun testIsTimedOut() { - mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData) + loadMediaData(KEY, null, mediaData) assertThat(mediaTimeoutListener.isTimedOut(KEY)).isFalse() } @@ -282,16 +289,17 @@ class MediaTimeoutListenerTest : SysuiTestCase() { fun testOnSessionDestroyed_active_clearsTimeout() { // GIVEN media that is paused val mediaPaused = mediaData.copy(isPlaying = false) - mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaPaused) + loadMediaData(KEY, null, mediaPaused) verify(mediaController).registerCallback(capture(mediaCallbackCaptor)) - assertThat(executor.numPending()).isEqualTo(1) + assertThat(mainExecutor.numPending()).isEqualTo(1) // WHEN the session is destroyed mediaCallbackCaptor.value.onSessionDestroyed() // THEN the controller is unregistered and timeout run + assertThat(bgExecutor.runAllReady()).isEqualTo(1) verify(mediaController).unregisterCallback(anyObject()) - assertThat(executor.numPending()).isEqualTo(0) + assertThat(mainExecutor.numPending()).isEqualTo(0) verify(logger).logSessionDestroyed(eq(KEY)) verify(sessionCallback).invoke(eq(KEY)) } @@ -306,11 +314,11 @@ class MediaTimeoutListenerTest : SysuiTestCase() { whenever(playingState.state).thenReturn(PlaybackState.STATE_PLAYING) whenever(mediaController.playbackState).thenReturn(playingState) val mediaPlaying = mediaData.copy(isPlaying = true) - mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaPlaying) + loadMediaData(KEY, null, mediaPlaying) // THEN the timeout runnable will update the state - assertThat(executor.numPending()).isEqualTo(1) - with(executor) { + assertThat(mainExecutor.numPending()).isEqualTo(1) + with(mainExecutor) { advanceClockToNext() runAllReady() } @@ -322,31 +330,32 @@ class MediaTimeoutListenerTest : SysuiTestCase() { fun testOnSessionDestroyed_resume_continuesTimeout() { // GIVEN resume media with session info val resumeWithSession = resumeData.copy(token = session.sessionToken) - mediaTimeoutListener.onMediaDataLoaded(PACKAGE, null, resumeWithSession) + loadMediaData(PACKAGE, null, resumeWithSession) verify(mediaController).registerCallback(capture(mediaCallbackCaptor)) - assertThat(executor.numPending()).isEqualTo(1) + assertThat(mainExecutor.numPending()).isEqualTo(1) // WHEN the session is destroyed mediaCallbackCaptor.value.onSessionDestroyed() // THEN the controller is unregistered, but the timeout is still scheduled + assertThat(bgExecutor.runAllReady()).isEqualTo(1) verify(mediaController).unregisterCallback(anyObject()) - assertThat(executor.numPending()).isEqualTo(1) + assertThat(mainExecutor.numPending()).isEqualTo(1) verify(sessionCallback, never()).invoke(eq(KEY)) } @Test fun testOnMediaDataLoaded_activeToResume_registersTimeout() { // WHEN a regular media is loaded - mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData) + loadMediaData(KEY, null, mediaData) // AND it turns into a resume control - mediaTimeoutListener.onMediaDataLoaded(PACKAGE, KEY, resumeData) + loadMediaData(PACKAGE, KEY, resumeData) // THEN we register a timeout - assertThat(executor.numPending()).isEqualTo(1) + assertThat(mainExecutor.numPending()).isEqualTo(1) verify(timeoutCallback, never()).invoke(anyString(), anyBoolean()) - assertThat(executor.advanceClockToNext()).isEqualTo(RESUME_MEDIA_TIMEOUT) + assertThat(mainExecutor.advanceClockToNext()).isEqualTo(RESUME_MEDIA_TIMEOUT) } @Test @@ -355,42 +364,42 @@ class MediaTimeoutListenerTest : SysuiTestCase() { val pausedState = PlaybackState.Builder().setState(PlaybackState.STATE_PAUSED, 0L, 0f).build() whenever(mediaController.playbackState).thenReturn(pausedState) - mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData) - assertThat(executor.numPending()).isEqualTo(1) + loadMediaData(KEY, null, mediaData) + assertThat(mainExecutor.numPending()).isEqualTo(1) // AND it turns into a resume control - mediaTimeoutListener.onMediaDataLoaded(PACKAGE, KEY, resumeData) + loadMediaData(PACKAGE, KEY, resumeData) // THEN we update the timeout length - assertThat(executor.numPending()).isEqualTo(1) + assertThat(mainExecutor.numPending()).isEqualTo(1) verify(timeoutCallback, never()).invoke(anyString(), anyBoolean()) - assertThat(executor.advanceClockToNext()).isEqualTo(RESUME_MEDIA_TIMEOUT) + assertThat(mainExecutor.advanceClockToNext()).isEqualTo(RESUME_MEDIA_TIMEOUT) } @Test fun testOnMediaDataLoaded_resumption_registersTimeout() { // WHEN a resume media is loaded - mediaTimeoutListener.onMediaDataLoaded(PACKAGE, null, resumeData) + loadMediaData(PACKAGE, null, resumeData) // THEN we register a timeout - assertThat(executor.numPending()).isEqualTo(1) + assertThat(mainExecutor.numPending()).isEqualTo(1) verify(timeoutCallback, never()).invoke(anyString(), anyBoolean()) - assertThat(executor.advanceClockToNext()).isEqualTo(RESUME_MEDIA_TIMEOUT) + assertThat(mainExecutor.advanceClockToNext()).isEqualTo(RESUME_MEDIA_TIMEOUT) } @Test fun testOnMediaDataLoaded_resumeToActive_updatesTimeout() { // WHEN we have a resume control - mediaTimeoutListener.onMediaDataLoaded(PACKAGE, null, resumeData) + loadMediaData(PACKAGE, null, resumeData) // AND that media is resumed val playingState = PlaybackState.Builder().setState(PlaybackState.STATE_PAUSED, 0L, 0f).build() whenever(mediaController.playbackState).thenReturn(playingState) - mediaTimeoutListener.onMediaDataLoaded(KEY, PACKAGE, mediaData) + loadMediaData(oldKey = PACKAGE, data = mediaData) // THEN the timeout length is changed to a regular media control - assertThat(executor.advanceClockToNext()).isEqualTo(PAUSED_MEDIA_TIMEOUT) + assertThat(mainExecutor.advanceClockToNext()).isEqualTo(PAUSED_MEDIA_TIMEOUT) } @Test @@ -401,7 +410,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() { mediaTimeoutListener.onMediaDataRemoved(PACKAGE, false) // THEN the timeout runnable is cancelled - assertThat(executor.numPending()).isEqualTo(0) + assertThat(mainExecutor.numPending()).isEqualTo(0) } @Test @@ -427,6 +436,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() { // When the playback state changes, and has different actions val playingState = PlaybackState.Builder().setActions(PlaybackState.ACTION_PLAY).build() mediaCallbackCaptor.value.onPlaybackStateChanged(playingState) + assertThat(uiExecutor.runAllReady()).isEqualTo(1) // Then the callback is invoked verify(stateCallback).invoke(eq(KEY), eq(playingState!!)) @@ -463,6 +473,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() { .addCustomAction(customTwo) .build() mediaCallbackCaptor.value.onPlaybackStateChanged(pausedStateTwoActions) + assertThat(uiExecutor.runAllReady()).isEqualTo(1) // Then the callback is invoked verify(stateCallback).invoke(eq(KEY), eq(pausedStateTwoActions!!)) @@ -534,6 +545,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() { val playingState = PlaybackState.Builder().setState(PlaybackState.STATE_PLAYING, 0L, 1f).build() mediaCallbackCaptor.value.onPlaybackStateChanged(playingState) + uiExecutor.runAllReady() // Then the callback is invoked verify(stateCallback).invoke(eq(KEY), eq(playingState!!)) @@ -567,7 +579,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() { // And we doze past the scheduled timeout val time = clock.currentTimeMillis() clock.setElapsedRealtime(time + PAUSED_MEDIA_TIMEOUT) - assertThat(executor.numPending()).isEqualTo(1) + assertThat(mainExecutor.numPending()).isEqualTo(1) // Then when no longer dozing, the timeout runs immediately dozingCallbackCaptor.value.onDozingChanged(false) @@ -576,7 +588,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() { // and cancel any later scheduled timeout verify(logger).logTimeoutCancelled(eq(KEY), any()) - assertThat(executor.numPending()).isEqualTo(0) + assertThat(mainExecutor.numPending()).isEqualTo(0) } @Test @@ -592,12 +604,12 @@ class MediaTimeoutListenerTest : SysuiTestCase() { // And we doze, but not past the scheduled timeout clock.setElapsedRealtime(time + PAUSED_MEDIA_TIMEOUT / 2L) - assertThat(executor.numPending()).isEqualTo(1) + assertThat(mainExecutor.numPending()).isEqualTo(1) // Then when no longer dozing, the timeout remains scheduled dozingCallbackCaptor.value.onDozingChanged(false) verify(timeoutCallback, never()).invoke(eq(KEY), eq(true)) - assertThat(executor.numPending()).isEqualTo(1) + assertThat(mainExecutor.numPending()).isEqualTo(1) } @Test @@ -610,8 +622,8 @@ class MediaTimeoutListenerTest : SysuiTestCase() { whenever(smartspaceData.expiryTimeMs).thenReturn(expireTime) mediaTimeoutListener.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) - assertThat(executor.numPending()).isEqualTo(1) - assertThat(executor.advanceClockToNext()).isEqualTo(duration) + assertThat(mainExecutor.numPending()).isEqualTo(1) + assertThat(mainExecutor.advanceClockToNext()).isEqualTo(duration) } @Test @@ -619,7 +631,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() { // Given a pending timeout testSmartspaceDataLoaded_schedulesTimeout() - executor.runAllReady() + mainExecutor.runAllReady() verify(timeoutCallback).invoke(eq(SMARTSPACE_KEY), eq(true)) } @@ -634,14 +646,14 @@ class MediaTimeoutListenerTest : SysuiTestCase() { whenever(smartspaceData.expiryTimeMs).thenReturn(expireTime) mediaTimeoutListener.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) - assertThat(executor.numPending()).isEqualTo(1) + assertThat(mainExecutor.numPending()).isEqualTo(1) val expiryLonger = expireTime + duration whenever(smartspaceData.expiryTimeMs).thenReturn(expiryLonger) mediaTimeoutListener.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) - assertThat(executor.numPending()).isEqualTo(1) - assertThat(executor.advanceClockToNext()).isEqualTo(duration * 2) + assertThat(mainExecutor.numPending()).isEqualTo(1) + assertThat(mainExecutor.advanceClockToNext()).isEqualTo(duration * 2) } @Test @@ -649,10 +661,10 @@ class MediaTimeoutListenerTest : SysuiTestCase() { whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true) mediaTimeoutListener.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) - assertThat(executor.numPending()).isEqualTo(1) + assertThat(mainExecutor.numPending()).isEqualTo(1) mediaTimeoutListener.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY) - assertThat(executor.numPending()).isEqualTo(0) + assertThat(mainExecutor.numPending()).isEqualTo(0) } @Test @@ -667,12 +679,12 @@ class MediaTimeoutListenerTest : SysuiTestCase() { whenever(smartspaceData.expiryTimeMs).thenReturn(expireTime) mediaTimeoutListener.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) - assertThat(executor.numPending()).isEqualTo(1) + assertThat(mainExecutor.numPending()).isEqualTo(1) // And we doze past the scheduled timeout val time = clock.currentTimeMillis() clock.setElapsedRealtime(time + duration * 2) - assertThat(executor.numPending()).isEqualTo(1) + assertThat(mainExecutor.numPending()).isEqualTo(1) // Then when no longer dozing, the timeout runs immediately dozingCallbackCaptor.value.onDozingChanged(false) @@ -680,12 +692,18 @@ class MediaTimeoutListenerTest : SysuiTestCase() { verify(logger).logTimeout(eq(SMARTSPACE_KEY)) // and cancel any later scheduled timeout - assertThat(executor.numPending()).isEqualTo(0) + assertThat(mainExecutor.numPending()).isEqualTo(0) } private fun loadMediaDataWithPlaybackState(state: PlaybackState) { whenever(mediaController.playbackState).thenReturn(state) - mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData) + loadMediaData(data = mediaData) verify(mediaController).registerCallback(capture(mediaCallbackCaptor)) } + + private fun loadMediaData(key: String = KEY, oldKey: String? = null, data: MediaData) { + mediaTimeoutListener.onMediaDataLoaded(key, oldKey, data) + bgExecutor.runAllReady() + uiExecutor.runAllReady() + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt index 1260a65b9c1c..68a5d9361046 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt @@ -79,6 +79,7 @@ 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.MediaData import com.android.systemui.media.controls.shared.model.MediaDeviceData +import com.android.systemui.media.controls.shared.model.MediaNotificationAction import com.android.systemui.media.controls.shared.model.SmartspaceMediaData import com.android.systemui.media.controls.ui.binder.SeekBarObserver import com.android.systemui.media.controls.ui.view.GutsViewHolder @@ -236,6 +237,19 @@ public class MediaControlPanelTest : SysuiTestCase() { @Mock private lateinit var recProgressBar3: SeekBar @Mock private lateinit var globalSettings: GlobalSettings + private val intent = + Intent().apply { + putExtras(Bundle().also { it.putString(KEY_SMARTSPACE_APP_NAME, REC_APP_NAME) }) + setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + private val pendingIntent = + PendingIntent.getActivity( + mContext, + 0, + intent.setPackage(mContext.packageName), + PendingIntent.FLAG_MUTABLE + ) + @JvmField @Rule val mockito = MockitoJUnit.rule() @Before @@ -989,14 +1003,13 @@ public class MediaControlPanelTest : SysuiTestCase() { @Test fun bindNotificationActions() { val icon = context.getDrawable(android.R.drawable.ic_media_play) - val bg = context.getDrawable(R.drawable.qs_media_round_button_background) val actions = listOf( - MediaAction(icon, Runnable {}, "previous", bg), - MediaAction(icon, Runnable {}, "play", bg), - MediaAction(icon, null, "next", bg), - MediaAction(icon, null, "custom 0", bg), - MediaAction(icon, Runnable {}, "custom 1", bg) + MediaNotificationAction(true, actionIntent = pendingIntent, icon, "previous"), + MediaNotificationAction(true, actionIntent = pendingIntent, icon, "play"), + MediaNotificationAction(true, actionIntent = null, icon, "next"), + MediaNotificationAction(true, actionIntent = null, icon, "custom 0"), + MediaNotificationAction(true, actionIntent = pendingIntent, icon, "custom 1") ) val state = mediaData.copy( @@ -1684,11 +1697,11 @@ public class MediaControlPanelTest : SysuiTestCase() { fun actionCustom2Click_isLogged() { val actions = listOf( - MediaAction(null, Runnable {}, "action 0", null), - MediaAction(null, Runnable {}, "action 1", null), - MediaAction(null, Runnable {}, "action 2", null), - MediaAction(null, Runnable {}, "action 3", null), - MediaAction(null, Runnable {}, "action 4", null) + MediaNotificationAction(true, actionIntent = pendingIntent, null, "action 0"), + MediaNotificationAction(true, actionIntent = pendingIntent, null, "action 1"), + MediaNotificationAction(true, actionIntent = pendingIntent, null, "action 2"), + MediaNotificationAction(true, actionIntent = pendingIntent, null, "action 3"), + MediaNotificationAction(true, actionIntent = pendingIntent, null, "action 4") ) val data = mediaData.copy(actions = actions) @@ -1703,11 +1716,11 @@ public class MediaControlPanelTest : SysuiTestCase() { fun actionCustom3Click_isLogged() { val actions = listOf( - MediaAction(null, Runnable {}, "action 0", null), - MediaAction(null, Runnable {}, "action 1", null), - MediaAction(null, Runnable {}, "action 2", null), - MediaAction(null, Runnable {}, "action 3", null), - MediaAction(null, Runnable {}, "action 4", null) + MediaNotificationAction(true, actionIntent = pendingIntent, null, "action 0"), + MediaNotificationAction(true, actionIntent = pendingIntent, null, "action 1"), + MediaNotificationAction(true, actionIntent = pendingIntent, null, "action 2"), + MediaNotificationAction(true, actionIntent = pendingIntent, null, "action 3"), + MediaNotificationAction(true, actionIntent = pendingIntent, null, "action 4") ) val data = mediaData.copy(actions = actions) @@ -1722,11 +1735,11 @@ public class MediaControlPanelTest : SysuiTestCase() { fun actionCustom4Click_isLogged() { val actions = listOf( - MediaAction(null, Runnable {}, "action 0", null), - MediaAction(null, Runnable {}, "action 1", null), - MediaAction(null, Runnable {}, "action 2", null), - MediaAction(null, Runnable {}, "action 3", null), - MediaAction(null, Runnable {}, "action 4", null) + MediaNotificationAction(true, actionIntent = pendingIntent, null, "action 0"), + MediaNotificationAction(true, actionIntent = pendingIntent, null, "action 1"), + MediaNotificationAction(true, actionIntent = pendingIntent, null, "action 2"), + MediaNotificationAction(true, actionIntent = pendingIntent, null, "action 3"), + MediaNotificationAction(true, actionIntent = pendingIntent, null, "action 4") ) val data = mediaData.copy(actions = actions) diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HearingDevicesTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HearingDevicesTileTest.java index 76c8cf081262..7d41a20e628f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HearingDevicesTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HearingDevicesTileTest.java @@ -16,6 +16,8 @@ package com.android.systemui.qs.tiles; +import static com.android.systemui.accessibility.hearingaid.HearingDevicesUiEventLogger.LAUNCH_SOURCE_QS_TILE; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -151,7 +153,7 @@ public class HearingDevicesTileTest extends SysuiTestCase { mTile.handleClick(expandable); mTestableLooper.processAllMessages(); - verify(mHearingDevicesDialogManager).showDialog(expandable); + verify(mHearingDevicesDialogManager).showDialog(expandable, LAUNCH_SOURCE_QS_TILE); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt index 828c7b2dbc69..e6ec07e97d17 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt @@ -259,7 +259,6 @@ class InternetTileNewImplTest : SysuiTestCase() { const val WIFI_SSID = "test ssid" val ACTIVE_WIFI = WifiNetworkModel.Active( - networkId = 1, isValidated = true, level = 4, ssid = WIFI_SSID, diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt index 73548baad377..ca518f9bc5d7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt @@ -35,6 +35,7 @@ import com.android.systemui.recordissue.IssueRecordingState import com.android.systemui.recordissue.RecordIssueDialogDelegate import com.android.systemui.recordissue.TraceurMessageSender import com.android.systemui.res.R +import com.android.systemui.screenrecord.RecordingController import com.android.systemui.settings.UserContextProvider import com.android.systemui.statusbar.phone.KeyguardDismissUtil import com.android.systemui.statusbar.phone.SystemUIDialog @@ -65,6 +66,7 @@ class RecordIssueTileTest : SysuiTestCase() { @Mock private lateinit var qsEventLogger: QsEventLogger @Mock private lateinit var metricsLogger: MetricsLogger @Mock private lateinit var statusBarStateController: StatusBarStateController + @Mock private lateinit var recordingController: RecordingController @Mock private lateinit var activityStarter: ActivityStarter @Mock private lateinit var qsLogger: QSLogger @Mock private lateinit var keyguardDismissUtil: KeyguardDismissUtil @@ -109,6 +111,7 @@ class RecordIssueTileTest : SysuiTestCase() { Executors.newSingleThreadExecutor(), issueRecordingState, delegateFactory, + recordingController, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/recordissue/IssueRecordingServiceCommandHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recordissue/IssueRecordingServiceCommandHandlerTest.kt new file mode 100644 index 000000000000..57cfe1b9e902 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/recordissue/IssueRecordingServiceCommandHandlerTest.kt @@ -0,0 +1,139 @@ +/* + * 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.recordissue + +import android.app.IActivityManager +import android.app.NotificationManager +import android.net.Uri +import android.os.UserHandle +import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.DialogTransitionAnimator +import com.android.systemui.animation.dialogTransitionAnimator +import com.android.systemui.concurrency.fakeExecutor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testCase +import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor +import com.android.systemui.settings.UserContextProvider +import com.android.systemui.settings.userFileManager +import com.android.systemui.settings.userTracker +import com.android.traceur.TraceConfig +import com.google.common.truth.Truth +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.kotlin.any +import org.mockito.kotlin.isNull +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify + +@SmallTest +@RunWith(AndroidJUnit4::class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +class IssueRecordingServiceCommandHandlerTest : SysuiTestCase() { + + private val kosmos = Kosmos().also { it.testCase = this } + private val bgExecutor = kosmos.fakeExecutor + private val userContextProvider: UserContextProvider = kosmos.userTracker + private val dialogTransitionAnimator: DialogTransitionAnimator = kosmos.dialogTransitionAnimator + private lateinit var traceurMessageSender: TraceurMessageSender + private val issueRecordingState = + IssueRecordingState(kosmos.userTracker, kosmos.userFileManager) + + private val iActivityManager = mock<IActivityManager>() + private val notificationManager = mock<NotificationManager>() + private val panelInteractor = mock<PanelInteractor>() + + private lateinit var underTest: IssueRecordingServiceCommandHandler + + @Before + fun setup() { + traceurMessageSender = mock<TraceurMessageSender>() + underTest = + IssueRecordingServiceCommandHandler( + bgExecutor, + dialogTransitionAnimator, + panelInteractor, + traceurMessageSender, + issueRecordingState, + iActivityManager, + notificationManager, + userContextProvider + ) + } + + @Test + fun startsTracing_afterReceivingActionStartCommand() { + underTest.handleStartCommand() + bgExecutor.runAllReady() + + Truth.assertThat(issueRecordingState.isRecording).isTrue() + verify(traceurMessageSender).startTracing(any<TraceConfig>()) + } + + @Test + fun stopsTracing_afterReceivingStopTracingCommand() { + underTest.handleStopCommand(mContext.contentResolver) + bgExecutor.runAllReady() + + Truth.assertThat(issueRecordingState.isRecording).isFalse() + verify(traceurMessageSender).stopTracing() + } + + @Test + fun cancelsNotification_afterReceivingShareCommand() { + underTest.handleShareCommand(0, null, mContext) + bgExecutor.runAllReady() + + verify(notificationManager).cancelAsUser(isNull(), anyInt(), any<UserHandle>()) + } + + @Test + fun requestBugreport_afterReceivingShareCommand_withTakeBugreportTrue() { + issueRecordingState.takeBugreport = true + val uri = mock<Uri>() + + underTest.handleShareCommand(0, uri, mContext) + bgExecutor.runAllReady() + + verify(iActivityManager).requestBugReportWithExtraAttachment(uri) + } + + @Test + fun sharesTracesDirectly_afterReceivingShareCommand_withTakeBugreportFalse() { + issueRecordingState.takeBugreport = false + val uri = mock<Uri>() + + underTest.handleShareCommand(0, uri, mContext) + bgExecutor.runAllReady() + + verify(traceurMessageSender).shareTraces(mContext, uri) + } + + @Test + fun closesShade_afterReceivingShareCommand() { + val uri = mock<Uri>() + + underTest.handleShareCommand(0, uri, mContext) + bgExecutor.runAllReady() + + verify(panelInteractor).collapsePanels() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplReceiveTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplReceiveTest.kt index 263b0017221a..78764c27327c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplReceiveTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplReceiveTest.kt @@ -79,21 +79,6 @@ class UserTrackerImplReceiveTest : SysuiTestCase() { @Test fun callsCallbackAndUpdatesProfilesWhenAnIntentReceived() = runTest { - tracker = - UserTrackerImpl( - context, - { fakeFeatures }, - userManager, - iActivityManager, - dumpManager, - this, - testDispatcher, - handler - ) - tracker.initialize(0) - 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) @@ -109,6 +94,21 @@ class UserTrackerImplReceiveTest : SysuiTestCase() { listOf(info, infoProfile) } + tracker = + UserTrackerImpl( + context, + { fakeFeatures }, + userManager, + iActivityManager, + dumpManager, + this, + testDispatcher, + handler + ) + tracker.initialize(0) + tracker.addCallback(callback, executor) + val profileID = tracker.userId + 10 + tracker.onReceive(context, Intent(intentAction)) verify(callback, times(0)).onUserChanged(anyInt(), any()) 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 3e3c046ce62e..01a3d36a05ec 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 @@ -779,6 +779,26 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { @Test @DisableSceneContainer + public void testResetDoesNotHideBouncerWhenNotShowing() { + reset(mDismissCallbackRegistry); + reset(mPrimaryBouncerInteractor); + + // GIVEN the keyguard is showing + reset(mAlternateBouncerInteractor); + when(mKeyguardStateController.isShowing()).thenReturn(true); + when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(false); + + // WHEN SBKV is reset with hideBouncerWhenShowing=true + mStatusBarKeyguardViewManager.reset(true); + + // THEN no calls to hide should be made + verify(mAlternateBouncerInteractor, never()).hide(); + verify(mDismissCallbackRegistry, never()).notifyDismissCancelled(); + verify(mPrimaryBouncerInteractor, never()).setDismissAction(eq(null), eq(null)); + } + + @Test + @DisableSceneContainer public void testResetHideBouncerWhenShowing_alternateBouncerHides() { reset(mDismissCallbackRegistry); reset(mPrimaryBouncerInteractor); @@ -786,6 +806,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { // GIVEN the keyguard is showing reset(mAlternateBouncerInteractor); when(mKeyguardStateController.isShowing()).thenReturn(true); + when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(true); // WHEN SBKV is reset with hideBouncerWhenShowing=true mStatusBarKeyguardViewManager.reset(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt index 237aabccfbd9..b6e23c1f42ee 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt @@ -104,7 +104,7 @@ class CarrierMergedConnectionRepositoryTest : SysuiTestCase() { underTest.dataConnectionState.onEach { latestConnState = it }.launchIn(this) val netJob = underTest.resolvedNetworkType.onEach { latestNetType = it }.launchIn(this) - wifiRepository.setWifiNetwork(WifiNetworkModel.Active(networkId = NET_ID, level = 1)) + wifiRepository.setWifiNetwork(WifiNetworkModel.Active(level = 1)) assertThat(latestConnState).isEqualTo(DataConnectionState.Disconnected) assertThat(latestNetType).isNotEqualTo(ResolvedNetworkType.CarrierMergedNetworkType) @@ -124,7 +124,6 @@ class CarrierMergedConnectionRepositoryTest : SysuiTestCase() { wifiRepository.setWifiNetwork( WifiNetworkModel.CarrierMerged( - networkId = NET_ID, subscriptionId = SUB_ID, level = 3, ) @@ -145,7 +144,6 @@ class CarrierMergedConnectionRepositoryTest : SysuiTestCase() { wifiRepository.setIsWifiDefault(true) wifiRepository.setWifiNetwork( WifiNetworkModel.CarrierMerged( - networkId = NET_ID, subscriptionId = SUB_ID, level = 3, ) @@ -183,7 +181,6 @@ class CarrierMergedConnectionRepositoryTest : SysuiTestCase() { wifiRepository.setWifiNetwork( WifiNetworkModel.CarrierMerged( - networkId = NET_ID, subscriptionId = SUB_ID + 10, level = 3, ) @@ -205,7 +202,6 @@ class CarrierMergedConnectionRepositoryTest : SysuiTestCase() { wifiRepository.setWifiNetwork( WifiNetworkModel.CarrierMerged( - networkId = NET_ID, subscriptionId = SUB_ID, level = 3, ) @@ -226,7 +222,6 @@ class CarrierMergedConnectionRepositoryTest : SysuiTestCase() { wifiRepository.setWifiNetwork( WifiNetworkModel.CarrierMerged( - networkId = NET_ID, subscriptionId = SUB_ID, level = 3, ) @@ -246,7 +241,6 @@ class CarrierMergedConnectionRepositoryTest : SysuiTestCase() { wifiRepository.setWifiNetwork( WifiNetworkModel.CarrierMerged( - networkId = NET_ID, subscriptionId = SUB_ID, level = 1, numberOfLevels = 6, @@ -310,7 +304,6 @@ class CarrierMergedConnectionRepositoryTest : SysuiTestCase() { whenever(telephonyManager.simOperatorName).thenReturn("New SIM name") wifiRepository.setWifiNetwork( WifiNetworkModel.CarrierMerged( - networkId = NET_ID, subscriptionId = SUB_ID, level = 3, ) @@ -331,6 +324,5 @@ class CarrierMergedConnectionRepositoryTest : SysuiTestCase() { private companion object { const val SUB_ID = 123 - const val NET_ID = 456 } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt index c02985057b86..a03980a9d45f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt @@ -487,10 +487,8 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() { val job = underTest.primaryLevel.launchIn(this) // WHEN we set up carrier merged info - val networkId = 2 wifiRepository.setWifiNetwork( WifiNetworkModel.CarrierMerged( - networkId, SUB_ID, level = 3, ) @@ -502,7 +500,6 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() { // WHEN we update the info wifiRepository.setWifiNetwork( WifiNetworkModel.CarrierMerged( - networkId, SUB_ID, level = 1, ) @@ -540,10 +537,8 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() { assertThat(dumpBuffer()).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}1") // WHEN isCarrierMerged is set to true - val networkId = 2 wifiRepository.setWifiNetwork( WifiNetworkModel.CarrierMerged( - networkId, SUB_ID, level = 3, ) @@ -556,7 +551,6 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() { // WHEN the carrier merge network is updated wifiRepository.setWifiNetwork( WifiNetworkModel.CarrierMerged( - networkId, SUB_ID, level = 4, ) @@ -607,10 +601,8 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() { .onSignalStrengthsChanged(signalStrength) // THEN updates to the carrier merged level aren't logged - val networkId = 2 wifiRepository.setWifiNetwork( WifiNetworkModel.CarrierMerged( - networkId, SUB_ID, level = 4, ) @@ -619,7 +611,6 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() { wifiRepository.setWifiNetwork( WifiNetworkModel.CarrierMerged( - networkId, SUB_ID, level = 3, ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt index fe408e3246c8..763449028f28 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt @@ -817,7 +817,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { captor.lastValue.onReceive(context, intent) // spnIntent() sets all values to true and test strings - assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN")) + assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN")) job.cancel() } @@ -852,7 +852,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { verify(context).registerReceiver(captor.capture(), any()) captor.lastValue.onReceive(context, intent) - assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN")) + assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN")) // WHEN an intent with a different subId is sent val wrongSubIntent = spnIntent(subId = 101) @@ -860,7 +860,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { captor.lastValue.onReceive(context, wrongSubIntent) // THEN the previous intent's name is still used - assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN")) + assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN")) job.cancel() } @@ -902,7 +902,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { verify(context).registerReceiver(captor.capture(), any()) captor.lastValue.onReceive(context, intent) - assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN")) + assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN")) val intentWithoutInfo = spnIntent( @@ -961,7 +961,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { // The value is still there despite no active subscribers assertThat(underTest.networkName.value) - .isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN")) + .isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN")) } @Test @@ -986,7 +986,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { @Test @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN) - fun networkName_allFieldsSet_doesNotUseDataSpn() = + fun networkName_allFieldsSet_prioritizesDataSpnOverSpn() = testScope.runTest { val latest by collectLastValue(underTest.networkName) val captor = argumentCaptor<BroadcastReceiver>() @@ -1002,6 +1002,27 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { plmn = PLMN, ) captor.lastValue.onReceive(context, intent) + assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN")) + } + + @Test + @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN) + fun networkName_spnAndPlmn_fallbackToSpnWhenNullDataSpn() = + testScope.runTest { + val latest by collectLastValue(underTest.networkName) + val captor = argumentCaptor<BroadcastReceiver>() + verify(context).registerReceiver(captor.capture(), any()) + + val intent = + spnIntent( + subId = SUB_1_ID, + showSpn = true, + spn = SPN, + dataSpn = null, + showPlmn = true, + plmn = PLMN, + ) + captor.lastValue.onReceive(context, intent) assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN")) } @@ -1043,7 +1064,27 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { plmn = PLMN, ) captor.lastValue.onReceive(context, intent) - assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN")) + assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN")) + } + + @Test + @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN) + fun networkName_showPlmn_plmnNotNull_showSpn_spnNotNull_dataSpnNull() = + testScope.runTest { + val latest by collectLastValue(underTest.networkName) + val captor = argumentCaptor<BroadcastReceiver>() + verify(context).registerReceiver(captor.capture(), any()) + val intent = + spnIntent( + subId = SUB_1_ID, + showSpn = true, + spn = SPN, + dataSpn = null, + showPlmn = true, + plmn = PLMN, + ) + captor.lastValue.onReceive(context, intent) + assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN")) } @Test @@ -1102,10 +1143,50 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { plmn = null, ) captor.lastValue.onReceive(context, intent) + assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$DATA_SPN")) + } + + @Test + @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN) + fun networkName_showPlmn_plmnNull_showSpn_dataSpnNull() = + testScope.runTest { + val latest by collectLastValue(underTest.networkName) + val captor = argumentCaptor<BroadcastReceiver>() + verify(context).registerReceiver(captor.capture(), any()) + val intent = + spnIntent( + subId = SUB_1_ID, + showSpn = true, + spn = SPN, + dataSpn = null, + showPlmn = true, + plmn = null, + ) + captor.lastValue.onReceive(context, intent) assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$SPN")) } @Test + @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN) + fun networkName_showPlmn_plmnNull_showSpn_bothSpnNull() = + testScope.runTest { + val latest by collectLastValue(underTest.networkName) + val captor = argumentCaptor<BroadcastReceiver>() + verify(context).registerReceiver(captor.capture(), any()) + val intent = + spnIntent( + subId = SUB_1_ID, + showSpn = true, + spn = null, + dataSpn = null, + showPlmn = true, + plmn = null, + ) + captor.lastValue.onReceive(context, intent) + assertThat(latest).isEqualTo(DEFAULT_NAME_MODEL) + } + + @Test @DisableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN) fun networkName_showPlmn_plmnNull_showSpn_flagOff() = testScope.runTest { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt index dbb77d5ba76b..a1cb29b8e95c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt @@ -574,7 +574,7 @@ class DeviceBasedSatelliteInteractorTest : SysuiTestCase() { val latest by collectLastValue(underTest.isWifiActive) // WHEN wifi is active - wifiRepository.setWifiNetwork(WifiNetworkModel.Active(networkId = 0, level = 1)) + wifiRepository.setWifiNetwork(WifiNetworkModel.Active(level = 1)) // THEN the interactor returns true due to the wifi network being active assertThat(latest).isTrue() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt index bf31f1e6d569..c1abf9826ea3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt @@ -375,7 +375,7 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() { repo.isSatelliteProvisioned.value = true // GIVEN wifi network is active - wifiRepository.setWifiNetwork(WifiNetworkModel.Active(networkId = 0, level = 1)) + wifiRepository.setWifiNetwork(WifiNetworkModel.Active(level = 1)) // THEN icon is null because the device is connected to wifi assertThat(latest).isNull() @@ -573,7 +573,7 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() { repo.isSatelliteProvisioned.value = true // GIVEN wifi network is active - wifiRepository.setWifiNetwork(WifiNetworkModel.Active(networkId = 0, level = 1)) + wifiRepository.setWifiNetwork(WifiNetworkModel.Active(level = 1)) // THEN carrier text is null because the device is connected to wifi assertThat(latest).isNull() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt index 50f262c95abb..fed33179250b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt @@ -155,7 +155,6 @@ class InternetTileViewModelTest : SysuiTestCase() { val networkModel = WifiNetworkModel.Active( - networkId = 1, level = 4, ssid = "test ssid", ) @@ -185,7 +184,6 @@ class InternetTileViewModelTest : SysuiTestCase() { val networkModel = WifiNetworkModel.Active( - networkId = 1, level = 4, ssid = "test ssid", hotspotDeviceType = WifiNetworkModel.HotspotDeviceType.NONE, @@ -393,7 +391,6 @@ class InternetTileViewModelTest : SysuiTestCase() { private fun setWifiNetworkWithHotspot(hotspot: WifiNetworkModel.HotspotDeviceType) { val networkModel = WifiNetworkModel.Active( - networkId = 1, level = 4, ssid = "test ssid", hotspotDeviceType = hotspot, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt index b9c57d8be650..46f34e82c688 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt @@ -283,27 +283,6 @@ class WifiRepositoryImplTest : SysuiTestCase() { } @Test - fun accessPointInfo_alwaysFalse() = - testScope.runTest { - val latest by collectLastValue(underTest.wifiNetwork) - - val wifiEntry = - mock<WifiEntry>().apply { - whenever(this.isPrimaryNetwork).thenReturn(true) - whenever(this.level).thenReturn(3) - whenever(this.title).thenReturn(TITLE) - } - whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) - getCallback().onWifiEntriesChanged() - - assertThat(latest is WifiNetworkModel.Active).isTrue() - val latestActive = latest as WifiNetworkModel.Active - assertThat(latestActive.isPasspointAccessPoint).isFalse() - assertThat(latestActive.isOnlineSignUpForPasspointAccessPoint).isFalse() - assertThat(latestActive.passpointProviderFriendlyName).isNull() - } - - @Test fun wifiNetwork_unreachableLevel_inactiveNetwork() = testScope.runTest { val latest by collectLastValue(underTest.wifiNetwork) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt index eb6b068ca8a4..92860efc0c35 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt @@ -34,31 +34,30 @@ class WifiNetworkModelTest : SysuiTestCase() { @Test fun active_levelsInValidRange_noException() { (MIN_VALID_LEVEL..MAX_VALID_LEVEL).forEach { level -> - WifiNetworkModel.Active(NETWORK_ID, level = level) + WifiNetworkModel.Active(level = level) // No assert, just need no crash } } @Test(expected = IllegalArgumentException::class) fun active_levelNegative_exceptionThrown() { - WifiNetworkModel.Active(NETWORK_ID, level = MIN_VALID_LEVEL - 1) + WifiNetworkModel.Active(level = MIN_VALID_LEVEL - 1) } @Test(expected = IllegalArgumentException::class) fun active_levelTooHigh_exceptionThrown() { - WifiNetworkModel.Active(NETWORK_ID, level = MAX_VALID_LEVEL + 1) + WifiNetworkModel.Active(level = MAX_VALID_LEVEL + 1) } @Test(expected = IllegalArgumentException::class) fun carrierMerged_invalidSubId_exceptionThrown() { - WifiNetworkModel.CarrierMerged(NETWORK_ID, INVALID_SUBSCRIPTION_ID, 1) + WifiNetworkModel.CarrierMerged(INVALID_SUBSCRIPTION_ID, 1) } @Test fun active_hasValidSsid_nullSsid_false() { val network = WifiNetworkModel.Active( - NETWORK_ID, level = MAX_VALID_LEVEL, ssid = null, ) @@ -70,7 +69,6 @@ class WifiNetworkModelTest : SysuiTestCase() { fun active_hasValidSsid_unknownSsid_false() { val network = WifiNetworkModel.Active( - NETWORK_ID, level = MAX_VALID_LEVEL, ssid = UNKNOWN_SSID, ) @@ -82,7 +80,6 @@ class WifiNetworkModelTest : SysuiTestCase() { fun active_hasValidSsid_validSsid_true() { val network = WifiNetworkModel.Active( - NETWORK_ID, level = MAX_VALID_LEVEL, ssid = "FakeSsid", ) @@ -97,7 +94,6 @@ class WifiNetworkModelTest : SysuiTestCase() { val logger = TestLogger() val prevVal = WifiNetworkModel.CarrierMerged( - networkId = 5, subscriptionId = 3, level = 1, ) @@ -105,7 +101,6 @@ class WifiNetworkModelTest : SysuiTestCase() { WifiNetworkModel.Inactive.logDiffs(prevVal, logger) assertThat(logger.changes).contains(Pair(COL_NETWORK_TYPE, TYPE_INACTIVE)) - assertThat(logger.changes).contains(Pair(COL_NETWORK_ID, NETWORK_ID_DEFAULT.toString())) assertThat(logger.changes).contains(Pair(COL_VALIDATED, "false")) assertThat(logger.changes).contains(Pair(COL_LEVEL, LEVEL_DEFAULT.toString())) assertThat(logger.changes).contains(Pair(COL_SSID, "null")) @@ -116,7 +111,6 @@ class WifiNetworkModelTest : SysuiTestCase() { val logger = TestLogger() val carrierMerged = WifiNetworkModel.CarrierMerged( - networkId = 6, subscriptionId = 3, level = 2, ) @@ -124,7 +118,6 @@ class WifiNetworkModelTest : SysuiTestCase() { carrierMerged.logDiffs(prevVal = WifiNetworkModel.Inactive, logger) assertThat(logger.changes).contains(Pair(COL_NETWORK_TYPE, TYPE_CARRIER_MERGED)) - assertThat(logger.changes).contains(Pair(COL_NETWORK_ID, "6")) assertThat(logger.changes).contains(Pair(COL_SUB_ID, "3")) assertThat(logger.changes).contains(Pair(COL_VALIDATED, "true")) assertThat(logger.changes).contains(Pair(COL_LEVEL, "2")) @@ -136,7 +129,6 @@ class WifiNetworkModelTest : SysuiTestCase() { val logger = TestLogger() val activeNetwork = WifiNetworkModel.Active( - networkId = 5, isValidated = true, level = 3, ssid = "Test SSID", @@ -146,27 +138,21 @@ class WifiNetworkModelTest : SysuiTestCase() { activeNetwork.logDiffs(prevVal = WifiNetworkModel.Inactive, logger) assertThat(logger.changes).contains(Pair(COL_NETWORK_TYPE, TYPE_ACTIVE)) - assertThat(logger.changes).contains(Pair(COL_NETWORK_ID, "5")) assertThat(logger.changes).contains(Pair(COL_VALIDATED, "true")) assertThat(logger.changes).contains(Pair(COL_LEVEL, "3")) assertThat(logger.changes).contains(Pair(COL_SSID, "Test SSID")) assertThat(logger.changes).contains(Pair(COL_HOTSPOT, "LAPTOP")) } + @Test fun logDiffs_activeToInactive_resetsAllActiveFields() { val logger = TestLogger() val activeNetwork = - WifiNetworkModel.Active( - networkId = 5, - isValidated = true, - level = 3, - ssid = "Test SSID" - ) + WifiNetworkModel.Active(isValidated = true, level = 3, ssid = "Test SSID") WifiNetworkModel.Inactive.logDiffs(prevVal = activeNetwork, logger) assertThat(logger.changes).contains(Pair(COL_NETWORK_TYPE, TYPE_INACTIVE)) - assertThat(logger.changes).contains(Pair(COL_NETWORK_ID, NETWORK_ID_DEFAULT.toString())) assertThat(logger.changes).contains(Pair(COL_VALIDATED, "false")) assertThat(logger.changes).contains(Pair(COL_LEVEL, LEVEL_DEFAULT.toString())) assertThat(logger.changes).contains(Pair(COL_SSID, "null")) @@ -178,7 +164,6 @@ class WifiNetworkModelTest : SysuiTestCase() { val logger = TestLogger() val activeNetwork = WifiNetworkModel.Active( - networkId = 5, isValidated = true, level = 3, ssid = "Test SSID", @@ -186,7 +171,6 @@ class WifiNetworkModelTest : SysuiTestCase() { ) val prevVal = WifiNetworkModel.CarrierMerged( - networkId = 5, subscriptionId = 3, level = 1, ) @@ -194,25 +178,19 @@ class WifiNetworkModelTest : SysuiTestCase() { activeNetwork.logDiffs(prevVal, logger) assertThat(logger.changes).contains(Pair(COL_NETWORK_TYPE, TYPE_ACTIVE)) - assertThat(logger.changes).contains(Pair(COL_NETWORK_ID, "5")) assertThat(logger.changes).contains(Pair(COL_VALIDATED, "true")) assertThat(logger.changes).contains(Pair(COL_LEVEL, "3")) assertThat(logger.changes).contains(Pair(COL_SSID, "Test SSID")) assertThat(logger.changes).contains(Pair(COL_HOTSPOT, "AUTO")) } + @Test fun logDiffs_activeToCarrierMerged_logsAllFields() { val logger = TestLogger() val activeNetwork = - WifiNetworkModel.Active( - networkId = 5, - isValidated = true, - level = 3, - ssid = "Test SSID" - ) + WifiNetworkModel.Active(isValidated = true, level = 3, ssid = "Test SSID") val carrierMerged = WifiNetworkModel.CarrierMerged( - networkId = 6, subscriptionId = 3, level = 2, ) @@ -220,7 +198,6 @@ class WifiNetworkModelTest : SysuiTestCase() { carrierMerged.logDiffs(prevVal = activeNetwork, logger) assertThat(logger.changes).contains(Pair(COL_NETWORK_TYPE, TYPE_CARRIER_MERGED)) - assertThat(logger.changes).contains(Pair(COL_NETWORK_ID, "6")) assertThat(logger.changes).contains(Pair(COL_SUB_ID, "3")) assertThat(logger.changes).contains(Pair(COL_VALIDATED, "true")) assertThat(logger.changes).contains(Pair(COL_LEVEL, "2")) @@ -231,19 +208,9 @@ class WifiNetworkModelTest : SysuiTestCase() { fun logDiffs_activeChangesLevel_onlyLevelLogged() { val logger = TestLogger() val prevActiveNetwork = - WifiNetworkModel.Active( - networkId = 5, - isValidated = true, - level = 3, - ssid = "Test SSID" - ) + WifiNetworkModel.Active(isValidated = true, level = 3, ssid = "Test SSID") val newActiveNetwork = - WifiNetworkModel.Active( - networkId = 5, - isValidated = true, - level = 2, - ssid = "Test SSID" - ) + WifiNetworkModel.Active(isValidated = true, level = 2, ssid = "Test SSID") newActiveNetwork.logDiffs(prevActiveNetwork, logger) @@ -265,8 +232,4 @@ class WifiNetworkModelTest : SysuiTestCase() { changes.add(Pair(columnName, value.toString())) } } - - companion object { - private const val NETWORK_ID = 2 - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt index 161c4f58f5e0..ff398f988636 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt @@ -195,9 +195,7 @@ class ModernStatusBarWifiViewTest : SysuiTestCase() { @Test fun isIconVisible_notEnabled_outputsFalse() { wifiRepository.setIsWifiEnabled(false) - wifiRepository.setWifiNetwork( - WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 2) - ) + wifiRepository.setWifiNetwork(WifiNetworkModel.Active(isValidated = true, level = 2)) val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel) @@ -212,9 +210,7 @@ class ModernStatusBarWifiViewTest : SysuiTestCase() { @Test fun isIconVisible_enabled_outputsTrue() { wifiRepository.setIsWifiEnabled(true) - wifiRepository.setWifiNetwork( - WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 2) - ) + wifiRepository.setWifiNetwork(WifiNetworkModel.Active(isValidated = true, level = 2)) val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel) @@ -272,4 +268,3 @@ class ModernStatusBarWifiViewTest : SysuiTestCase() { } private const val SLOT_NAME = "TestSlotName" -private const val NETWORK_ID = 200 diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt index d2a4bf3f7505..82acb40ec90c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt @@ -206,8 +206,7 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase // Enabled = false => no networks shown TestCase( enabled = false, - network = - WifiNetworkModel.CarrierMerged(NETWORK_ID, subscriptionId = 1, level = 1), + network = WifiNetworkModel.CarrierMerged(subscriptionId = 1, level = 1), expected = null, ), TestCase( @@ -217,20 +216,19 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase ), TestCase( enabled = false, - network = WifiNetworkModel.Active(NETWORK_ID, isValidated = false, level = 1), + network = WifiNetworkModel.Active(isValidated = false, level = 1), expected = null, ), TestCase( enabled = false, - network = WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 3), + network = WifiNetworkModel.Active(isValidated = true, level = 3), expected = null, ), // forceHidden = true => no networks shown TestCase( forceHidden = true, - network = - WifiNetworkModel.CarrierMerged(NETWORK_ID, subscriptionId = 1, level = 1), + network = WifiNetworkModel.CarrierMerged(subscriptionId = 1, level = 1), expected = null, ), TestCase( @@ -240,12 +238,12 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase ), TestCase( enabled = false, - network = WifiNetworkModel.Active(NETWORK_ID, isValidated = false, level = 2), + network = WifiNetworkModel.Active(isValidated = false, level = 2), expected = null, ), TestCase( forceHidden = true, - network = WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 1), + network = WifiNetworkModel.Active(isValidated = true, level = 1), expected = null, ), @@ -265,7 +263,7 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase ), TestCase( alwaysShowIconWhenEnabled = true, - network = WifiNetworkModel.Active(NETWORK_ID, isValidated = false, level = 4), + network = WifiNetworkModel.Active(isValidated = false, level = 4), expected = Expected( iconResource = WIFI_NO_INTERNET_ICONS[4], @@ -278,7 +276,7 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase ), TestCase( alwaysShowIconWhenEnabled = true, - network = WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 2), + network = WifiNetworkModel.Active(isValidated = true, level = 2), expected = Expected( iconResource = WIFI_FULL_ICONS[2], @@ -305,7 +303,7 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase ), TestCase( hasDataCapabilities = false, - network = WifiNetworkModel.Active(NETWORK_ID, isValidated = false, level = 2), + network = WifiNetworkModel.Active(isValidated = false, level = 2), expected = Expected( iconResource = WIFI_NO_INTERNET_ICONS[2], @@ -318,7 +316,7 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase ), TestCase( hasDataCapabilities = false, - network = WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 0), + network = WifiNetworkModel.Active(isValidated = true, level = 0), expected = Expected( iconResource = WIFI_FULL_ICONS[0], @@ -345,7 +343,7 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase ), TestCase( isDefault = true, - network = WifiNetworkModel.Active(NETWORK_ID, isValidated = false, level = 3), + network = WifiNetworkModel.Active(isValidated = false, level = 3), expected = Expected( iconResource = WIFI_NO_INTERNET_ICONS[3], @@ -358,7 +356,7 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase ), TestCase( isDefault = true, - network = WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 1), + network = WifiNetworkModel.Active(isValidated = true, level = 1), expected = Expected( iconResource = WIFI_FULL_ICONS[1], @@ -374,8 +372,7 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase enabled = true, isDefault = true, forceHidden = false, - network = - WifiNetworkModel.CarrierMerged(NETWORK_ID, subscriptionId = 1, level = 1), + network = WifiNetworkModel.CarrierMerged(subscriptionId = 1, level = 1), expected = null, ), @@ -392,7 +389,7 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase ), TestCase( isDefault = false, - network = WifiNetworkModel.Active(NETWORK_ID, isValidated = false, level = 3), + network = WifiNetworkModel.Active(isValidated = false, level = 3), expected = null, ), @@ -400,7 +397,7 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase // because wifi isn't the default connection (b/272509965). TestCase( isDefault = false, - network = WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 4), + network = WifiNetworkModel.Active(isValidated = true, level = 4), expected = null, ), ) @@ -408,4 +405,3 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase } private val IMMEDIATE = Dispatchers.Main.immediate -private const val NETWORK_ID = 789 diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java index 035847497178..3041240e8c86 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java @@ -19,6 +19,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.PackageManager; import android.hardware.display.DisplayManager; import android.os.Handler; import android.os.UserHandle; @@ -40,6 +41,7 @@ public class SysuiTestableContext extends TestableContext { @GuardedBy("mRegisteredReceivers") private final Set<BroadcastReceiver> mRegisteredReceivers = new ArraySet<>(); private final Map<UserHandle, Context> mContextForUser = new HashMap<>(); + private final Map<String, Context> mContextForPackage = new HashMap<>(); public SysuiTestableContext(Context base) { super(base); @@ -175,4 +177,22 @@ public class SysuiTestableContext extends TestableContext { } return super.createContextAsUser(user, flags); } + + /** + * Sets a Context object that will be returned as the result of {@link #createPackageContext} + * for a specific {@code packageName}. + */ + public void prepareCreatePackageContext(String packageName, Context context) { + mContextForPackage.put(packageName, context); + } + + @Override + public Context createPackageContext(String packageName, int flags) + throws PackageManager.NameNotFoundException { + Context packageContext = mContextForPackage.get(packageName); + if (packageContext != null) { + return packageContext; + } + return super.createPackageContext(packageName, flags); + } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorKosmos.kt index 5ced578ad974..3087d01a2479 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorKosmos.kt @@ -26,6 +26,7 @@ import com.android.systemui.bouncer.data.repository.emergencyServicesRepository import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepository import com.android.systemui.telephony.domain.interactor.telephonyInteractor import com.android.systemui.user.domain.interactor.selectedUserInteractor @@ -50,5 +51,6 @@ val Kosmos.bouncerActionButtonInteractor by Fixture { }, metricsLogger = metricsLogger, dozeLogger = mock(), + sceneInteractor = { sceneInteractor }, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/MSDLPlayerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/MSDLPlayerKosmos.kt index f5a05b44d2cf..4f5c32abd2f8 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/MSDLPlayerKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/MSDLPlayerKosmos.kt @@ -17,5 +17,7 @@ package com.android.systemui.haptics.msdl import com.android.systemui.kosmos.Kosmos +import com.google.android.msdl.domain.MSDLPlayer -val Kosmos.msdlPlayer by Kosmos.Fixture { FakeMSDLPlayer() } +var Kosmos.msdlPlayer: MSDLPlayer by Kosmos.Fixture { fakeMSDLPlayer } +val Kosmos.fakeMSDLPlayer by Kosmos.Fixture { FakeMSDLPlayer() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderKosmos.kt index 1e95fc12bdb5..740d8919cbc0 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderKosmos.kt @@ -34,6 +34,7 @@ import com.android.systemui.keyguard.ui.viewmodel.alternateBouncerViewModel import com.android.systemui.keyguard.ui.viewmodel.alternateBouncerWindowViewModel import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.log.logcatLogBuffer import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.statusbar.gesture.TapGestureDetector import com.android.systemui.util.mockito.mock @@ -64,6 +65,7 @@ private val Kosmos.alternateBouncerDependencies by }, messageAreaViewModel = mock<AlternateBouncerMessageAreaViewModel>(), powerInteractor = powerInteractor, + touchLogBuffer = logcatLogBuffer(), ) } 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 851a378f3165..457bd284ea8d 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,7 +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.msdl.fakeMSDLPlayer import com.android.systemui.haptics.qs.qsLongPressEffect import com.android.systemui.jank.interactionJankMonitor import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository @@ -155,5 +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 } + val msdlPlayer by lazy { kosmos.fakeMSDLPlayer } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderKosmos.kt index a5690a0fa560..cb7750f55647 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderKosmos.kt @@ -24,7 +24,6 @@ import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.media.controls.util.fakeMediaControllerFactory import com.android.systemui.media.controls.util.mediaFlags -import com.android.systemui.plugins.activityStarter val Kosmos.mediaDataLoader by Kosmos.Fixture { @@ -32,7 +31,6 @@ val Kosmos.mediaDataLoader by testableContext, testDispatcher, testScope, - activityStarter, fakeMediaControllerFactory, mediaFlags, imageLoader, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorKosmos.kt index 632436a4574a..174e6532abcf 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorKosmos.kt @@ -26,6 +26,7 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testDispatcher import com.android.systemui.media.controls.data.repository.mediaDataRepository +import com.android.systemui.media.controls.shared.mediaLogger import com.android.systemui.media.controls.shared.model.SmartspaceMediaDataProvider import com.android.systemui.media.controls.util.fakeMediaControllerFactory import com.android.systemui.media.controls.util.mediaFlags @@ -60,5 +61,6 @@ val Kosmos.mediaDataProcessor by keyguardUpdateMonitor = keyguardUpdateMonitor, mediaDataRepository = mediaDataRepository, mediaDataLoader = { mediaDataLoader }, + mediaLogger = mediaLogger, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerKosmos.kt index b7660e05ee91..b33edf97bd55 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerKosmos.kt @@ -28,6 +28,8 @@ val Kosmos.mediaTimeoutListener by Kosmos.Fixture { MediaTimeoutListener( mediaControllerFactory = fakeMediaControllerFactory, + bgExecutor = fakeExecutor, + uiExecutor = fakeExecutor, mainExecutor = fakeExecutor, logger = MediaTimeoutLogger(logcatLogBuffer("MediaTimeoutLogBuffer")), statusBarStateController = statusBarStateController, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt index b612a8b5893a..9a5698cfb8ca 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt @@ -27,6 +27,7 @@ import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInt import com.android.systemui.deviceentry.domain.interactor.deviceEntryHapticsInteractor import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor +import com.android.systemui.haptics.msdl.msdlPlayer import com.android.systemui.haptics.vibratorHelper import com.android.systemui.keyguard.dismissCallbackRegistry import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor @@ -86,5 +87,6 @@ val Kosmos.sceneContainerStartable by Fixture { statusBarStateController = sysuiStatusBarStateController, alternateBouncerInteractor = alternateBouncerInteractor, vibratorHelper = vibratorHelper, + msdlPlayer = msdlPlayer, ) } diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp index 9b0c8e554d64..333fe4c8147f 100644 --- a/ravenwood/Android.bp +++ b/ravenwood/Android.bp @@ -127,6 +127,7 @@ java_library { libs: [ "framework-minus-apex.ravenwood", "ravenwood-junit", + "ravenwood-helper-libcore-runtime", ], visibility: ["//visibility:private"], } diff --git a/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodRedirect.java b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodRedirect.java new file mode 100644 index 000000000000..b582ccf7b656 --- /dev/null +++ b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodRedirect.java @@ -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.ravenwood.annotation; + +import static java.lang.annotation.ElementType.METHOD; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * THIS ANNOTATION IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY + * QUESTIONS ABOUT IT. + * + * TODO: Javadoc + * + * @hide + */ +@Target({METHOD}) +@Retention(RetentionPolicy.CLASS) +public @interface RavenwoodRedirect { +} diff --git a/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodNativeSubstitutionClass.java b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodRedirectionClass.java index 4b9cf85e16fa..bee9222ae5eb 100644 --- a/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodNativeSubstitutionClass.java +++ b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodRedirectionClass.java @@ -31,6 +31,6 @@ import java.lang.annotation.Target; */ @Target({TYPE}) @Retention(RetentionPolicy.CLASS) -public @interface RavenwoodNativeSubstitutionClass { +public @interface RavenwoodRedirectionClass { String value(); } 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 f237ba908507..1d182da5e7fd 100644 --- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java +++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java @@ -27,6 +27,9 @@ import android.util.Log; import androidx.test.platform.app.InstrumentationRegistry; +import com.android.internal.os.RuntimeInit; +import com.android.ravenwood.common.RavenwoodCommonUtils; + import org.junit.runner.Description; import org.junit.runner.Runner; import org.junit.runners.model.TestClass; @@ -35,7 +38,7 @@ import org.junit.runners.model.TestClass; * Provide hook points created by {@link RavenwoodAwareTestRunner}. */ public class RavenwoodAwareTestRunnerHook { - private static final String TAG = "RavenwoodAwareTestRunnerHook"; + private static final String TAG = RavenwoodAwareTestRunner.TAG; private RavenwoodAwareTestRunnerHook() { } @@ -56,20 +59,36 @@ public class RavenwoodAwareTestRunnerHook { * Called when a runner starts, before the inner runner gets a chance to run. */ public static void onRunnerInitializing(Runner runner, TestClass testClass) { + // TODO: Move the initialization code to a better place. + + initOnce(); + // This log call also ensures the framework JNI is loaded. Log.i(TAG, "onRunnerInitializing: testClass=" + testClass.getJavaClass() + " runner=" + runner); - // TODO: Move the initialization code to a better place. + // This is needed to make AndroidJUnit4ClassRunner happy. + InstrumentationRegistry.registerInstance(null, Bundle.EMPTY); + } + + private static boolean sInitialized = false; + + private static void initOnce() { + if (sInitialized) { + return; + } + sInitialized = true; + + // We haven't initialized liblog yet, so directly write to System.out here. + RavenwoodCommonUtils.log(TAG, "initOnce()"); + + // Redirect stdout/stdin to liblog. + RuntimeInit.redirectLogStreams(); // This will let AndroidJUnit4 use the original runner. System.setProperty("android.junit.runner", "androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner"); System.setProperty(RAVENWOOD_VERSION_JAVA_SYSPROP, "1"); - - - // This is needed to make AndroidJUnit4ClassRunner happy. - InstrumentationRegistry.registerInstance(null, Bundle.EMPTY); } /** @@ -87,7 +106,7 @@ public class RavenwoodAwareTestRunnerHook { */ public static boolean onBefore(RavenwoodAwareTestRunner runner, Description description, Scope scope, Order order) { - Log.i(TAG, "onBefore: description=" + description + ", " + scope + ", " + order); + Log.v(TAG, "onBefore: description=" + description + ", " + scope + ", " + order); if (scope == Scope.Class && order == Order.First) { // Keep track of the current class. @@ -113,7 +132,7 @@ public class RavenwoodAwareTestRunnerHook { */ public static boolean onAfter(RavenwoodAwareTestRunner runner, Description description, Scope scope, Order order, Throwable th) { - Log.i(TAG, "onAfter: description=" + description + ", " + scope + ", " + order + ", " + th); + Log.v(TAG, "onAfter: description=" + description + ", " + scope + ", " + order + ", " + th); if (scope == Scope.Instance && order == Order.First) { getStats().onTestFinished(sCurrentClassDescription, description, diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java index a2088fd0b77f..0059360a0a29 100644 --- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java +++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java @@ -36,7 +36,6 @@ import android.view.DisplayAdjustments; import androidx.test.platform.app.InstrumentationRegistry; -import com.android.internal.os.RuntimeInit; import com.android.server.LocalServices; import org.junit.runner.Description; @@ -92,8 +91,6 @@ public class RavenwoodRuleImpl { Thread.setDefaultUncaughtExceptionHandler(sUncaughtExceptionHandler); } - RuntimeInit.redirectLogStreams(); - android.os.Process.init$ravenwood(rule.mUid, rule.mPid); android.os.Binder.init$ravenwood(); setSystemProperties(rule.mSystemProperties); diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java index 7d991663f4b1..bfde9cb7099e 100644 --- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java +++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java @@ -23,7 +23,6 @@ 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; @@ -75,7 +74,7 @@ import java.lang.reflect.InvocationTargetException; * (no hooks, etc.) */ public class RavenwoodAwareTestRunner extends Runner implements Filterable, Orderable { - private static final String TAG = "RavenwoodAwareTestRunner"; + public static final String TAG = "Ravenwood"; @Inherited @Target({TYPE}) @@ -142,16 +141,9 @@ public class RavenwoodAwareTestRunner extends Runner implements Filterable, Orde private Description mDescription = null; private Throwable mExceptionInConstructor = null; - /** Simple logging method. */ - private void log(String message) { - RavenwoodCommonUtils.log(TAG, "[" + getTestClass().getJavaClass() + " @" + this + "] " - + message); - } - - private Error logAndFail(String message, Throwable innerException) { - log(message); - log(" Exception=" + innerException); - throw new AssertionError(message, innerException); + private Error logAndFail(String message, Throwable exception) { + Log.e(TAG, message, exception); + throw new AssertionError(message, exception); } public TestClass getTestClass() { @@ -165,6 +157,8 @@ public class RavenwoodAwareTestRunner extends Runner implements Filterable, Orde try { mTestClass = new TestClass(testClass); + onRunnerInitializing(); + /* * If the class has @DisabledOnRavenwood, then we'll delegate to * ClassSkippingTestRunner, which simply skips it. @@ -186,10 +180,8 @@ public class RavenwoodAwareTestRunner extends Runner implements Filterable, Orde realRunnerClass = BlockJUnit4ClassRunner.class; } - onRunnerInitializing(); - try { - log("Initializing the inner runner: " + realRunnerClass); + Log.i(TAG, "Initializing the inner runner: " + realRunnerClass); mRealRunner = instantiateRealRunner(realRunnerClass, testClass); mDescription = mRealRunner.getDescription(); @@ -201,8 +193,7 @@ public class RavenwoodAwareTestRunner extends Runner implements Filterable, Orde } 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)); + Log.e(TAG, "Fatal: Exception detected in constructor", th); mExceptionInConstructor = new RuntimeException("Exception detected in constructor", th); mDescription = Description.createTestDescription(testClass, "Constructor"); @@ -236,8 +227,7 @@ public class RavenwoodAwareTestRunner extends Runner implements Filterable, Orde if (!isOnRavenwood()) { return; } - - log("onRunnerInitializing"); + // DO NOT USE android.util.Log before calling onRunnerInitializing(). RavenwoodAwareTestRunnerHook.onRunnerInitializing(this, mTestClass); @@ -250,7 +240,7 @@ public class RavenwoodAwareTestRunner extends Runner implements Filterable, Orde if (!isOnRavenwood()) { return; } - log("runAnnotatedMethodsOnRavenwood() " + annotationClass.getName()); + Log.v(TAG, "runAnnotatedMethodsOnRavenwood() " + annotationClass.getName()); for (var method : getTestClass().getAnnotatedMethods(annotationClass)) { ensureIsPublicVoidMethod(method.getMethod(), /* isStatic=*/ instance == null); diff --git a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/CursorWindow_host.java b/ravenwood/runtime-helper-src/framework/android/database/CursorWindow_host.java index f38d5653d3a9..e21a9cd71a2d 100644 --- a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/CursorWindow_host.java +++ b/ravenwood/runtime-helper-src/framework/android/database/CursorWindow_host.java @@ -13,9 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.platform.test.ravenwood.nativesubstitution; +package android.database; -import android.database.Cursor; import android.database.sqlite.SQLiteException; import android.os.Parcel; import android.util.Base64; @@ -35,8 +34,8 @@ public class CursorWindow_host { private String mName; private int mColumnNum; private static class Row { - String[] fields; - int[] types; + String[] mFields; + int[] mTypes; } private final List<Row> mRows = new ArrayList<>(); @@ -69,9 +68,9 @@ public class CursorWindow_host { public static boolean nativeAllocRow(long windowPtr) { CursorWindow_host instance = sInstances.get(windowPtr); Row row = new Row(); - row.fields = new String[instance.mColumnNum]; - row.types = new int[instance.mColumnNum]; - Arrays.fill(row.types, Cursor.FIELD_TYPE_NULL); + row.mFields = new String[instance.mColumnNum]; + row.mTypes = new int[instance.mColumnNum]; + Arrays.fill(row.mTypes, Cursor.FIELD_TYPE_NULL); instance.mRows.add(row); return true; } @@ -82,8 +81,8 @@ public class CursorWindow_host { return false; } Row r = instance.mRows.get(row); - r.fields[column] = value; - r.types[column] = type; + r.mFields[column] = value; + r.mTypes[column] = type; return true; } @@ -93,7 +92,7 @@ public class CursorWindow_host { return Cursor.FIELD_TYPE_NULL; } - return instance.mRows.get(row).types[column]; + return instance.mRows.get(row).mTypes[column]; } public static boolean nativePutString(long windowPtr, String value, @@ -107,7 +106,7 @@ public class CursorWindow_host { return null; } - return instance.mRows.get(row).fields[column]; + return instance.mRows.get(row).mFields[column]; } public static boolean nativePutLong(long windowPtr, long value, int row, int column) { @@ -170,8 +169,8 @@ public class CursorWindow_host { parcel.writeInt(window.mColumnNum); parcel.writeInt(window.mRows.size()); for (int row = 0; row < window.mRows.size(); row++) { - parcel.writeStringArray(window.mRows.get(row).fields); - parcel.writeIntArray(window.mRows.get(row).types); + parcel.writeStringArray(window.mRows.get(row).mFields); + parcel.writeIntArray(window.mRows.get(row).mTypes); } } @@ -183,8 +182,8 @@ public class CursorWindow_host { int rowCount = parcel.readInt(); for (int row = 0; row < rowCount; row++) { Row r = new Row(); - r.fields = parcel.createStringArray(); - r.types = parcel.createIntArray(); + r.mFields = parcel.createStringArray(); + r.mTypes = parcel.createIntArray(); window.mRows.add(r); } return windowPtr; diff --git a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/MessageQueue_host.java b/ravenwood/runtime-helper-src/framework/android/os/MessageQueue_host.java index 5e81124b6e70..1b63adc4319f 100644 --- a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/MessageQueue_host.java +++ b/ravenwood/runtime-helper-src/framework/android/os/MessageQueue_host.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.platform.test.ravenwood.nativesubstitution; +package android.os; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; diff --git a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/Parcel_host.java b/ravenwood/runtime-helper-src/framework/android/os/Parcel_host.java index cb00b3e758fa..720f1d227326 100644 --- a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/Parcel_host.java +++ b/ravenwood/runtime-helper-src/framework/android/os/Parcel_host.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.platform.test.ravenwood.nativesubstitution; +package android.os; import android.system.ErrnoException; import android.system.Os; @@ -527,4 +527,4 @@ public class Parcel_host { } return false; } -}
\ No newline at end of file +} diff --git a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/SystemProperties_host.java b/ravenwood/runtime-helper-src/framework/android/os/SystemProperties_host.java index e7479d313918..b09bf3119cfa 100644 --- a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/SystemProperties_host.java +++ b/ravenwood/runtime-helper-src/framework/android/os/SystemProperties_host.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.platform.test.ravenwood.nativesubstitution; +package android.os; import android.util.SparseArray; @@ -36,9 +36,6 @@ public class SystemProperties_host { /** Predicate tested to determine if a given key can be written. */ @GuardedBy("sLock") private static Predicate<String> sKeyWritablePredicate; - /** Callback to trigger when values are changed */ - @GuardedBy("sLock") - private static Runnable sChangeCallback; /** * Reverse mapping that provides a way back to an original key from the @@ -48,7 +45,7 @@ public class SystemProperties_host { private static SparseArray<String> sKeyHandles = new SparseArray<>(); /** - * Basically the same as {@link #native_init$ravenwood}, but it'll only run if no values are + * Basically the same as {@link #init$ravenwood}, but it'll only run if no values are * set yet. */ public static void initializeIfNeeded(Map<String, String> values, @@ -57,30 +54,32 @@ public class SystemProperties_host { if (sValues != null) { return; // Already initialized. } - native_init$ravenwood(values, keyReadablePredicate, keyWritablePredicate, - () -> {}); + init$ravenwood(values, keyReadablePredicate, keyWritablePredicate); } } - public static void native_init$ravenwood(Map<String, String> values, - Predicate<String> keyReadablePredicate, Predicate<String> keyWritablePredicate, - Runnable changeCallback) { + public static void init$ravenwood(Map<String, String> values, + Predicate<String> keyReadablePredicate, Predicate<String> keyWritablePredicate) { synchronized (sLock) { sValues = Objects.requireNonNull(values); sKeyReadablePredicate = Objects.requireNonNull(keyReadablePredicate); sKeyWritablePredicate = Objects.requireNonNull(keyWritablePredicate); - sChangeCallback = Objects.requireNonNull(changeCallback); sKeyHandles.clear(); + synchronized (SystemProperties.sChangeCallbacks) { + SystemProperties.sChangeCallbacks.clear(); + } } } - public static void native_reset$ravenwood() { + public static void reset$ravenwood() { synchronized (sLock) { sValues = null; sKeyReadablePredicate = null; sKeyWritablePredicate = null; - sChangeCallback = null; sKeyHandles.clear(); + synchronized (SystemProperties.sChangeCallbacks) { + SystemProperties.sChangeCallbacks.clear(); + } } } @@ -101,7 +100,7 @@ public class SystemProperties_host { } else { sValues.put(key, val); } - sChangeCallback.run(); + SystemProperties.callChangeCallbacks(); } } @@ -183,7 +182,7 @@ public class SystemProperties_host { // Report through callback always registered via init above synchronized (sLock) { Preconditions.requireNonNullViaRavenwoodRule(sValues); - sChangeCallback.run(); + SystemProperties.callChangeCallbacks(); } } diff --git a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/EventLog_host.java b/ravenwood/runtime-helper-src/framework/android/util/EventLog_host.java index 55d4ffb41e78..878a0ff57a1d 100644 --- a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/EventLog_host.java +++ b/ravenwood/runtime-helper-src/framework/android/util/EventLog_host.java @@ -13,12 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.platform.test.ravenwood.nativesubstitution; +package android.util; import com.android.internal.os.RuntimeInit; import java.io.PrintStream; -import java.util.Collection; public class EventLog_host { public static int writeEvent(int tag, int value) { @@ -58,15 +57,6 @@ public class EventLog_host { return sb.length(); } - public static void readEvents(int[] tags, Collection<android.util.EventLog.Event> output) { - throw new UnsupportedOperationException(); - } - - public static void readEventsOnWrapping(int[] tags, long timestamp, - Collection<android.util.EventLog.Event> output) { - throw new UnsupportedOperationException(); - } - /** * Return the "real" {@code System.out} if it's been swapped by {@code RavenwoodRuleImpl}, so * that we don't end up in a recursive loop. diff --git a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/Log_host.java b/ravenwood/runtime-helper-src/framework/android/util/Log_host.java index f301b9c46b0e..d232ef2076be 100644 --- a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/Log_host.java +++ b/ravenwood/runtime-helper-src/framework/android/util/Log_host.java @@ -13,9 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.platform.test.ravenwood.nativesubstitution; +package android.util; -import android.util.Log; import android.util.Log.Level; import com.android.internal.os.RuntimeInit; @@ -44,7 +43,7 @@ public class Log_host { case Log.LOG_ID_SYSTEM: buffer = "system"; break; case Log.LOG_ID_CRASH: buffer = "crash"; break; default: buffer = "buf:" + bufID; break; - }; + } final String prio; switch (priority) { @@ -55,7 +54,7 @@ public class Log_host { case Log.ERROR: prio = "E"; break; case Log.ASSERT: prio = "A"; break; default: prio = "prio:" + priority; break; - }; + } for (String s : msg.split("\\n")) { getRealOut().println(String.format("logd: [%s] %s %s: %s", buffer, prio, tag, s)); diff --git a/ravenwood/runtime-helper-src/framework/com/android/internal/os/LongArrayContainer_host.java b/ravenwood/runtime-helper-src/framework/com/android/internal/os/LongArrayContainer_host.java new file mode 100644 index 000000000000..c18c307ad1e3 --- /dev/null +++ b/ravenwood/runtime-helper-src/framework/com/android/internal/os/LongArrayContainer_host.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 com.android.internal.os; + +import java.util.Arrays; +import java.util.HashMap; + +public class LongArrayContainer_host { + private static final HashMap<Long, long[]> sInstances = new HashMap<>(); + private static long sNextId = 1; + + public static long native_init(int arrayLength) { + long[] array = new long[arrayLength]; + long instanceId = sNextId++; + sInstances.put(instanceId, array); + return instanceId; + } + + static long[] getInstance(long instanceId) { + return sInstances.get(instanceId); + } + + public static void native_setValues(long instanceId, long[] values) { + System.arraycopy(values, 0, getInstance(instanceId), 0, values.length); + } + + public static void native_getValues(long instanceId, long[] values) { + System.arraycopy(getInstance(instanceId), 0, values, 0, values.length); + } + + public static boolean native_combineValues(long instanceId, long[] array, int[] indexMap) { + long[] values = getInstance(instanceId); + + boolean nonZero = false; + Arrays.fill(array, 0); + + for (int i = 0; i < values.length; i++) { + int index = indexMap[i]; + if (index < 0 || index >= array.length) { + throw new IndexOutOfBoundsException("Index " + index + " is out of bounds: [0, " + + (array.length - 1) + "]"); + } + if (values[i] != 0) { + array[index] += values[i]; + nonZero = true; + } + } + return nonZero; + } +} diff --git a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/LongArrayMultiStateCounter_host.java b/ravenwood/runtime-helper-src/framework/com/android/internal/os/LongArrayMultiStateCounter_host.java index 0f65544f8b66..9ce8ea8e16ef 100644 --- a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/LongArrayMultiStateCounter_host.java +++ b/ravenwood/runtime-helper-src/framework/com/android/internal/os/LongArrayMultiStateCounter_host.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.platform.test.ravenwood.nativesubstitution; +package com.android.internal.os; import android.os.BadParcelableException; import android.os.Parcel; @@ -28,7 +28,7 @@ import java.util.HashMap; public class LongArrayMultiStateCounter_host { /** - * A reimplementation of {@link com.android.internal.os.LongArrayMultiStateCounter}, only in + * A reimplementation of {@link LongArrayMultiStateCounter}, only in * Java instead of native. The majority of the code (in C++) can be found in * /frameworks/native/libs/battery/MultiStateCounter.h */ @@ -257,50 +257,6 @@ public class LongArrayMultiStateCounter_host { } } - public static class LongArrayContainer_host { - private static final HashMap<Long, long[]> sInstances = new HashMap<>(); - private static long sNextId = 1; - - public static long native_init(int arrayLength) { - long[] array = new long[arrayLength]; - long instanceId = sNextId++; - sInstances.put(instanceId, array); - return instanceId; - } - - static long[] getInstance(long instanceId) { - return sInstances.get(instanceId); - } - - public static void native_setValues(long instanceId, long[] values) { - System.arraycopy(values, 0, getInstance(instanceId), 0, values.length); - } - - public static void native_getValues(long instanceId, long[] values) { - System.arraycopy(getInstance(instanceId), 0, values, 0, values.length); - } - - public static boolean native_combineValues(long instanceId, long[] array, int[] indexMap) { - long[] values = getInstance(instanceId); - - boolean nonZero = false; - Arrays.fill(array, 0); - - for (int i = 0; i < values.length; i++) { - int index = indexMap[i]; - if (index < 0 || index >= array.length) { - throw new IndexOutOfBoundsException("Index " + index + " is out of bounds: [0, " - + (array.length - 1) + "]"); - } - if (values[i] != 0) { - array[index] += values[i]; - nonZero = true; - } - } - return nonZero; - } - } - private static final HashMap<Long, LongArrayMultiStateCounterRavenwood> sInstances = new HashMap<>(); private static long sNextId = 1; diff --git a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/LongMultiStateCounter_host.java b/ravenwood/runtime-helper-src/framework/com/android/internal/os/LongMultiStateCounter_host.java index 9486651ce48d..1d95aa143549 100644 --- a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/LongMultiStateCounter_host.java +++ b/ravenwood/runtime-helper-src/framework/com/android/internal/os/LongMultiStateCounter_host.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.platform.test.ravenwood.nativesubstitution; +package com.android.internal.os; import android.os.BadParcelableException; import android.os.Parcel; diff --git a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/RavenwoodEnvironment_host.java b/ravenwood/runtime-helper-src/framework/com/android/internal/ravenwood/RavenwoodEnvironment_host.java index 58f6bbb5baf5..3bf116da19b8 100644 --- a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/RavenwoodEnvironment_host.java +++ b/ravenwood/runtime-helper-src/framework/com/android/internal/ravenwood/RavenwoodEnvironment_host.java @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.platform.test.ravenwood.nativesubstitution; +package com.android.internal.ravenwood; +import android.os.SystemProperties_host; import android.platform.test.ravenwood.RavenwoodSystemProperties; import android.util.Log; -import com.android.internal.ravenwood.RavenwoodEnvironment; import com.android.ravenwood.common.JvmWorkaround; import com.android.ravenwood.common.RavenwoodCommonUtils; @@ -36,7 +36,7 @@ public class RavenwoodEnvironment_host { /** * Called from {@link RavenwoodEnvironment#ensureRavenwoodInitialized()}. */ - public static void nativeEnsureRavenwoodInitialized() { + public static void ensureRavenwoodInitialized() { // TODO Unify it with the initialization code in RavenwoodAwareTestRunnerHook. @@ -63,14 +63,14 @@ public class RavenwoodEnvironment_host { /** * Called from {@link RavenwoodEnvironment#getRavenwoodRuntimePath()}. */ - public static String nativeGetRavenwoodRuntimePath(RavenwoodEnvironment env) { + public static String getRavenwoodRuntimePath(RavenwoodEnvironment env) { return RavenwoodCommonUtils.getRavenwoodRuntimePath(); } /** * Called from {@link RavenwoodEnvironment#fromAddress(long)}. */ - public static <T> T nativeFromAddress(RavenwoodEnvironment env, long address) { + public static <T> T fromAddress(RavenwoodEnvironment env, long address) { return JvmWorkaround.getInstance().fromAddress(address); } } diff --git a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java index 0f955e772445..c519204d0586 100644 --- a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java +++ b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java @@ -15,6 +15,9 @@ */ package com.android.platform.test.ravenwood.runtimehelper; +import android.system.ErrnoException; +import android.system.Os; + import com.android.ravenwood.common.RavenwoodCommonUtils; import java.io.File; @@ -37,6 +40,14 @@ public class ClassLoadHook { private static final boolean SKIP_LOADING_LIBANDROID = "1".equals(System.getenv( "RAVENWOOD_SKIP_LOADING_LIBANDROID")); + /** + * If set to 1, and if $ANDROID_LOG_TAGS isn't set, we enable the verbose logging. + * + * (See also InitLogging() in http://ac/system/libbase/logging.cpp) + */ + private static final boolean RAVENWOOD_VERBOSE_LOGGING = "1".equals(System.getenv( + "RAVENWOOD_VERBOSE")); + public static final String CORE_NATIVE_CLASSES = "core_native_classes"; public static final String ICU_DATA_PATH = "icu.data.path"; public static final String KEYBOARD_PATHS = "keyboard_paths"; @@ -123,6 +134,15 @@ public class ClassLoadHook { return; } + if (RAVENWOOD_VERBOSE_LOGGING) { + log("Force enabling verbose logging"); + try { + Os.setenv("ANDROID_LOG_TAGS", "*:v", true); + } catch (ErrnoException e) { + // Shouldn't happen. + } + } + // Make sure these properties are not set. ensurePropertyNotSet(CORE_NATIVE_CLASSES); ensurePropertyNotSet(ICU_DATA_PATH); diff --git a/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java b/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java index a5c0b54a8637..c94ef31a5e5e 100644 --- a/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java +++ b/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java @@ -93,4 +93,8 @@ public final class Os { throw new ErrnoException("pread", OsConstants.EIO, e); } } + + public static void setenv(String name, String value, boolean overwrite) throws ErrnoException { + RavenwoodRuntimeNative.setenv(name, value, overwrite); + } } diff --git a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java index 0d8408c12033..ad80d92686ab 100644 --- a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java +++ b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java @@ -53,6 +53,9 @@ public class RavenwoodRuntimeNative { private static native int nOpen(String path, int flags, int mode) throws ErrnoException; + public static native void setenv(String name, String value, boolean overwrite) + throws ErrnoException; + public static long lseek(FileDescriptor fd, long offset, int whence) throws ErrnoException { return nLseek(JvmWorkaround.getInstance().getFdInt(fd), offset, whence); } diff --git a/ravenwood/runtime-jni/ravenwood_runtime.cpp b/ravenwood/runtime-jni/ravenwood_runtime.cpp index f5cb019f4e7e..c255be5f61aa 100644 --- a/ravenwood/runtime-jni/ravenwood_runtime.cpp +++ b/ravenwood/runtime-jni/ravenwood_runtime.cpp @@ -214,6 +214,19 @@ static jint Linux_open(JNIEnv* env, jobject, jstring javaPath, jint flags, jint return throwIfMinusOne(env, "open", TEMP_FAILURE_RETRY(open(path.c_str(), flags, mode))); } +static void Linux_setenv(JNIEnv* env, jobject, jstring javaName, jstring javaValue, + jboolean overwrite) { + ScopedRealUtf8Chars name(env, javaName); + if (name.c_str() == NULL) { + jniThrowNullPointerException(env); + } + ScopedRealUtf8Chars value(env, javaValue); + if (value.c_str() == NULL) { + jniThrowNullPointerException(env); + } + throwIfMinusOne(env, "setenv", setenv(name.c_str(), value.c_str(), overwrite ? 1 : 0)); +} + // ---- Registration ---- static const JNINativeMethod sMethods[] = @@ -227,6 +240,7 @@ static const JNINativeMethod sMethods[] = { "lstat", "(Ljava/lang/String;)Landroid/system/StructStat;", (void*)Linux_lstat }, { "stat", "(Ljava/lang/String;)Landroid/system/StructStat;", (void*)Linux_stat }, { "nOpen", "(Ljava/lang/String;II)I", (void*)Linux_open }, + { "setenv", "(Ljava/lang/String;Ljava/lang/String;Z)V", (void*)Linux_setenv }, }; extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) diff --git a/ravenwood/texts/ravenwood-standard-options.txt b/ravenwood/texts/ravenwood-standard-options.txt index 952ab8244e64..3ec3e3ce2946 100644 --- a/ravenwood/texts/ravenwood-standard-options.txt +++ b/ravenwood/texts/ravenwood-standard-options.txt @@ -32,8 +32,11 @@ --substitute-annotation android.ravenwood.annotation.RavenwoodReplace ---native-substitute-annotation - android.ravenwood.annotation.RavenwoodNativeSubstitutionClass +--redirect-annotation + android.ravenwood.annotation.RavenwoodRedirect + +--redirection-class-annotation + android.ravenwood.annotation.RavenwoodRedirectionClass --class-load-hook-annotation android.ravenwood.annotation.RavenwoodClassLoadHook diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig index ffa48411a9a8..ee3bbcaf711d 100644 --- a/services/accessibility/accessibility.aconfig +++ b/services/accessibility/accessibility.aconfig @@ -216,6 +216,16 @@ flag { } flag { + name: "reset_input_dispatcher_before_first_touch_exploration" + namespace: "accessibility" + description: "Resets InputDispatcher state by sending ACTION_CANCEL before the first TouchExploration hover events" + bug: "364408887" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "scan_packages_without_lock" namespace: "accessibility" description: "Scans packages for accessibility service/activity info without holding the A11yMS lock" @@ -228,6 +238,7 @@ flag { description: "Sends accessibility events in TouchExplorer#onAccessibilityEvent based on internal state to keep it consistent. This reduces test flakiness." bug: "295575684" } + flag { name: "send_hover_events_based_on_event_stream" namespace: "accessibility" diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java index 04b42e49fad9..0ed239e442e7 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java @@ -1613,6 +1613,19 @@ public class TouchExplorer extends BaseEventStreamTransformation dispatchGesture(gestureEvent); } if (!mEvents.isEmpty() && !mRawEvents.isEmpty()) { + if (Flags.resetInputDispatcherBeforeFirstTouchExploration() + && !mState.hasResetInputDispatcherState()) { + // Cancel any possible ongoing touch gesture from before touch exploration + // started. This clears out the InputDispatcher event stream state so that it + // is ready to accept new injected HOVER events. + mDispatcher.sendMotionEvent( + mEvents.get(0), + ACTION_CANCEL, + mRawEvents.get(0), + mPointerIdBits, + mPolicyFlags); + setHasResetInputDispatcherState(true); + } // Deliver a down event. mDispatcher.sendMotionEvent( mEvents.get(0), @@ -1773,4 +1786,9 @@ public class TouchExplorer extends BaseEventStreamTransformation + ", mDraggingPointerId: " + mDraggingPointerId + " }"; } + + @VisibleForTesting + void setHasResetInputDispatcherState(boolean value) { + mState.setHasResetInputDispatcherState(value); + } } diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java index e13994e75690..f15b8eec3f6b 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java @@ -86,6 +86,7 @@ public class TouchState { private MotionEvent mLastInjectedHoverEvent; // The last injected hover event used for performing clicks. private MotionEvent mLastInjectedHoverEventForClick; + private boolean mHasResetInputDispatcherState; // The time of the last injected down. private long mLastInjectedDownEventTime; // Keep track of which pointers sent to the system are down. @@ -361,6 +362,14 @@ public class TouchState { return mLastInjectedDownEventTime; } + boolean hasResetInputDispatcherState() { + return mHasResetInputDispatcherState; + } + + void setHasResetInputDispatcherState(boolean value) { + mHasResetInputDispatcherState = value; + } + public int getLastTouchedWindowId() { return mLastTouchedWindowId; } diff --git a/services/appfunctions/java/com/android/server/appfunctions/SyncAppSearchCallHelper.java b/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSession.java index e56f81f0034a..eba628dc1fba 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/SyncAppSearchCallHelper.java +++ b/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSession.java @@ -20,11 +20,16 @@ import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANA import android.annotation.FlaggedApi; import android.annotation.NonNull; +import android.app.appsearch.AppSearchBatchResult; 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.PutDocumentsRequest; +import android.app.appsearch.SearchResult; +import android.app.appsearch.SearchResults; +import android.app.appsearch.SearchSpec; import android.app.appsearch.SetSchemaRequest; import android.app.appsearch.SetSchemaResponse; import android.util.Slog; @@ -33,22 +38,20 @@ import com.android.internal.infra.AndroidFuture; import java.io.Closeable; import java.io.IOException; +import java.util.List; import java.util.Objects; import java.util.concurrent.Executor; /** - * Helper class for interacting with a system server local appsearch session asynchronously. - * - * <p>Converts the AppSearch Callback API to {@link AndroidFuture}. + * A future API wrapper of {@link AppSearchSession} APIs. */ @FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER) -public class SyncAppSearchCallHelper implements Closeable { - private static final String TAG = SyncAppSearchCallHelper.class.getSimpleName(); +public class FutureAppSearchSession implements Closeable { + private static final String TAG = FutureAppSearchSession.class.getSimpleName(); private final Executor mExecutor; - private final AppSearchManager mAppSearchManager; private final AndroidFuture<AppSearchResult<AppSearchSession>> mSettableSessionFuture; - public SyncAppSearchCallHelper( + public FutureAppSearchSession( @NonNull AppSearchManager appSearchManager, @NonNull Executor executor, @NonNull SearchContext appSearchContext) { @@ -57,22 +60,21 @@ public class SyncAppSearchCallHelper implements Closeable { Objects.requireNonNull(appSearchContext); mExecutor = executor; - mAppSearchManager = appSearchManager; mSettableSessionFuture = new AndroidFuture<>(); - mAppSearchManager.createSearchSession( + appSearchManager.createSearchSession( appSearchContext, mExecutor, mSettableSessionFuture::complete); } /** Converts a failed app search result codes into an exception. */ @NonNull - private static Exception failedResultToException(@NonNull AppSearchResult appSearchResult) { + 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()); + 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()); }; } @@ -91,7 +93,7 @@ public class SyncAppSearchCallHelper implements Closeable { /** Gets the schema for a given app search session. */ public AndroidFuture<GetSchemaResponse> getSchema() { return getSessionAsync() - .thenComposeAsync( + .thenCompose( session -> { AndroidFuture<AppSearchResult<GetSchemaResponse>> settableSchemaResponse = new AndroidFuture<>(); @@ -105,14 +107,13 @@ public class SyncAppSearchCallHelper implements Closeable { failedResultToException(result)); } }); - }, - mExecutor); + }); } /** Sets the schema for a given app search session. */ public AndroidFuture<SetSchemaResponse> setSchema(@NonNull SetSchemaRequest setSchemaRequest) { return getSessionAsync() - .thenComposeAsync( + .thenCompose( session -> { AndroidFuture<AppSearchResult<SetSchemaResponse>> settableSchemaResponse = new AndroidFuture<>(); @@ -130,8 +131,32 @@ public class SyncAppSearchCallHelper implements Closeable { failedResultToException(result)); } }); - }, - mExecutor); + }); + } + + /** Indexes documents into the AppSearchSession database. */ + public AndroidFuture<AppSearchBatchResult<String, Void>> put( + @NonNull PutDocumentsRequest putDocumentsRequest) { + return getSessionAsync().thenCompose( + session -> { + AndroidFuture<AppSearchBatchResult<String, Void>> batchResultFuture = + new AndroidFuture<>(); + + session.put(putDocumentsRequest, mExecutor, batchResultFuture::complete); + return batchResultFuture; + }); + } + + /** + * Retrieves documents from the open AppSearchSession that match a given query string and type + * of search provided. + */ + public AndroidFuture<FutureSearchResults> search( + @NonNull String queryExpression, + @NonNull SearchSpec searchSpec) { + return getSessionAsync().thenApply( + session -> session.search(queryExpression, searchSpec)) + .thenApply(result -> new FutureSearchResults(result, mExecutor)); } @Override @@ -142,4 +167,32 @@ public class SyncAppSearchCallHelper implements Closeable { Slog.e(TAG, "Failed to close app search session", ex); } } + + /** A future API wrapper of {@link android.app.appsearch.SearchResults}. */ + public static class FutureSearchResults { + private final SearchResults mSearchResults; + private final Executor mExecutor; + + public FutureSearchResults(@NonNull SearchResults searchResults, + @NonNull Executor executor) { + mSearchResults = Objects.requireNonNull(searchResults); + mExecutor = Objects.requireNonNull(executor); + } + + public AndroidFuture<List<SearchResult>> getNextPage() { + AndroidFuture<AppSearchResult<List<SearchResult>>> nextPageFuture = + new AndroidFuture<>(); + + mSearchResults.getNextPage(mExecutor, nextPageFuture::complete); + return nextPageFuture.thenApply(result -> { + if (result.isSuccess()) { + return result.getResultValue(); + } else { + throw new RuntimeException( + failedResultToException(result)); + } + }); + } + + } } diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java index 30683985f047..b53bf984880d 100644 --- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java +++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java @@ -220,6 +220,15 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku // See {@link Provider#pendingDeletedWidgetIds}. private static final String PENDING_DELETED_IDS_ATTR = "pending_deleted_ids"; + // Hard limit of number of hosts an app can create, note that the app that hosts the widgets + // can have multiple instances of {@link AppWidgetHost}, typically in respect to different + // surfaces in the host app. + // @see AppWidgetHost + // @see AppWidgetHost#mHostId + private static final int MAX_NUMBER_OF_HOSTS_PER_PACKAGE = 20; + // Hard limit of number of widgets can be pinned by a host. + private static final int MAX_NUMBER_OF_WIDGETS_PER_HOST = 200; + // Handles user and package related broadcasts. // See {@link #registerBroadcastReceiver} private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @@ -2284,7 +2293,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku if (host != null) { return host; } - + ensureHostCountBeforeAddLocked(id); host = new Host(); host.id = id; mHosts.add(host); @@ -2292,6 +2301,24 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku return host; } + /** + * Ensures that the number of hosts for a package is less than the maximum number of hosts per + * package. If the number of hosts is greater than the maximum number of hosts per package, then + * removes the oldest host. + */ + private void ensureHostCountBeforeAddLocked(@NonNull final HostId hostId) { + final List<Host> hosts = new ArrayList<>(); + for (Host host : mHosts) { + if (host.id.uid == hostId.uid + && host.id.packageName.equals(hostId.packageName)) { + hosts.add(host); + } + } + while (hosts.size() >= MAX_NUMBER_OF_HOSTS_PER_PACKAGE) { + deleteHostLocked(hosts.remove(0)); + } + } + private void deleteHostLocked(Host host) { if (DEBUG) { Slog.i(TAG, "deleteHostLocked() " + host); @@ -2377,6 +2404,11 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } @Override + public void onNullBinding(ComponentName name) { + mContext.unbindService(this); + } + + @Override public void onServiceDisconnected(ComponentName name) { // Do nothing } @@ -2524,6 +2556,11 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } @Override + public void onNullBinding(ComponentName name) { + mContext.unbindService(this); + } + + @Override public void onServiceDisconnected(android.content.ComponentName name) { // Do nothing } @@ -3582,12 +3619,33 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku if (DEBUG) { Slog.i(TAG, "addWidgetLocked() " + widget); } + ensureWidgetCountBeforeAddLocked(widget); mWidgets.add(widget); onWidgetProviderAddedOrChangedLocked(widget); } /** + * Ensures that the widget count for the widget's host is not greater than the maximum + * number of widgets per host. If the count is greater than the maximum, removes oldest widgets + * from the host until the count is less than or equal to the maximum. + */ + private void ensureWidgetCountBeforeAddLocked(@NonNull final Widget widget) { + if (widget.host == null || widget.host.id == null) { + return; + } + final List<Widget> widgetsInSameHost = new ArrayList<>(); + for (Widget w : mWidgets) { + if (w.host != null && widget.host.id.equals(w.host.id)) { + widgetsInSameHost.add(w); + } + } + while (widgetsInSameHost.size() >= MAX_NUMBER_OF_WIDGETS_PER_HOST) { + removeWidgetLocked(widgetsInSameHost.remove(0)); + } + } + + /** * Checks if the provider is assigned and updates the mWidgetPackages to track packages * that have bound widgets. */ diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index d4f729cfbaf6..36665240c16b 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -1516,9 +1516,8 @@ public final class ActiveServices { serviceName, FrameworkStatsLog.SERVICE_STATE_CHANGED__STATE__START); mAm.mBatteryStatsService.noteServiceStartRunning(uid, packageName, serviceName); final ProcessRecord hostApp = r.app; - final boolean wasStopped = hostApp == null ? wasStopped(r) : false; - final boolean firstLaunch = - hostApp == null ? !mAm.wasPackageEverLaunched(r.packageName, r.userId) : false; + final boolean wasStopped = hostApp == null ? r.appInfo.isStopped() : false; + final boolean firstLaunch = hostApp == null ? r.appInfo.isNotLaunched() : false; String error = bringUpServiceLocked(r, service.getFlags(), callerFg, false /* whileRestarting */, @@ -4308,9 +4307,8 @@ public final class ActiveServices { true, UNKNOWN_ADJ); } - final boolean wasStopped = hostApp == null ? wasStopped(s) : false; - final boolean firstLaunch = - hostApp == null ? !mAm.wasPackageEverLaunched(s.packageName, s.userId) : false; + final boolean wasStopped = hostApp == null ? s.appInfo.isStopped() : false; + final boolean firstLaunch = hostApp == null ? s.appInfo.isNotLaunched() : false; boolean needOomAdj = false; if (c.hasFlag(Context.BIND_AUTO_CREATE)) { @@ -9350,8 +9348,4 @@ public final class ActiveServices { return mCachedDeviceProvisioningPackage != null && mCachedDeviceProvisioningPackage.equals(packageName); } - - private boolean wasStopped(ServiceRecord serviceRecord) { - return (serviceRecord.appInfo.flags & ApplicationInfo.FLAG_STOPPED) != 0; - } } diff --git a/services/core/java/com/android/server/am/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java index 1b00cec90bc4..6aadcdc74870 100644 --- a/services/core/java/com/android/server/am/AppStartInfoTracker.java +++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java @@ -33,6 +33,7 @@ import android.content.IntentFilter; import android.content.pm.PackageManager; import android.icu.text.SimpleDateFormat; import android.os.Binder; +import android.os.Debug; import android.os.FileUtils; import android.os.Handler; import android.os.IBinder.DeathRecipient; @@ -495,6 +496,10 @@ public final class AppStartInfoTracker { private void addBaseFieldsFromProcessRecord(ApplicationStartInfo start, ProcessRecord app) { if (app == null) { + if (DEBUG) { + Slog.w(TAG, + "app is null in addBaseFieldsFromProcessRecord: " + Debug.getCallers(4)); + } return; } final int definingUid = app.getHostingRecord() != null diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java index 8fe33d18152c..9e4666cca140 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java @@ -1005,13 +1005,10 @@ class BroadcastQueueModernImpl extends BroadcastQueue { final ApplicationInfo info = ((ResolveInfo) receiver).activityInfo.applicationInfo; final ComponentName component = ((ResolveInfo) receiver).activityInfo.getComponentName(); - if ((info.flags & ApplicationInfo.FLAG_STOPPED) != 0) { - queue.setActiveWasStopped(true); - } - final int intentFlags = r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND; - final boolean firstLaunch = !mService.wasPackageEverLaunched(info.packageName, r.userId); - queue.setActiveFirstLaunch(firstLaunch); + queue.setActiveWasStopped(info.isStopped()); + queue.setActiveFirstLaunch(info.isNotLaunched()); + final int intentFlags = r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND; final HostingRecord hostingRecord = new HostingRecord(HostingRecord.HOSTING_TYPE_BROADCAST, component, r.intent.getAction(), r.getHostingRecordTriggerType()); final boolean isActivityCapable = (r.options != null diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java index 13145214c1c1..da408266bfac 100644 --- a/services/core/java/com/android/server/am/ContentProviderHelper.java +++ b/services/core/java/com/android/server/am/ContentProviderHelper.java @@ -554,13 +554,11 @@ public class ContentProviderHelper { callingProcessState, proc.mState.getCurProcState(), false, 0L); } else { - final boolean stopped = - (cpr.appInfo.flags & ApplicationInfo.FLAG_STOPPED) != 0; + final boolean stopped = cpr.appInfo.isStopped(); final int packageState = stopped ? PROVIDER_ACQUISITION_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_STOPPED : PROVIDER_ACQUISITION_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL; - final boolean firstLaunch = !mService.wasPackageEverLaunched( - cpi.packageName, userId); + final boolean firstLaunch = cpr.appInfo.isNotLaunched(); checkTime(startTime, "getContentProviderImpl: before start process"); proc = mService.startProcessLocked( cpi.processName, cpr.appInfo, false, 0, diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index bb0c24b4f8c6..5b4e57350c40 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -3394,24 +3394,33 @@ public final class ProcessList { hostingRecord.getDefiningUid(), hostingRecord.getDefiningProcessName()); final ProcessStateRecord state = r.mState; - final boolean wasStopped = (info.flags & ApplicationInfo.FLAG_STOPPED) != 0; + final boolean wasStopped = info.isStopped(); // Check if we should mark the processrecord for first launch after force-stopping if (wasStopped) { + boolean wasEverLaunched; + if (android.app.Flags.useAppInfoNotLaunched()) { + wasEverLaunched = !info.isNotLaunched(); + } else { + wasEverLaunched = mService.getPackageManagerInternal() + .wasPackageEverLaunched(r.getApplicationInfo().packageName, r.userId); + } // Check if the hosting record is for an activity or not. Since the stopped // state tracking is handled differently to avoid WM calling back into AM, // store the state in the correct record if (hostingRecord.isTypeActivity()) { - final boolean wasPackageEverLaunched = mService - .wasPackageEverLaunched(r.getApplicationInfo().packageName, r.userId); // If the package was launched in the past but is currently stopped, only then // should it be considered as force-stopped. - @WindowProcessController.StoppedState int stoppedState = wasPackageEverLaunched + @WindowProcessController.StoppedState int stoppedState = wasEverLaunched ? STOPPED_STATE_FORCE_STOPPED : STOPPED_STATE_FIRST_LAUNCH; r.getWindowProcessController().setStoppedState(stoppedState); } else { - r.setWasForceStopped(true); - // first launch is computed just before logging, for non-activity types + if (android.app.Flags.useAppInfoNotLaunched()) { + // If it was launched before, then it must be a force-stop + r.setWasForceStopped(wasEverLaunched); + } else { + r.setWasForceStopped(true); + } } } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 4e24cf38fe73..6daf0d0b7d3b 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -285,7 +285,6 @@ import java.util.Objects; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.CancellationException; -import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.Executors; @@ -411,6 +410,9 @@ public class AudioService extends IAudioService.Stub /** The controller for the volume UI. */ private final VolumeController mVolumeController = new VolumeController(); + /** Used only for testing to enable/disable the long press timeout volume actions. */ + private final AtomicBoolean mVolumeControllerLongPressEnabled = new AtomicBoolean(true); + // sendMsg() flags /** If the msg is already queued, replace it with this one. */ private static final int SENDMSG_REPLACE = 0; @@ -12554,6 +12556,15 @@ public class AudioService extends IAudioService.Stub if (DEBUG_VOL) Log.d(TAG, "Volume controller visible: " + visible); } + /** @see AudioManager#setVolumeControllerLongPressTimeoutEnabled(boolean) */ + @Override + @android.annotation.EnforcePermission(MODIFY_AUDIO_SETTINGS_PRIVILEGED) + public void setVolumeControllerLongPressTimeoutEnabled(boolean enable) { + super.setVolumeControllerLongPressTimeoutEnabled_enforcePermission(); + mVolumeControllerLongPressEnabled.set(enable); + Log.i(TAG, "Volume controller long press timeout enabled: " + enable); + } + @Override public void setVolumePolicy(VolumePolicy policy) { enforceVolumeController("set volume policy"); @@ -12632,7 +12643,9 @@ public class AudioService extends IAudioService.Stub if ((flags & AudioManager.FLAG_SHOW_UI) != 0 && !mVisible) { // UI is not visible yet, adjustment is ignored if (mNextLongPress < now) { - mNextLongPress = now + mLongPressTimeout; + mNextLongPress = + now + (mVolumeControllerLongPressEnabled.get() ? mLongPressTimeout + : 0); } suppress = true; } else if (mNextLongPress > 0) { // in a long-press @@ -12699,11 +12712,6 @@ public class AudioService extends IAudioService.Stub if (mController == null) return; try { - // TODO: remove this when deprecating STREAM_BLUETOOTH_SCO - if (isStreamBluetoothSco(streamType)) { - // TODO: notify both sco and voice_call about volume changes - streamType = AudioSystem.STREAM_BLUETOOTH_SCO; - } mController.volumeChanged(streamType, flags); } catch (RemoteException e) { Log.w(TAG, "Error calling volumeChanged", e); @@ -14714,6 +14722,7 @@ public class AudioService extends IAudioService.Stub @Override /** @see AudioManager#permissionUpdateBarrier() */ public void permissionUpdateBarrier() { + if (!audioserverPermissions()) return; mAudioSystem.triggerSystemPropertyUpdate(mSysPropListenerNativeHandle); List<Future> snapshot; synchronized (mScheduledPermissionTasks) { diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java index 907e7c639352..86015acc232f 100644 --- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java +++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java @@ -938,7 +938,7 @@ public class AutomaticBrightnessController { setAmbientLux(mFastAmbientLux); if (mLoggingEnabled) { Slog.d(TAG, "updateAmbientLux: " - + ((mFastAmbientLux > mAmbientLux) ? "Brightened" : "Darkened") + ": " + + ((mFastAmbientLux > mPreThresholdLux) ? "Brightened" : "Darkened") + ": " + "mAmbientBrighteningThreshold=" + mAmbientBrighteningThreshold + ", " + "mAmbientDarkeningThreshold=" + mAmbientDarkeningThreshold + ", " + "mAmbientLightRingBuffer=" + mAmbientLightRingBuffer + ", " diff --git a/services/core/java/com/android/server/display/DisplayBrightnessState.java b/services/core/java/com/android/server/display/DisplayBrightnessState.java index 334dda079e7a..01bbd2fb39f8 100644 --- a/services/core/java/com/android/server/display/DisplayBrightnessState.java +++ b/services/core/java/com/android/server/display/DisplayBrightnessState.java @@ -17,6 +17,7 @@ package com.android.server.display; import android.hardware.display.BrightnessInfo; +import android.os.PowerManager; import android.text.TextUtils; import com.android.server.display.brightness.BrightnessEvent; @@ -255,7 +256,7 @@ public final class DisplayBrightnessState { private String mDisplayBrightnessStrategyName; private boolean mShouldUseAutoBrightness; private boolean mIsSlowChange; - private float mMaxBrightness; + private float mMaxBrightness = PowerManager.BRIGHTNESS_MAX; private float mMinBrightness; private float mCustomAnimationRate = CUSTOM_ANIMATION_RATE_NOT_SET; private boolean mShouldUpdateScreenBrightnessSetting; diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 2b732eab67cc..ed16b1472ee5 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -134,6 +134,7 @@ import android.util.ArraySet; import android.util.EventLog; import android.util.IndentingPrintWriter; import android.util.IntArray; +import android.util.MathUtils; import android.util.Pair; import android.util.Slog; import android.util.SparseArray; @@ -1275,6 +1276,9 @@ public final class DisplayManagerService extends SystemService { || isUidPresentOnDisplayInternal(callingUid, displayId)) { return info; } + } else if (displayId == Display.DEFAULT_DISPLAY) { + Slog.e(TAG, "Default display is null for info request from uid " + + callingUid); } return null; } @@ -2223,10 +2227,11 @@ public final class DisplayManagerService extends SystemService { if (display.isValidLocked()) { applyDisplayChangedLocked(display); } - return; + } else { + releaseDisplayAndEmitEvent(display, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED); } - releaseDisplayAndEmitEvent(display, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED); + Slog.i(TAG, "Logical display removed: " + display.getDisplayIdLocked()); } private void releaseDisplayAndEmitEvent(LogicalDisplay display, int event) { @@ -3098,6 +3103,7 @@ public final class DisplayManagerService extends SystemService { /** * Get internal or external viewport. Create it if does not currently exist. + * * @param viewportType - either INTERNAL or EXTERNAL * @return the viewport with the requested type */ @@ -4413,7 +4419,6 @@ public final class DisplayManagerService extends SystemService { } - @Override // Binder call public BrightnessConfiguration getBrightnessConfigurationForUser(int userId) { final String uniqueId; @@ -4492,10 +4497,12 @@ public final class DisplayManagerService extends SystemService { @Override // Binder call public void setBrightness(int displayId, float brightness) { setBrightness_enforcePermission(); - if (!isValidBrightness(brightness)) { - Slog.w(TAG, "Attempted to set invalid brightness" + brightness); + if (Float.isNaN(brightness)) { + Slog.w(TAG, "Attempted to set invalid brightness: " + brightness); return; } + MathUtils.constrain(brightness, PowerManager.BRIGHTNESS_MIN, + PowerManager.BRIGHTNESS_MAX); final long token = Binder.clearCallingIdentity(); try { synchronized (mSyncRoot) { @@ -4791,12 +4798,6 @@ public final class DisplayManagerService extends SystemService { } } - private static boolean isValidBrightness(float brightness) { - return !Float.isNaN(brightness) - && (brightness >= PowerManager.BRIGHTNESS_MIN) - && (brightness <= PowerManager.BRIGHTNESS_MAX); - } - @VisibleForTesting void overrideSensorManager(SensorManager sensorManager) { synchronized (mSyncRoot) { diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index bb2bed7281f7..7c591e3a2c03 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -2222,6 +2222,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call unblockScreenOn(); } mWindowManagerPolicy.screenTurningOn(mDisplayId, mPendingScreenOnUnblocker); + Slog.i(TAG, "Window Manager Policy screenTurningOn complete"); } // Return true if the screen isn't blocked. diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java index 40e9198ea921..cf44ac029c82 100644 --- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java +++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java @@ -73,7 +73,6 @@ abstract class BrightnessClamper<T> { abstract void stop(); protected enum Type { - THERMAL, POWER, WEAR_BEDTIME_MODE, } 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 d3be33f51e5c..9404034cdd34 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 @@ -72,6 +72,8 @@ public class BrightnessClamperController { private final List<DisplayDeviceDataListener> mDisplayDeviceDataListeners = new ArrayList<>(); private final List<StatefulModifier> mStatefulModifiers = new ArrayList<>(); private final List<UserSwitchListener> mUserSwitchListeners = new ArrayList<>(); + private final List<DeviceConfigListener> mDeviceConfigListeners = new ArrayList<>(); + private ModifiersAggregatedState mModifiersAggregatedState = new ModifiersAggregatedState(); private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener; @@ -144,9 +146,14 @@ public class BrightnessClamperController { if (m instanceof UserSwitchListener l) { mUserSwitchListeners.add(l); } + if (m instanceof DeviceConfigListener l) { + mDeviceConfigListeners.add(l); + } }); - mOnPropertiesChangedListener = - properties -> mClampers.forEach(BrightnessClamper::onDeviceConfigChanged); + mOnPropertiesChangedListener = properties -> { + mClampers.forEach(BrightnessClamper::onDeviceConfigChanged); + mDeviceConfigListeners.forEach(DeviceConfigListener::onDeviceConfigChanged); + }; mLightSensorController.configure(data.getAmbientLightSensor(), data.getDisplayId()); start(); } @@ -209,8 +216,6 @@ public class BrightnessClamperController { private int getBrightnessMaxReason() { if (mClamperType == null) { return BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE; - } else if (mClamperType == Type.THERMAL) { - return BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL; } else if (mClamperType == Type.POWER) { return BrightnessInfo.BRIGHTNESS_MAX_REASON_POWER_IC; } else if (mClamperType == Type.WEAR_BEDTIME_MODE) { @@ -225,7 +230,7 @@ public class BrightnessClamperController { * Called when the user switches. */ public void onUserSwitch() { - mUserSwitchListeners.forEach(listener -> listener.onSwitchUser()); + mUserSwitchListeners.forEach(UserSwitchListener::onSwitchUser); } /** @@ -294,11 +299,14 @@ public class BrightnessClamperController { state2.mMaxDesiredHdrRatio) || !BrightnessSynchronizer.floatEquals(state1.mMaxHdrBrightness, state2.mMaxHdrBrightness) - || state1.mSdrHdrRatioSpline != state2.mSdrHdrRatioSpline; + || state1.mSdrHdrRatioSpline != state2.mSdrHdrRatioSpline + || state1.mMaxBrightnessReason != state2.mMaxBrightnessReason + || !BrightnessSynchronizer.floatEquals(state1.mMaxBrightness, + state2.mMaxBrightness); } private void start() { - if (!mClampers.isEmpty()) { + if (!mClampers.isEmpty() || !mDeviceConfigListeners.isEmpty()) { mDeviceConfigParameterProvider.addOnPropertiesChangedListener( mExecutor, mOnPropertiesChangedListener); } @@ -333,8 +341,7 @@ public class BrightnessClamperController { ClamperChangeListener clamperChangeListener, DisplayDeviceData data, DisplayManagerFlags flags, Context context, float currentBrightness) { List<BrightnessClamper<? super DisplayDeviceData>> clampers = new ArrayList<>(); - clampers.add( - new BrightnessThermalClamper(handler, clamperChangeListener, data)); + if (flags.isPowerThrottlingClamperEnabled()) { // Check if power-throttling config is present. PowerThrottlingConfigData configData = data.getPowerThrottlingConfigData(); @@ -354,6 +361,8 @@ public class BrightnessClamperController { Handler handler, ClamperChangeListener listener, DisplayDeviceData data) { List<BrightnessStateModifier> modifiers = new ArrayList<>(); + modifiers.add(new BrightnessThermalModifier(handler, listener, data)); + modifiers.add(new DisplayDimModifier(context)); modifiers.add(new BrightnessLowPowerModeModifier()); if (flags.isEvenDimmerEnabled() && data.mDisplayDeviceConfig.isEvenDimmerAvailable()) { @@ -384,7 +393,7 @@ public class BrightnessClamperController { /** * Config Data for clampers/modifiers */ - public static class DisplayDeviceData implements BrightnessThermalClamper.ThermalData, + public static class DisplayDeviceData implements BrightnessThermalModifier.ThermalData, BrightnessPowerClamper.PowerData, BrightnessWearBedtimeModeClamper.WearBedtimeModeData { @NonNull @@ -498,13 +507,23 @@ public class BrightnessClamperController { } /** + * Modifier should implement this interface in order to receive device config updates + */ + interface DeviceConfigListener { + void onDeviceConfigChanged(); + } + + /** * StatefulModifiers contribute to AggregatedState, that is used to decide if brightness - * adjustement is needed + * adjustment is needed */ public static class ModifiersAggregatedState { float mMaxDesiredHdrRatio = HdrBrightnessModifier.DEFAULT_MAX_HDR_SDR_RATIO; float mMaxHdrBrightness = PowerManager.BRIGHTNESS_MAX; @Nullable Spline mSdrHdrRatioSpline = null; + @BrightnessInfo.BrightnessMaxReason + int mMaxBrightnessReason = BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE; + float mMaxBrightness = PowerManager.BRIGHTNESS_MAX; } } diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessThermalClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessThermalModifier.java index 449825831182..21ef309fb327 100644 --- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessThermalClamper.java +++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessThermalModifier.java @@ -22,6 +22,8 @@ import static com.android.server.display.brightness.clamper.BrightnessClamperCon import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; +import android.hardware.display.BrightnessInfo; +import android.hardware.display.DisplayManagerInternal; import android.os.Handler; import android.os.IThermalEventListener; import android.os.IThermalService; @@ -33,8 +35,10 @@ import android.provider.DeviceConfigInterface; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.display.DisplayBrightnessState; import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData; import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel; +import com.android.server.display.brightness.BrightnessReason; import com.android.server.display.config.SensorData; import com.android.server.display.feature.DeviceConfigParameterProvider; import com.android.server.display.utils.DeviceConfigParsingUtils; @@ -43,12 +47,15 @@ import com.android.server.display.utils.SensorUtils; import java.io.PrintWriter; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.function.BiFunction; import java.util.function.Function; -class BrightnessThermalClamper extends - BrightnessClamper<BrightnessThermalClamper.ThermalData> { +class BrightnessThermalModifier implements BrightnessStateModifier, + BrightnessClamperController.DisplayDeviceDataListener, + BrightnessClamperController.StatefulModifier, + BrightnessClamperController.DeviceConfigListener { private static final String TAG = "BrightnessThermalClamper"; @NonNull @@ -58,6 +65,11 @@ class BrightnessThermalClamper extends // data from DeviceConfig, for all displays, for all dataSets // mapOf(uniqueDisplayId to mapOf(dataSetId to ThermalBrightnessThrottlingData)) @NonNull + protected final Handler mHandler; + @NonNull + protected final BrightnessClamperController.ClamperChangeListener mChangeListener; + + @NonNull private Map<String, Map<String, ThermalBrightnessThrottlingData>> mThermalThrottlingDataOverride = Map.of(); // data from DisplayDeviceConfig, for particular display+dataSet @@ -73,6 +85,8 @@ class BrightnessThermalClamper extends private String mDataId = null; @Temperature.ThrottlingStatus private int mThrottlingStatus = Temperature.THROTTLING_NONE; + private float mBrightnessCap = PowerManager.BRIGHTNESS_MAX; + private boolean mApplied = false; private final BiFunction<String, String, ThrottlingLevel> mDataPointMapper = (key, value) -> { try { @@ -88,63 +102,104 @@ class BrightnessThermalClamper extends mDataSetMapper = ThermalBrightnessThrottlingData::create; - BrightnessThermalClamper(Handler handler, ClamperChangeListener listener, - ThermalData thermalData) { - this(new Injector(), handler, listener, thermalData); + BrightnessThermalModifier(Handler handler, ClamperChangeListener listener, + BrightnessClamperController.DisplayDeviceData data) { + this(new Injector(), handler, listener, data); } @VisibleForTesting - BrightnessThermalClamper(Injector injector, Handler handler, - ClamperChangeListener listener, ThermalData thermalData) { - super(handler, listener); + BrightnessThermalModifier(Injector injector, @NonNull Handler handler, + @NonNull ClamperChangeListener listener, + @NonNull BrightnessClamperController.DisplayDeviceData data) { + mHandler = handler; + mChangeListener = listener; mConfigParameterProvider = injector.getDeviceConfigParameterProvider(); mThermalStatusObserver = new ThermalStatusObserver(injector, handler); mHandler.post(() -> { - setDisplayData(thermalData); + setDisplayData(data); loadOverrideData(); }); + } + //region BrightnessStateModifier + @Override + public void apply(DisplayManagerInternal.DisplayPowerRequest request, + DisplayBrightnessState.Builder stateBuilder) { + if (stateBuilder.getMaxBrightness() > mBrightnessCap) { + stateBuilder.setMaxBrightness(mBrightnessCap); + stateBuilder.setBrightness(Math.min(stateBuilder.getBrightness(), mBrightnessCap)); + stateBuilder.setBrightnessMaxReason(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL); + stateBuilder.getBrightnessReason().addModifier(BrightnessReason.MODIFIER_THROTTLED); + // set fast change only when modifier is activated. + // this will allow auto brightness to apply slow change even when modifier is active + if (!mApplied) { + stateBuilder.setIsSlowChange(false); + } + mApplied = true; + } else { + mApplied = false; + } + } + @Override + public void stop() { + mThermalStatusObserver.stopObserving(); } @Override - @NonNull - Type getType() { - return Type.THERMAL; + public void dump(PrintWriter writer) { + writer.println("BrightnessThermalClamper:"); + writer.println(" mThrottlingStatus: " + mThrottlingStatus); + writer.println(" mUniqueDisplayId: " + mUniqueDisplayId); + writer.println(" mDataId: " + mDataId); + writer.println(" mDataOverride: " + mThermalThrottlingDataOverride); + writer.println(" mDataFromDeviceConfig: " + mThermalThrottlingDataFromDeviceConfig); + writer.println(" mDataActive: " + mThermalThrottlingDataActive); + writer.println(" mBrightnessCap:" + mBrightnessCap); + writer.println(" mApplied:" + mApplied); + mThermalStatusObserver.dump(writer); } @Override - void onDeviceConfigChanged() { - mHandler.post(() -> { - loadOverrideData(); - recalculateActiveData(); - }); + public boolean shouldListenToLightSensor() { + return false; + } + + @Override + public void setAmbientLux(float lux) { + // noop } + //endregion + //region DisplayDeviceDataListener @Override - void onDisplayChanged(ThermalData data) { + public void onDisplayChanged(BrightnessClamperController.DisplayDeviceData data) { mHandler.post(() -> { setDisplayData(data); recalculateActiveData(); }); } + //endregion + //region StatefulModifier @Override - void stop() { - mThermalStatusObserver.stopObserving(); + public void applyStateChange( + BrightnessClamperController.ModifiersAggregatedState aggregatedState) { + if (aggregatedState.mMaxBrightness > mBrightnessCap) { + aggregatedState.mMaxBrightness = mBrightnessCap; + aggregatedState.mMaxBrightnessReason = BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL; + } } + //endregion + //region DeviceConfigListener @Override - void dump(PrintWriter writer) { - writer.println("BrightnessThermalClamper:"); - writer.println(" mThrottlingStatus: " + mThrottlingStatus); - writer.println(" mUniqueDisplayId: " + mUniqueDisplayId); - writer.println(" mDataId: " + mDataId); - writer.println(" mDataOverride: " + mThermalThrottlingDataOverride); - writer.println(" mDataFromDeviceConfig: " + mThermalThrottlingDataFromDeviceConfig); - writer.println(" mDataActive: " + mThermalThrottlingDataActive); - mThermalStatusObserver.dump(writer); - super.dump(writer); + public void onDeviceConfigChanged() { + mHandler.post(() -> { + loadOverrideData(); + recalculateActiveData(); + }); } + //endregion private void recalculateActiveData() { if (mUniqueDisplayId == null || mDataId == null) { @@ -176,14 +231,11 @@ class BrightnessThermalClamper extends private void recalculateBrightnessCap() { float brightnessCap = PowerManager.BRIGHTNESS_MAX; - boolean isActive = false; - if (mThermalThrottlingDataActive != null) { // Throttling levels are sorted by increasing severity for (ThrottlingLevel level : mThermalThrottlingDataActive.throttlingLevels) { if (level.thermalStatus <= mThrottlingStatus) { brightnessCap = level.brightness; - isActive = true; } else { // Throttling levels that are greater than the current status are irrelevant break; @@ -191,9 +243,8 @@ class BrightnessThermalClamper extends } } - if (brightnessCap != mBrightnessCap || mIsActive != isActive) { + if (brightnessCap != mBrightnessCap) { mBrightnessCap = brightnessCap; - mIsActive = isActive; mChangeListener.onChanged(); } } @@ -205,7 +256,6 @@ class BrightnessThermalClamper extends } } - private final class ThermalStatusObserver extends IThermalEventListener.Stub { private final Injector mInjector; private final Handler mHandler; @@ -228,7 +278,7 @@ class BrightnessThermalClamper extends String curType = mObserverTempSensor.type; mObserverTempSensor = tempSensor; - if (curType.equals(tempSensor.type)) { + if (Objects.equals(curType, tempSensor.type)) { Slog.d(TAG, "Thermal status observer already started"); return; } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java b/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java index 234d6d323838..236333ee433d 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java @@ -53,6 +53,10 @@ abstract class HdmiCecFeatureAction { // Default state used in common by all the feature actions. protected static final int STATE_NONE = 0; + // Delay to query avr's audio status, some avrs could report the old volume first. It could + // show inverse mute state on TV. + protected static final long DELAY_GIVE_AUDIO_STATUS = 500; + // Internal state indicating the progress of action. protected int mState = STATE_NONE; diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index 4b85217c2136..101596d9d7c1 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -1150,6 +1150,13 @@ public class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { return Constants.ABORT_REFUSED; } + if (mArcEstablished) { + HdmiLogger.debug("ARC is already established."); + HdmiCecMessage command = HdmiCecMessageBuilder.buildReportArcInitiated( + getDeviceInfo().getLogicalAddress(), message.getSource()); + mService.sendCecCommand(command); + return Constants.HANDLED; + } // In case where <Initiate Arc> is started by <Request ARC Initiation>, this message is // handled in RequestArcInitiationAction as well. SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this, diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 3c7b9d37e4c6..271836ac7d29 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -1638,6 +1638,10 @@ public class HdmiControlService extends SystemService { mHandler.post(new WorkSourceUidPreservingRunnable(runnable)); } + void runOnServiceThreadDelayed(Runnable runnable, long delay) { + mHandler.postDelayed(new WorkSourceUidPreservingRunnable(runnable), delay); + } + private void assertRunOnServiceThread() { if (Looper.myLooper() != mHandler.getLooper()) { throw new IllegalStateException("Should run on service thread."); diff --git a/services/core/java/com/android/server/hdmi/RequestSadAction.java b/services/core/java/com/android/server/hdmi/RequestSadAction.java index 0188e963140e..25663006b4b1 100644 --- a/services/core/java/com/android/server/hdmi/RequestSadAction.java +++ b/services/core/java/com/android/server/hdmi/RequestSadAction.java @@ -19,6 +19,8 @@ package com.android.server.hdmi; import android.hardware.hdmi.HdmiControlManager; import android.util.Slog; +import com.android.internal.annotations.VisibleForTesting; + import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -36,7 +38,8 @@ final class RequestSadAction extends HdmiCecFeatureAction { // State in which the action is waiting for <Report Short Audio Descriptor>. private static final int STATE_WAITING_FOR_REPORT_SAD = 1; private static final int MAX_SAD_PER_REQUEST = 4; - private static final int RETRY_COUNTER_MAX = 1; + @VisibleForTesting + public static final int RETRY_COUNTER_MAX = 3; private final int mTargetAddress; private final RequestSadCallback mCallback; private final List<Integer> mCecCodecsToQuery = new ArrayList<>(); diff --git a/services/core/java/com/android/server/hdmi/SendKeyAction.java b/services/core/java/com/android/server/hdmi/SendKeyAction.java index 7e18d8412aae..11682f664a15 100644 --- a/services/core/java/com/android/server/hdmi/SendKeyAction.java +++ b/services/core/java/com/android/server/hdmi/SendKeyAction.java @@ -180,15 +180,22 @@ final class SendKeyAction extends HdmiCecFeatureAction { && localDevice().getService().isAbsoluteVolumeBehaviorEnabled()) { sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(getSourceAddress(), mTargetAddress), - __ -> sendCommand(HdmiCecMessageBuilder.buildGiveAudioStatus( - getSourceAddress(), - localDevice().findAudioReceiverAddress()))); + __ -> queryAvrAudioStatus()); } else { sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(getSourceAddress(), mTargetAddress)); } } + private void queryAvrAudioStatus() { + localDevice().mService.runOnServiceThreadDelayed( + () -> sendCommand(HdmiCecMessageBuilder.buildGiveAudioStatus( + getSourceAddress(), + localDevice().findAudioReceiverAddress())), + DELAY_GIVE_AUDIO_STATUS); + + } + @Override public boolean processCommand(HdmiCecMessage cmd) { // Send key action doesn't need any incoming CEC command, hence does not consume it. diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index af0ccf93c826..f7478799527c 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -4678,6 +4678,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } } + // TODO(b/356239178): Make dump proto multi-user aware. private void dumpDebug(ProtoOutputStream proto, long fieldId) { synchronized (ImfLock.class) { final int userId = mCurrentImeUserId; @@ -6103,17 +6104,40 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @BinderThread private void dumpAsStringNoCheck(FileDescriptor fd, PrintWriter pw, String[] args, boolean isCritical) { - IInputMethodInvoker method; - ClientState client; - + final int argUserId = parseUserIdFromDumpArgs(args); final Printer p = new PrintWriterPrinter(pw); + p.println("Current Input Method Manager state:"); + p.println(" concurrentMultiUserModeEnabled=" + mConcurrentMultiUserModeEnabled); + if (mConcurrentMultiUserModeEnabled && argUserId == UserHandle.USER_NULL) { + mUserDataRepository.forAllUserData( + u -> dumpAsStringNoCheckForUser(u, fd, pw, args, isCritical)); + } else { + final int userId = argUserId != UserHandle.USER_NULL ? argUserId : mCurrentImeUserId; + final var userData = getUserData(userId); + dumpAsStringNoCheckForUser(userData, fd, pw, args, isCritical); + } + } + + @UserIdInt + private static int parseUserIdFromDumpArgs(String[] args) { + final int userIdx = Arrays.binarySearch(args, "--user"); + if (userIdx == -1 || userIdx == args.length - 1) { + return UserHandle.USER_NULL; + } + return Integer.parseInt(args[userIdx + 1]); + } + // TODO(b/356239178): Update dump format output to better group per-user info. + @BinderThread + private void dumpAsStringNoCheckForUser(UserData userData, FileDescriptor fd, PrintWriter pw, + String[] args, boolean isCritical) { + final Printer p = new PrintWriterPrinter(pw); + IInputMethodInvoker method; + ClientState client; + p.println(" UserId=" + userData.mUserId); synchronized (ImfLock.class) { - final int userId = mCurrentImeUserId; - final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); - final var userData = getUserData(userId); - p.println("Current Input Method Manager state:"); - p.println(" concurrentMultiUserModeEnabled" + mConcurrentMultiUserModeEnabled); + final InputMethodSettings settings = InputMethodSettingsRepository.get( + userData.mUserId); final List<InputMethodInfo> methodList = settings.getMethodList(); int numImes = methodList.size(); p.println(" Input Methods:"); @@ -6133,7 +6157,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. p.println(" sessionRequested=" + c.mSessionRequested); p.println(" sessionRequestedForAccessibility=" - + c.mSessionRequestedForAccessibility); + + c.mSessionRequestedForAccessibility); p.println(" curSession=" + c.mCurSession); p.println(" selfReportedDisplayId=" + c.mSelfReportedDisplayId); p.println(" uid=" + c.mUid); @@ -6141,7 +6165,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. }; mClientController.forAllClients(clientControllerDump); final var bindingController = userData.mBindingController; - p.println(" mCurrentImeUserId=" + userData.mUserId); p.println(" mCurMethodId=" + bindingController.getSelectedMethodId()); client = userData.mCurClient; p.println(" mCurClient=" + client + " mCurSeq=" @@ -6234,8 +6257,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. p.println("No input method client."); } synchronized (ImfLock.class) { - final int userId = mCurrentImeUserId; - final var userData = getUserData(userId); if (userData.mImeBindingState.mFocusedWindowClient != null && client != userData.mImeBindingState.mFocusedWindowClient) { p.println(" "); diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index 4fcf27d62a1a..38ef5b8cedb9 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -3403,8 +3403,13 @@ public class LockSettingsService extends ILockSettings.Stub { // It's OK to dump the credential type since anyone with physical access can just // observe it from the keyguard directly. pw.println("Quality: " + getKeyguardStoredQuality(userId)); - pw.println("CredentialType: " + LockPatternUtils.credentialTypeToString( - getCredentialTypeInternal(userId))); + final int credentialType = getCredentialTypeInternal(userId); + pw.println("CredentialType: " + + LockPatternUtils.credentialTypeToString(credentialType)); + if (credentialType == CREDENTIAL_TYPE_NONE) { + pw.println("IsLockScreenDisabled: " + + getBoolean(LockPatternUtils.DISABLE_LOCKSCREEN_KEY, false, userId)); + } pw.println("SeparateChallenge: " + getSeparateProfileChallengeEnabledInternal(userId)); pw.println(TextUtils.formatSimple("Metrics: %s", getUserPasswordMetrics(userId) != null ? "known" : "unknown")); diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java index 0cc50e68ea21..3523a3336a63 100644 --- a/services/core/java/com/android/server/notification/ManagedServices.java +++ b/services/core/java/com/android/server/notification/ManagedServices.java @@ -103,7 +103,7 @@ import java.util.Set; * - A remote interface definition (aidl) provided by the service used for communication. */ abstract public class ManagedServices { - protected final String TAG = getClass().getSimpleName(); + protected final String TAG = getClass().getSimpleName().replace('$', '.'); protected final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final int ON_BINDING_DIED_REBIND_DELAY_MS = 10000; diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java index 1c70af0a56ea..5ca5fda19b2f 100644 --- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java +++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java @@ -397,6 +397,8 @@ public class PackageInfoUtils { ai.privateFlags |= flag(state.isInstantApp(), ApplicationInfo.PRIVATE_FLAG_INSTANT) | flag(state.isVirtualPreload(), ApplicationInfo.PRIVATE_FLAG_VIRTUAL_PRELOAD) | flag(state.isHidden(), ApplicationInfo.PRIVATE_FLAG_HIDDEN); + ai.privateFlagsExt |= + flag(state.isNotLaunched(), ApplicationInfo.PRIVATE_FLAG_EXT_NOT_LAUNCHED); if (state.getEnabledState() == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) { ai.enabled = true; } else if (state.getEnabledState() diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 749025b0cf40..1fcd7f1ef861 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -810,7 +810,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { event.recycle(); break; case MSG_HANDLE_ALL_APPS: - launchAllAppsAction(); + launchAllAppsAction((KeyEvent) msg.obj); break; case MSG_RINGER_TOGGLE_CHORD: handleRingerChordGesture(); @@ -1879,26 +1879,31 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - private void launchAllAppsAction() { - Intent intent = new Intent(Intent.ACTION_ALL_APPS); - if (mHasFeatureLeanback) { - Intent intentLauncher = new Intent(Intent.ACTION_MAIN); - intentLauncher.addCategory(Intent.CATEGORY_HOME); - ResolveInfo resolveInfo = mPackageManager.resolveActivityAsUser(intentLauncher, - PackageManager.MATCH_SYSTEM_ONLY, - mCurrentUserId); - if (resolveInfo != null) { - intent.setPackage(resolveInfo.activityInfo.packageName); + private void launchAllAppsAction(KeyEvent event) { + if (mHasFeatureLeanback || mHasFeatureWatch) { + // TV and watch support the all apps intent + notifyKeyGestureCompleted(event, + KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS); + Intent intent = new Intent(Intent.ACTION_ALL_APPS); + if (mHasFeatureLeanback) { + Intent intentLauncher = new Intent(Intent.ACTION_MAIN); + intentLauncher.addCategory(Intent.CATEGORY_HOME); + ResolveInfo resolveInfo = mPackageManager.resolveActivityAsUser(intentLauncher, + PackageManager.MATCH_SYSTEM_ONLY, + mCurrentUserId); + if (resolveInfo != null) { + intent.setPackage(resolveInfo.activityInfo.packageName); + } + } + startActivityAsUser(intent, UserHandle.CURRENT); + } else { + notifyKeyGestureCompleted(event, + KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS); + AccessibilityManagerInternal accessibilityManager = getAccessibilityManagerInternal(); + if (accessibilityManager != null) { + accessibilityManager.performSystemAction( + AccessibilityService.GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS); } - } - startActivityAsUser(intent, UserHandle.CURRENT); - } - - private void launchAllAppsViaA11y() { - AccessibilityManagerInternal accessibilityManager = getAccessibilityManagerInternal(); - if (accessibilityManager != null) { - accessibilityManager.performSystemAction( - AccessibilityService.GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS); } dismissKeyboardShortcutsMenu(); } @@ -2076,15 +2081,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, "Home - Long Press"); switch (mLongPressOnHomeBehavior) { case LONG_PRESS_HOME_ALL_APPS: - if (mHasFeatureLeanback) { - launchAllAppsAction(); - notifyKeyGestureCompleted(event, - KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS); - } else { - launchAllAppsViaA11y(); - notifyKeyGestureCompleted(event, - KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS); - } + launchAllAppsAction(event); break; case LONG_PRESS_HOME_ASSIST: notifyKeyGestureCompleted(event, @@ -3695,18 +3692,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { break; case KeyEvent.KEYCODE_ALL_APPS: if (firstDown) { - if (mHasFeatureLeanback) { - mHandler.removeMessages(MSG_HANDLE_ALL_APPS); - Message msg = mHandler.obtainMessage(MSG_HANDLE_ALL_APPS); - msg.setAsynchronous(true); - msg.sendToTarget(); - notifyKeyGestureCompleted(event, - KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS); - } else { - launchAllAppsViaA11y(); - notifyKeyGestureCompleted(event, - KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS); - } + mHandler.removeMessages(MSG_HANDLE_ALL_APPS); + + Message msg = mHandler.obtainMessage(MSG_HANDLE_ALL_APPS, new KeyEvent(event)); + msg.setAsynchronous(true); + msg.sendToTarget(); } return true; case KeyEvent.KEYCODE_NOTIFICATION: @@ -3759,9 +3749,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK); } else if (mPendingMetaAction) { if (!canceled) { - launchAllAppsViaA11y(); - notifyKeyGestureCompleted(event, - KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS); + launchAllAppsAction(event); } mPendingMetaAction = false; } diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 12e7fd010e3d..71cb8820761f 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -3668,7 +3668,7 @@ public final class PowerManagerService extends SystemService mBrightWhenDozingConfig); int wakefulness = powerGroup.getWakefulnessLocked(); if (DEBUG_SPEW) { - Slog.d(TAG, "updateDisplayPowerStateLocked: displayReady=" + ready + Slog.d(TAG, "updatePowerGroupsLocked: displayReady=" + ready + ", groupId=" + groupId + ", policy=" + policyToString(powerGroup.getPolicyLocked()) + ", mWakefulness=" diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index 60b5e658dfc5..91a17a9e1c31 100644 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -599,13 +599,6 @@ public final class TvInputManagerService extends SystemService { ComponentName component = it.next(); ServiceState serviceState = userState.serviceStateMap.get(component); if (serviceState != null && serviceState.sessionTokens.isEmpty()) { - if (serviceState.callback != null) { - try { - serviceState.service.unregisterCallback(serviceState.callback); - } catch (RemoteException e) { - Slog.e(TAG, "error in unregisterCallback", e); - } - } unbindService(serviceState); it.remove(); } @@ -667,13 +660,6 @@ public final class TvInputManagerService extends SystemService { // Unregister all callbacks and unbind all services. for (ServiceState serviceState : userState.serviceStateMap.values()) { if (serviceState.service != null) { - if (serviceState.callback != null) { - try { - serviceState.service.unregisterCallback(serviceState.callback); - } catch (RemoteException e) { - Slog.e(TAG, "error in unregisterCallback", e); - } - } unbindService(serviceState); } } @@ -3571,12 +3557,19 @@ public final class TvInputManagerService extends SystemService { @GuardedBy("mLock") private void unbindService(ServiceState serviceState) { - if (!serviceState.bound) { + if (serviceState == null || !serviceState.bound) { return; } if (DEBUG) { Slog.d(TAG, "unbindService(service=" + serviceState.component + ")"); } + if (serviceState.callback != null) { + try { + serviceState.service.unregisterCallback(serviceState.callback); + } catch (RemoteException e) { + Slog.e(TAG, "error in unregisterCallback", e); + } + } mContext.unbindService(serviceState.connection); serviceState.bound = false; serviceState.service = null; @@ -3794,9 +3787,9 @@ public final class TvInputManagerService extends SystemService { if (serviceState.hardwareInputMap.containsKey(inputInfo.getId())) { return; } - Slog.d("ServiceCallback", - "addHardwareInput: device id " + deviceId + ", " - + inputInfo.toString()); + Slog.d(TAG, "ServiceCallback: addHardwareInput, deviceId: " + deviceId + + ", inputInfo: " + inputInfo.toString() + " by " + mComponent + + ", userId: " + mUserId); mTvInputHardwareManager.addHardwareInput(deviceId, inputInfo); addHardwareInputLocked(inputInfo, mComponent, mUserId); } @@ -3815,6 +3808,9 @@ public final class TvInputManagerService extends SystemService { if (serviceState.hardwareInputMap.containsKey(inputInfo.getId())) { return; } + Slog.d(TAG, "ServiceCallback: addHdmiInput, id: " + id + + ", inputInfo: "+ inputInfo.toString() + " by " + mComponent + + ", userId: " + mUserId); mTvInputHardwareManager.addHdmiInput(id, inputInfo); addHardwareInputLocked(inputInfo, mComponent, mUserId); if (mOnScreenInputId != null && mOnScreenSessionState != null) { @@ -3845,8 +3841,8 @@ public final class TvInputManagerService extends SystemService { final long identity = Binder.clearCallingIdentity(); try { synchronized (mLock) { - Slog.d("ServiceCallback", - "removeHardwareInput " + inputId + " by " + mComponent); + Slog.d(TAG, "ServiceCallback: removeHardwareInput, inputId: " + inputId + + " by " + mComponent + ", userId: " + mUserId); removeHardwareInputLocked(inputId, mUserId); } } finally { diff --git a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java index ecd140e23ab6..96a25dac21e3 100644 --- a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java +++ b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java @@ -44,7 +44,6 @@ import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting.Visibility; -import com.android.internal.telephony.flags.Flags; import com.android.internal.util.IndentingPrintWriter; import com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper; @@ -324,11 +323,8 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver { if (SubscriptionManager.isValidSubscriptionId(subId)) { // Get only configs as needed to save memory. final PersistableBundle carrierConfig = - Flags.fixCrashOnGettingConfigWhenPhoneIsGone() - ? CarrierConfigManager.getCarrierConfigSubset(mContext, subId, - VcnManager.VCN_RELATED_CARRIER_CONFIG_KEYS) - : mCarrierConfigManager.getConfigForSubId(subId, - VcnManager.VCN_RELATED_CARRIER_CONFIG_KEYS); + CarrierConfigManager.getCarrierConfigSubset(mContext, subId, + VcnManager.VCN_RELATED_CARRIER_CONFIG_KEYS); if (mDeps.isConfigForIdentifiedCarrier(carrierConfig)) { mReadySubIdsBySlotId.put(slotId, subId); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 85e24c15c1a5..b6e45fc803f7 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -14799,7 +14799,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } @Override - public void setSecondaryLockscreenEnabled(ComponentName who, boolean enabled) { + public void setSecondaryLockscreenEnabled(ComponentName who, boolean enabled, + PersistableBundle options) { Objects.requireNonNull(who, "ComponentName is null"); // Check can set secondary lockscreen enabled @@ -18644,11 +18645,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { toggleBackupServiceActive(caller.getUserId(), enabled); - if (Flags.backupServiceSecurityLogEventEnabled()) { - if (SecurityLog.isLoggingEnabled()) { - SecurityLog.writeEvent(SecurityLog.TAG_BACKUP_SERVICE_TOGGLED, - caller.getPackageName(), caller.getUserId(), enabled ? 1 : 0); - } + if (SecurityLog.isLoggingEnabled()) { + SecurityLog.writeEvent(SecurityLog.TAG_BACKUP_SERVICE_TOGGLED, + caller.getPackageName(), caller.getUserId(), enabled ? 1 : 0); } } diff --git a/services/tests/appfunctions/Android.bp b/services/tests/appfunctions/Android.bp new file mode 100644 index 000000000000..b5cf98697d54 --- /dev/null +++ b/services/tests/appfunctions/Android.bp @@ -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 { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test { + name: "FrameworksAppFunctionsTests", + team: "trendy_team_machine_learning", + defaults: [ + "modules-utils-testable-device-config-defaults", + ], + + srcs: [ + "src/**/*.kt", + ], + + static_libs: [ + "androidx.test.core", + "androidx.test.runner", + "androidx.test.ext.truth", + "platform-test-annotations", + "services.appfunctions", + "servicestests-core-utils", + "truth", + "frameworks-base-testutils", + "androidx.test.rules", + ], + + libs: [ + "android.test.base", + "android.test.runner", + ], + + certificate: "platform", + platform_apis: true, + test_suites: ["device-tests"], + + optimize: { + enabled: false, + }, +} diff --git a/services/tests/appfunctions/AndroidManifest.xml b/services/tests/appfunctions/AndroidManifest.xml new file mode 100644 index 000000000000..1d42b177db59 --- /dev/null +++ b/services/tests/appfunctions/AndroidManifest.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.frameworks.appfunctionstests"> + + <application android:testOnly="true" + android:debuggable="true"> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation + android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.frameworks.appfunctionstests" + android:label="Frameworks AppFunctions Services Tests" /> + +</manifest>
\ No newline at end of file diff --git a/services/tests/appfunctions/AndroidTest.xml b/services/tests/appfunctions/AndroidTest.xml new file mode 100644 index 000000000000..0650120afe24 --- /dev/null +++ b/services/tests/appfunctions/AndroidTest.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<configuration description="Runs Frameworks AppFunctions Services Tests."> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="install-arg" value="-t" /> + <option name="test-file-name" value="FrameworksAppFunctionsTests.apk" /> + </target_preparer> + + <option name="test-tag" value="FrameworksAppFunctionsTests" /> + + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.frameworks.appfunctionstests" /> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> + <option name="hidden-api-checks" value="false"/> + </test> +</configuration>
\ No newline at end of file diff --git a/services/tests/appfunctions/src/android/app/appfunctions/AppFunctionStaticMetadataHelperTest.kt b/services/tests/appfunctions/src/android/app/appfunctions/AppFunctionStaticMetadataHelperTest.kt new file mode 100644 index 000000000000..d8ce39356d9c --- /dev/null +++ b/services/tests/appfunctions/src/android/app/appfunctions/AppFunctionStaticMetadataHelperTest.kt @@ -0,0 +1,57 @@ +/* + * 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 com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@RunWith(JUnit4::class) +class AppFunctionStaticMetadataHelperTest { + + @Test + fun getStaticSchemaNameForPackage() { + val actualSchemaName = + AppFunctionStaticMetadataHelper.getStaticSchemaNameForPackage("com.example.app") + + assertThat(actualSchemaName).isEqualTo("AppFunctionStaticMetadata-com.example.app") + } + + @Test + fun getDocumentIdForAppFunction() { + val packageName = "com.example.app" + val functionId = "someFunction" + + val actualDocumentId = + AppFunctionStaticMetadataHelper.getDocumentIdForAppFunction(packageName, functionId) + + assertThat(actualDocumentId).isEqualTo("com.example.app/someFunction") + } + + @Test + fun getStaticMetadataQualifiedId() { + val packageName = "com.example.app" + val functionId = "someFunction" + + val actualQualifiedId = + AppFunctionStaticMetadataHelper.getStaticMetadataQualifiedId(packageName, functionId) + + assertThat(actualQualifiedId) + .isEqualTo("android\$apps-db/app_functions#com.example.app/someFunction") + } +} diff --git a/services/tests/appfunctions/src/com/android/server/appfunctions/FutureAppSearchSessionTest.kt b/services/tests/appfunctions/src/com/android/server/appfunctions/FutureAppSearchSessionTest.kt new file mode 100644 index 000000000000..5233f194d6c5 --- /dev/null +++ b/services/tests/appfunctions/src/com/android/server/appfunctions/FutureAppSearchSessionTest.kt @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.appfunctions + +import android.app.appfunctions.AppFunctionRuntimeMetadata +import android.app.appfunctions.AppFunctionRuntimeMetadata.createAppFunctionRuntimeSchema +import android.app.appfunctions.AppFunctionRuntimeMetadata.createParentAppFunctionRuntimeSchema +import android.app.appsearch.AppSearchManager +import android.app.appsearch.PutDocumentsRequest +import android.app.appsearch.SearchSpec +import android.app.appsearch.SetSchemaRequest +import androidx.test.platform.app.InstrumentationRegistry +import com.google.common.truth.Truth.assertThat +import com.google.common.util.concurrent.MoreExecutors +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@RunWith(JUnit4::class) +class FutureAppSearchSessionTest { + private val context = InstrumentationRegistry.getInstrumentation().targetContext + private val appSearchManager = context.getSystemService(AppSearchManager::class.java) + private val testExecutor = MoreExecutors.directExecutor() + + @Before + @After + fun clearData() { + val searchContext = AppSearchManager.SearchContext.Builder(TEST_DB).build() + FutureAppSearchSession(appSearchManager, testExecutor, searchContext).use { + val setSchemaRequest = SetSchemaRequest.Builder().build() + it.setSchema(setSchemaRequest) + } + } + + @Test + fun setSchema() { + val searchContext = AppSearchManager.SearchContext.Builder(TEST_DB).build() + FutureAppSearchSession(appSearchManager, testExecutor, searchContext).use { session -> + val setSchemaRequest = + SetSchemaRequest.Builder() + .addSchemas( + createParentAppFunctionRuntimeSchema(), + createAppFunctionRuntimeSchema(TEST_PACKAGE_NAME) + ) + .build() + + val schema = session.setSchema(setSchemaRequest) + + assertThat(schema.get()).isNotNull() + } + } + + @Test + fun put() { + val searchContext = AppSearchManager.SearchContext.Builder(TEST_DB).build() + FutureAppSearchSession(appSearchManager, testExecutor, searchContext).use { session -> + val setSchemaRequest = + SetSchemaRequest.Builder() + .addSchemas( + createParentAppFunctionRuntimeSchema(), + createAppFunctionRuntimeSchema(TEST_PACKAGE_NAME) + ) + .build() + val schema = session.setSchema(setSchemaRequest) + assertThat(schema.get()).isNotNull() + val appFunctionRuntimeMetadata = + AppFunctionRuntimeMetadata.Builder(TEST_PACKAGE_NAME, TEST_FUNCTION_ID, "").build() + val putDocumentsRequest: PutDocumentsRequest = + PutDocumentsRequest.Builder() + .addGenericDocuments(appFunctionRuntimeMetadata) + .build() + + val putResult = session.put(putDocumentsRequest) + + assertThat(putResult.get().isSuccess).isTrue() + } + } + + @Test + fun search() { + val searchContext = AppSearchManager.SearchContext.Builder(TEST_DB).build() + FutureAppSearchSession(appSearchManager, testExecutor, searchContext).use { session -> + val setSchemaRequest = + SetSchemaRequest.Builder() + .addSchemas( + createParentAppFunctionRuntimeSchema(), + createAppFunctionRuntimeSchema(TEST_PACKAGE_NAME) + ) + .build() + val schema = session.setSchema(setSchemaRequest) + assertThat(schema.get()).isNotNull() + val appFunctionRuntimeMetadata = + AppFunctionRuntimeMetadata.Builder(TEST_PACKAGE_NAME, TEST_FUNCTION_ID, "").build() + val putDocumentsRequest: PutDocumentsRequest = + PutDocumentsRequest.Builder() + .addGenericDocuments(appFunctionRuntimeMetadata) + .build() + val putResult = session.put(putDocumentsRequest) + assertThat(putResult.get().isSuccess).isTrue() + + val searchResult = session.search("", SearchSpec.Builder().build()) + + val genericDocs = + searchResult.get().nextPage.get().stream().map { it.genericDocument }.toList() + assertThat(genericDocs).hasSize(1) + val foundAppFunctionRuntimeMetadata = AppFunctionRuntimeMetadata(genericDocs[0]) + assertThat(foundAppFunctionRuntimeMetadata.functionId).isEqualTo(TEST_FUNCTION_ID) + } + } + + private companion object { + const val TEST_DB: String = "test_db" + const val TEST_PACKAGE_NAME: String = "test_pkg" + const val TEST_FUNCTION_ID: String = "print" + } +} diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java index 5bb8ded6920e..52f1cbd89e13 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -107,6 +107,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.MessageQueue; +import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; import android.os.SystemProperties; @@ -3053,6 +3054,74 @@ public class DisplayManagerServiceTest { } @Test + public void testBrightnessUpdates() { + DisplayManagerService displayManager = + new DisplayManagerService(mContext, mShortMockedInjector); + DisplayManagerInternal localService = displayManager.new LocalService(); + DisplayManagerService.BinderService displayManagerBinderService = + displayManager.new BinderService(); + registerDefaultDisplays(displayManager); + initDisplayPowerController(localService); + + final float invalidBrightness = -0.3f; + final float brightnessOff = -1.0f; + final float minimumBrightness = 0.0f; + final float validBrightness = 0.5f; + + Settings.System.putInt(mContext.getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS_MODE, + Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL); + + // set and check valid brightness + waitForIdleHandler(mPowerHandler); + displayManagerBinderService.setBrightness(Display.DEFAULT_DISPLAY, validBrightness); + waitForIdleHandler(mPowerHandler); + assertEquals(validBrightness, + displayManagerBinderService.getBrightness(Display.DEFAULT_DISPLAY), + FLOAT_TOLERANCE); + + // set and check invalid brightness + waitForIdleHandler(mPowerHandler); + displayManagerBinderService.setBrightness(Display.DEFAULT_DISPLAY, invalidBrightness); + waitForIdleHandler(mPowerHandler); + assertEquals(PowerManager.BRIGHTNESS_MIN, + displayManagerBinderService.getBrightness(Display.DEFAULT_DISPLAY), + FLOAT_TOLERANCE); + + // reset and check valid brightness + waitForIdleHandler(mPowerHandler); + displayManagerBinderService.setBrightness(Display.DEFAULT_DISPLAY, validBrightness); + waitForIdleHandler(mPowerHandler); + assertEquals(validBrightness, + displayManagerBinderService.getBrightness(Display.DEFAULT_DISPLAY), + FLOAT_TOLERANCE); + + // set and check brightness off + waitForIdleHandler(mPowerHandler); + displayManagerBinderService.setBrightness(Display.DEFAULT_DISPLAY, brightnessOff); + waitForIdleHandler(mPowerHandler); + assertEquals(PowerManager.BRIGHTNESS_MIN, + displayManagerBinderService.getBrightness(Display.DEFAULT_DISPLAY), + FLOAT_TOLERANCE); + + // reset and check valid brightness + waitForIdleHandler(mPowerHandler); + displayManagerBinderService.setBrightness(Display.DEFAULT_DISPLAY, validBrightness); + waitForIdleHandler(mPowerHandler); + assertEquals(validBrightness, + displayManagerBinderService.getBrightness(Display.DEFAULT_DISPLAY), + FLOAT_TOLERANCE); + + // set and check minimum brightness + waitForIdleHandler(mPowerHandler); + displayManagerBinderService.setBrightness(Display.DEFAULT_DISPLAY, minimumBrightness); + waitForIdleHandler(mPowerHandler); + assertEquals(PowerManager.BRIGHTNESS_MIN, + displayManagerBinderService.getBrightness(Display.DEFAULT_DISPLAY), + FLOAT_TOLERANCE); + } + + @Test public void testResolutionChangeGetsBackedUp() throws Exception { when(mMockFlags.isResolutionBackupRestoreEnabled()).thenReturn(true); DisplayManagerService displayManager = 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 f9dc12258667..da79f301ee3c 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 @@ -226,7 +226,7 @@ public class BrightnessClamperControllerTest { float clampedBrightness = 0.6f; float customAnimationRate = 0.01f; when(mMockClamper.getBrightnessCap()).thenReturn(clampedBrightness); - when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.THERMAL); + when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.POWER); when(mMockClamper.getCustomAnimationRate()).thenReturn(customAnimationRate); when(mMockClamper.isActive()).thenReturn(false); mTestInjector.mCapturedChangeListener.onChanged(); @@ -250,7 +250,7 @@ public class BrightnessClamperControllerTest { float clampedBrightness = 0.6f; float customAnimationRate = 0.01f; when(mMockClamper.getBrightnessCap()).thenReturn(clampedBrightness); - when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.THERMAL); + when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.POWER); when(mMockClamper.getCustomAnimationRate()).thenReturn(customAnimationRate); when(mMockClamper.isActive()).thenReturn(true); mTestInjector.mCapturedChangeListener.onChanged(); @@ -274,7 +274,7 @@ public class BrightnessClamperControllerTest { float clampedBrightness = 0.8f; float customAnimationRate = 0.01f; when(mMockClamper.getBrightnessCap()).thenReturn(clampedBrightness); - when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.THERMAL); + when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.POWER); when(mMockClamper.getCustomAnimationRate()).thenReturn(customAnimationRate); when(mMockClamper.isActive()).thenReturn(true); mTestInjector.mCapturedChangeListener.onChanged(); @@ -298,7 +298,7 @@ public class BrightnessClamperControllerTest { float clampedBrightness = 0.6f; float customAnimationRate = 0.01f; when(mMockClamper.getBrightnessCap()).thenReturn(clampedBrightness); - when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.THERMAL); + when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.POWER); when(mMockClamper.getCustomAnimationRate()).thenReturn(customAnimationRate); when(mMockClamper.isActive()).thenReturn(true); mTestInjector.mCapturedChangeListener.onChanged(); diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessThermalClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessThermalModifierTest.java index 9d16594fae93..35d384bccc7f 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessThermalClamperTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessThermalModifierTest.java @@ -18,13 +18,16 @@ package com.android.server.display.brightness.clamper; import static com.android.server.display.config.DisplayDeviceConfigTestUtilsKt.createSensorData; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static com.google.common.truth.Truth.assertThat; + import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import android.hardware.display.BrightnessInfo; import android.hardware.display.DisplayManager; +import android.hardware.display.DisplayManagerInternal; +import android.os.IBinder; import android.os.IThermalEventListener; import android.os.IThermalService; import android.os.PowerManager; @@ -33,12 +36,14 @@ import android.os.Temperature; import android.provider.DeviceConfig; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import com.android.internal.annotations.Keep; +import com.android.server.display.DisplayBrightnessState; import com.android.server.display.DisplayDeviceConfig; import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData; import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel; +import com.android.server.display.brightness.BrightnessReason; +import com.android.server.display.brightness.clamper.BrightnessClamperController.ModifiersAggregatedState; import com.android.server.display.config.SensorData; import com.android.server.display.feature.DeviceConfigParameterProvider; import com.android.server.testutils.FakeDeviceConfigInterface; @@ -54,10 +59,13 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.HashMap; import java.util.List; +import java.util.Map; @RunWith(JUnitParamsRunner.class) -public class BrightnessThermalClamperTest { +public class BrightnessThermalModifierTest { + private static final int NO_MODIFIER = 0; private static final float FLOAT_TOLERANCE = 0.001f; @@ -66,28 +74,35 @@ public class BrightnessThermalClamperTest { private IThermalService mMockThermalService; @Mock private BrightnessClamperController.ClamperChangeListener mMockClamperChangeListener; + @Mock + private DisplayManagerInternal.DisplayPowerRequest mMockRequest; + @Mock + private DisplayDeviceConfig mMockDisplayDeviceConfig; + @Mock + private IBinder mMockBinder; private final FakeDeviceConfigInterface mFakeDeviceConfigInterface = new FakeDeviceConfigInterface(); private final TestHandler mTestHandler = new TestHandler(null); - private BrightnessThermalClamper mClamper; + private BrightnessThermalModifier mModifier; @Before public void setUp() { MockitoAnnotations.initMocks(this); - mClamper = new BrightnessThermalClamper(new TestInjector(), mTestHandler, - mMockClamperChangeListener, new TestThermalData()); + when(mMockDisplayDeviceConfig.getTempSensor()) + .thenReturn(SensorData.loadTempSensorUnspecifiedConfig()); + mModifier = new BrightnessThermalModifier(new TestInjector(), mTestHandler, + mMockClamperChangeListener, + ClamperTestUtilsKt.createDisplayDeviceData(mMockDisplayDeviceConfig, mMockBinder)); mTestHandler.flush(); } - @Test - public void testTypeIsThermal() { - assertEquals(BrightnessClamper.Type.THERMAL, mClamper.getType()); - } @Test public void testNoThrottlingData() { - assertFalse(mClamper.isActive()); - assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE); + assertModifierState( + 0.3f, true, + PowerManager.BRIGHTNESS_MAX, 0.3f, + false, true); } @Keep @@ -129,15 +144,19 @@ public class BrightnessThermalClamperTest { @Temperature.ThrottlingStatus int throttlingStatus, boolean expectedActive, float expectedBrightness) throws RemoteException { IThermalEventListener thermalEventListener = captureSkinThermalEventListener(); - mClamper.onDisplayChanged(new TestThermalData(throttlingLevels)); + onDisplayChange(throttlingLevels); mTestHandler.flush(); - assertFalse(mClamper.isActive()); - assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE); + assertModifierState( + PowerManager.BRIGHTNESS_MAX, true, + PowerManager.BRIGHTNESS_MAX, PowerManager.BRIGHTNESS_MAX, + false, true); thermalEventListener.notifyThrottling(createSkinTemperature(throttlingStatus)); mTestHandler.flush(); - assertEquals(expectedActive, mClamper.isActive()); - assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE); + assertModifierState( + PowerManager.BRIGHTNESS_MAX, true, + expectedBrightness, expectedBrightness, + expectedActive, !expectedActive); } @Test @@ -148,13 +167,56 @@ public class BrightnessThermalClamperTest { IThermalEventListener thermalEventListener = captureSkinThermalEventListener(); thermalEventListener.notifyThrottling(createSkinTemperature(throttlingStatus)); mTestHandler.flush(); - assertFalse(mClamper.isActive()); - assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE); - mClamper.onDisplayChanged(new TestThermalData(throttlingLevels)); + assertModifierState( + PowerManager.BRIGHTNESS_MAX, true, + PowerManager.BRIGHTNESS_MAX, PowerManager.BRIGHTNESS_MAX, + false, true); + + onDisplayChange(throttlingLevels); + mTestHandler.flush(); + assertModifierState( + PowerManager.BRIGHTNESS_MAX, true, + expectedBrightness, expectedBrightness, + expectedActive, !expectedActive); + } + + @Test + public void testAppliesFastChangeOnlyOnActivation() throws RemoteException { + IThermalEventListener thermalEventListener = captureSkinThermalEventListener(); + onDisplayChange(List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_SEVERE, 0.5f))); + mTestHandler.flush(); + + thermalEventListener.notifyThrottling(createSkinTemperature(Temperature.THROTTLING_SEVERE)); + mTestHandler.flush(); + + // expectedSlowChange = false + assertModifierState( + PowerManager.BRIGHTNESS_MAX, true, + 0.5f, 0.5f, + true, false); + + // slowChange is unchanged + assertModifierState( + PowerManager.BRIGHTNESS_MAX, true, + 0.5f, 0.5f, + true, true); + } + + @Test + public void testCapsMaxBrightnessOnly_currentBrightnessIsLowAndFastChange() + throws RemoteException { + IThermalEventListener thermalEventListener = captureSkinThermalEventListener(); + onDisplayChange(List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_SEVERE, 0.5f))); mTestHandler.flush(); - assertEquals(expectedActive, mClamper.isActive()); - assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE); + + thermalEventListener.notifyThrottling(createSkinTemperature(Temperature.THROTTLING_SEVERE)); + mTestHandler.flush(); + + assertModifierState( + 0.1f, false, + 0.5f, 0.1f, + true, false); } @Test @@ -162,28 +224,37 @@ public class BrightnessThermalClamperTest { IThermalEventListener thermalEventListener = captureSkinThermalEventListener(); thermalEventListener.notifyThrottling(createSkinTemperature(Temperature.THROTTLING_SEVERE)); mTestHandler.flush(); - assertFalse(mClamper.isActive()); - assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE); - mClamper.onDisplayChanged(new TestThermalData( - List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_SEVERE, 0.5f)))); + assertModifierState( + PowerManager.BRIGHTNESS_MAX, true, + PowerManager.BRIGHTNESS_MAX, PowerManager.BRIGHTNESS_MAX, + false, true); + + onDisplayChange(List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_SEVERE, 0.5f))); mTestHandler.flush(); - assertTrue(mClamper.isActive()); - assertEquals(0.5f, mClamper.getBrightnessCap(), FLOAT_TOLERANCE); + + assertModifierState( + PowerManager.BRIGHTNESS_MAX, true, + 0.5f, 0.5f, + true, false); overrideThrottlingData("displayId,1,emergency,0.4"); - mClamper.onDeviceConfigChanged(); + mModifier.onDeviceConfigChanged(); mTestHandler.flush(); - assertFalse(mClamper.isActive()); - assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE); + assertModifierState( + PowerManager.BRIGHTNESS_MAX, true, + PowerManager.BRIGHTNESS_MAX, PowerManager.BRIGHTNESS_MAX, + false, true); overrideThrottlingData("displayId,1,moderate,0.4"); - mClamper.onDeviceConfigChanged(); + mModifier.onDeviceConfigChanged(); mTestHandler.flush(); - assertTrue(mClamper.isActive()); - assertEquals(0.4f, mClamper.getBrightnessCap(), FLOAT_TOLERANCE); + assertModifierState( + PowerManager.BRIGHTNESS_MAX, true, + 0.4f, 0.4f, + true, false); } @Test @@ -191,35 +262,41 @@ public class BrightnessThermalClamperTest { final int severity = PowerManager.THERMAL_STATUS_SEVERE; IThermalEventListener thermalEventListener = captureSkinThermalEventListener(); // Update config to listen to display type sensor. - final SensorData tempSensor = createSensorData("DISPLAY", "VIRTUAL-SKIN-DISPLAY"); - final TestThermalData thermalData = - new TestThermalData( - DISPLAY_ID, - DisplayDeviceConfig.DEFAULT_ID, - List.of(new ThrottlingLevel(severity, 0.5f)), - tempSensor); - mClamper.onDisplayChanged(thermalData); + SensorData tempSensor = createSensorData("DISPLAY", "VIRTUAL-SKIN-DISPLAY"); + + when(mMockDisplayDeviceConfig.getTempSensor()).thenReturn(tempSensor); + onDisplayChange(List.of(new ThrottlingLevel(severity, 0.5f))); mTestHandler.flush(); + verify(mMockThermalService).unregisterThermalEventListener(thermalEventListener); thermalEventListener = captureThermalEventListener(Temperature.TYPE_DISPLAY); - assertFalse(mClamper.isActive()); + assertModifierState( + PowerManager.BRIGHTNESS_MAX, true, + PowerManager.BRIGHTNESS_MAX, PowerManager.BRIGHTNESS_MAX, + false, true); // Verify no throttling triggered when any other sensor notification received. thermalEventListener.notifyThrottling(createSkinTemperature(severity)); mTestHandler.flush(); - assertFalse(mClamper.isActive()); + assertModifierState( + PowerManager.BRIGHTNESS_MAX, true, + PowerManager.BRIGHTNESS_MAX, PowerManager.BRIGHTNESS_MAX, + false, true); thermalEventListener.notifyThrottling(createDisplayTemperature("OTHER-SENSOR", severity)); mTestHandler.flush(); - assertFalse(mClamper.isActive()); - - assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE); + assertModifierState( + PowerManager.BRIGHTNESS_MAX, true, + PowerManager.BRIGHTNESS_MAX, PowerManager.BRIGHTNESS_MAX, + false, true); // Verify throttling triggered when display sensor of given name throttled. thermalEventListener.notifyThrottling(createDisplayTemperature(tempSensor.name, severity)); mTestHandler.flush(); - assertTrue(mClamper.isActive()); - assertEquals(0.5f, mClamper.getBrightnessCap(), FLOAT_TOLERANCE); + assertModifierState( + PowerManager.BRIGHTNESS_MAX, true, + 0.5f, 0.5f, + true, false); } private IThermalEventListener captureSkinThermalEventListener() throws RemoteException { @@ -248,65 +325,54 @@ public class BrightnessThermalClamperTest { DisplayManager.DeviceConfig.KEY_BRIGHTNESS_THROTTLING_DATA, data); } - private class TestInjector extends BrightnessThermalClamper.Injector { - @Override - IThermalService getThermalService() { - return mMockThermalService; - } - - @Override - DeviceConfigParameterProvider getDeviceConfigParameterProvider() { - return new DeviceConfigParameterProvider(mFakeDeviceConfigInterface); - } + private void onDisplayChange(List<ThrottlingLevel> throttlingLevels) { + Map<String, ThermalBrightnessThrottlingData> throttlingLevelsMap = new HashMap<>(); + throttlingLevelsMap.put(DisplayDeviceConfig.DEFAULT_ID, + ThermalBrightnessThrottlingData.create(throttlingLevels)); + when(mMockDisplayDeviceConfig.getThermalBrightnessThrottlingDataMapByThrottlingId()) + .thenReturn(throttlingLevelsMap); + mModifier.onDisplayChanged(ClamperTestUtilsKt.createDisplayDeviceData( + mMockDisplayDeviceConfig, mMockBinder, DISPLAY_ID, DisplayDeviceConfig.DEFAULT_ID)); } - private static class TestThermalData implements BrightnessThermalClamper.ThermalData { - - private final String mUniqueDisplayId; - private final String mDataId; - private final ThermalBrightnessThrottlingData mData; - private final SensorData mTempSensor; - - private TestThermalData() { - this(DISPLAY_ID, DisplayDeviceConfig.DEFAULT_ID, null, - SensorData.loadTempSensorUnspecifiedConfig()); - } - - private TestThermalData(List<ThrottlingLevel> data) { - this(DISPLAY_ID, DisplayDeviceConfig.DEFAULT_ID, data, - SensorData.loadTempSensorUnspecifiedConfig()); - } - - private TestThermalData(String uniqueDisplayId, String dataId, List<ThrottlingLevel> data, - SensorData tempSensor) { - mUniqueDisplayId = uniqueDisplayId; - mDataId = dataId; - mData = ThermalBrightnessThrottlingData.create(data); - mTempSensor = tempSensor; - } - - @NonNull - @Override - public String getUniqueDisplayId() { - return mUniqueDisplayId; - } + private void assertModifierState( + float currentBrightness, + boolean currentSlowChange, + float maxBrightness, float brightness, + boolean isActive, + boolean isSlowChange) { + ModifiersAggregatedState modifierState = new ModifiersAggregatedState(); + DisplayBrightnessState.Builder stateBuilder = DisplayBrightnessState.builder(); + stateBuilder.setBrightness(currentBrightness); + stateBuilder.setIsSlowChange(currentSlowChange); + + int maxBrightnessReason = isActive ? BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL + : BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE; + int modifier = isActive ? BrightnessReason.MODIFIER_THROTTLED : NO_MODIFIER; + + mModifier.applyStateChange(modifierState); + assertThat(modifierState.mMaxBrightness).isEqualTo(maxBrightness); + assertThat(modifierState.mMaxBrightnessReason).isEqualTo(maxBrightnessReason); + + mModifier.apply(mMockRequest, stateBuilder); + + assertThat(stateBuilder.getMaxBrightness()).isWithin(FLOAT_TOLERANCE).of(maxBrightness); + assertThat(stateBuilder.getBrightness()).isWithin(FLOAT_TOLERANCE).of(brightness); + assertThat(stateBuilder.getBrightnessMaxReason()).isEqualTo(maxBrightnessReason); + assertThat(stateBuilder.getBrightnessReason().getModifier()).isEqualTo(modifier); + assertThat(stateBuilder.isSlowChange()).isEqualTo(isSlowChange); + } - @NonNull - @Override - public String getThermalThrottlingDataId() { - return mDataId; - } - @Nullable + private class TestInjector extends BrightnessThermalModifier.Injector { @Override - public ThermalBrightnessThrottlingData getThermalBrightnessThrottlingData() { - return mData; + IThermalService getThermalService() { + return mMockThermalService; } - @NonNull @Override - public SensorData getTempSensor() { - return mTempSensor; + DeviceConfigParameterProvider getDeviceConfigParameterProvider() { + return new DeviceConfigParameterProvider(mFakeDeviceConfigInterface); } } } diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/ClamperTestUtils.kt b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/ClamperTestUtils.kt index 5fd848f6adcc..f21749e0b843 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/ClamperTestUtils.kt +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/ClamperTestUtils.kt @@ -21,6 +21,7 @@ import android.view.Display import com.android.server.display.DisplayDeviceConfig import com.android.server.display.brightness.clamper.BrightnessClamperController.DisplayDeviceData +@JvmOverloads fun createDisplayDeviceData( displayDeviceConfig: DisplayDeviceConfig, displayToken: IBinder, diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobParametersTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobParametersTest.java new file mode 100644 index 000000000000..c8e4f89aaee6 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/job/JobParametersTest.java @@ -0,0 +1,153 @@ +/* + * 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.job; + +import static android.app.job.Flags.FLAG_CLEANUP_EMPTY_JOBS; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; + +import android.app.job.IJobCallback; +import android.app.job.JobParameters; +import android.net.Uri; +import android.os.Parcel; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.platform.test.flag.junit.SetFlagsRule; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoSession; +import org.mockito.quality.Strictness; + +public class JobParametersTest { + private static final String TAG = JobParametersTest.class.getSimpleName(); + private static final int TEST_JOB_ID_1 = 123; + private static final String TEST_NAMESPACE = "TEST_NAMESPACE"; + private static final String TEST_DEBUG_STOP_REASON = "TEST_DEBUG_STOP_REASON"; + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + + private MockitoSession mMockingSession; + @Mock private Parcel mMockParcel; + @Mock private IJobCallback.Stub mMockJobCallbackStub; + + @Before + public void setUp() throws Exception { + mMockingSession = + mockitoSession().initMocks(this).strictness(Strictness.LENIENT).startMocking(); + } + + @After + public void tearDown() { + if (mMockingSession != null) { + mMockingSession.finishMocking(); + } + + when(mMockParcel.readInt()) + .thenReturn(TEST_JOB_ID_1) // Job ID + .thenReturn(0) // No clip data + .thenReturn(0) // No deadline expired + .thenReturn(0) // No network + .thenReturn(0) // No stop reason + .thenReturn(0); // Internal stop reason + when(mMockParcel.readString()) + .thenReturn(TEST_NAMESPACE) // Job namespace + .thenReturn(TEST_DEBUG_STOP_REASON); // Debug stop reason + when(mMockParcel.readPersistableBundle()).thenReturn(null); + when(mMockParcel.readBundle()).thenReturn(null); + when(mMockParcel.readStrongBinder()).thenReturn(mMockJobCallbackStub); + when(mMockParcel.readBoolean()) + .thenReturn(false) // expedited + .thenReturn(false); // user initiated + when(mMockParcel.createTypedArray(any())).thenReturn(new Uri[0]); + when(mMockParcel.createStringArray()).thenReturn(new String[0]); + } + + /** + * Test to verify that the JobParameters created using Non-Parcelable constructor has not + * cleaner attached + */ + @Test + public void testJobParametersNonParcelableConstructor_noCleaner() { + JobParameters jobParameters = + new JobParameters( + null, + TEST_NAMESPACE, + TEST_JOB_ID_1, + null, + null, + null, + 0, + false, + false, + false, + null, + null, + null); + + // Verify that cleaner is not registered + assertThat(jobParameters.getCleanable()).isNull(); + assertThat(jobParameters.getJobCleanupCallback()).isNull(); + } + + /** + * Test to verify that the JobParameters created using Parcelable constructor has not cleaner + * attached + */ + @Test + public void testJobParametersParcelableConstructor_noCleaner() { + JobParameters jobParameters = JobParameters.CREATOR.createFromParcel(mMockParcel); + + // Verify that cleaner is not registered + assertThat(jobParameters.getCleanable()).isNull(); + assertThat(jobParameters.getJobCleanupCallback()).isNull(); + } + + /** Test to verify that the JobParameters Cleaner is disabled */ + @RequiresFlagsEnabled(FLAG_CLEANUP_EMPTY_JOBS) + @Test + public void testCleanerWithLeakedJobCleanerDisabled_flagCleanupEmptyJobsEnabled() { + // Inject real JobCallbackCleanup + JobParameters jobParameters = JobParameters.CREATOR.createFromParcel(mMockParcel); + + // Enable the cleaner + jobParameters.enableCleaner(); + + // Verify the cleaner is enabled + assertThat(jobParameters.getCleanable()).isNotNull(); + assertThat(jobParameters.getJobCleanupCallback()).isNotNull(); + assertThat(jobParameters.getJobCleanupCallback().isCleanerEnabled()).isTrue(); + + // Disable the cleaner + jobParameters.disableCleaner(); + + // Verify the cleaner is disabled + assertThat(jobParameters.getCleanable()).isNull(); + assertThat(jobParameters.getJobCleanupCallback()).isNull(); + } +} diff --git a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java index 1cd61e90126e..e5005d1beed4 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java @@ -44,6 +44,8 @@ import android.content.Context; import android.graphics.PointF; import android.os.Looper; import android.os.SystemClock; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; import android.testing.DexmakerShareClassLoaderRule; import android.view.InputDevice; import android.view.MotionEvent; @@ -56,6 +58,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.server.accessibility.AccessibilityManagerService; import com.android.server.accessibility.AccessibilityTraceManager; import com.android.server.accessibility.EventStreamTransformation; +import com.android.server.accessibility.Flags; import com.android.server.accessibility.utils.GestureLogParser; import com.android.server.testutils.OffsettableClock; @@ -119,6 +122,9 @@ public class TouchExplorerTest { public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule = new DexmakerShareClassLoaderRule(); + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + /** * {@link TouchExplorer#sendDownForAllNotInjectedPointers} injecting events with the same object * is resulting {@link ArgumentCaptor} to capture events with last state. Before implementation @@ -154,18 +160,43 @@ public class TouchExplorerTest { mHandler = new TestHandler(); mTouchExplorer = new TouchExplorer(mContext, mMockAms, null, mHandler); mTouchExplorer.setNext(mCaptor); + // Start TouchExplorer in the state where it has already reset InputDispatcher so that + // all tests do not start with an irrelevant ACTION_CANCEL. + mTouchExplorer.setHasResetInputDispatcherState(true); } @Test public void testOneFingerMove_shouldInjectHoverEvents() { - goFromStateClearTo(STATE_TOUCH_EXPLORING_1FINGER); - // Wait for transiting to touch exploring state. + triggerTouchExplorationWithOneFingerDownMoveUp(); + assertCapturedEvents(ACTION_HOVER_ENTER, ACTION_HOVER_MOVE, ACTION_HOVER_EXIT); + assertState(STATE_TOUCH_EXPLORING); + } + + @Test + @EnableFlags(Flags.FLAG_RESET_INPUT_DISPATCHER_BEFORE_FIRST_TOUCH_EXPLORATION) + public void testStartTouchExploration_shouldResetInputDispatcherStateWithActionCancel() { + // Start TouchExplorer in the state where it has *not yet* reset InputDispatcher. + mTouchExplorer.setHasResetInputDispatcherState(false); + // Trigger touch exploration twice, with a handler fast-forward in between so TouchExplorer + // treats these as two separate interactions. + triggerTouchExplorationWithOneFingerDownMoveUp(); + mHandler.fastForward(2 * USER_INTENT_TIMEOUT); + triggerTouchExplorationWithOneFingerDownMoveUp(); + + assertCapturedEvents( + ACTION_CANCEL, // Only one ACTION_CANCEL before the first touch exploration + ACTION_HOVER_ENTER, ACTION_HOVER_MOVE, ACTION_HOVER_EXIT, + ACTION_HOVER_ENTER, ACTION_HOVER_MOVE, ACTION_HOVER_EXIT); + assertState(STATE_TOUCH_EXPLORING); + } + + private void triggerTouchExplorationWithOneFingerDownMoveUp() { + send(downEvent()); + // Fast forward so that TouchExplorer's timeouts transition us to the touch exploring state. mHandler.fastForward(2 * USER_INTENT_TIMEOUT); moveEachPointers(mLastEvent, p(10, 10)); send(mLastEvent); - goToStateClearFrom(STATE_TOUCH_EXPLORING_1FINGER); - assertCapturedEvents(ACTION_HOVER_ENTER, ACTION_HOVER_MOVE, ACTION_HOVER_EXIT); - assertState(STATE_TOUCH_EXPLORING); + send(upEvent()); } /** diff --git a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java index 6ace9f14757c..fca0cfbc7d2f 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java @@ -20,6 +20,7 @@ import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM; import static com.android.server.hdmi.HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP; import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_BOOT_UP; +import static com.android.server.hdmi.HdmiCecFeatureAction.DELAY_GIVE_AUDIO_STATUS; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.TruthJUnit.assume; @@ -582,6 +583,9 @@ public abstract class BaseAbsoluteVolumeBehaviorTest { ); mTestLooper.dispatchAll(); + mTestLooper.moveTimeForward(DELAY_GIVE_AUDIO_STATUS); + mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getResultMessages()).contains( HdmiCecMessageBuilder.buildUserControlPressed(getLogicalAddress(), getSystemAudioDeviceLogicalAddress(), CEC_KEYCODE_VOLUME_UP)); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java b/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java index 673140390ae7..ec44a918f8e8 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java @@ -17,6 +17,7 @@ package com.android.server.hdmi; import static com.android.server.hdmi.HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP; +import static com.android.server.hdmi.HdmiCecFeatureAction.DELAY_GIVE_AUDIO_STATUS; import static com.google.common.truth.Truth.assertThat; @@ -223,6 +224,9 @@ public abstract class BaseTvToAudioSystemAvbTest extends BaseAbsoluteVolumeBehav ); mTestLooper.dispatchAll(); + mTestLooper.moveTimeForward(DELAY_GIVE_AUDIO_STATUS); + mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getResultMessages()).contains( HdmiCecMessageBuilder.buildUserControlPressed(getLogicalAddress(), getSystemAudioDeviceLogicalAddress(), CEC_KEYCODE_VOLUME_UP)); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java index a5f1fcd01aa1..2d957401e6bd 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java @@ -30,6 +30,7 @@ import static com.android.server.hdmi.HdmiControlService.STANDBY_SCREEN_OFF; import static com.android.server.hdmi.HdmiControlService.WAKE_UP_SCREEN_ON; import static com.android.server.hdmi.RequestActiveSourceAction.TIMEOUT_WAIT_FOR_LAUNCHERX_API_CALL_MS; import static com.android.server.hdmi.RoutingControlAction.TIMEOUT_ROUTING_INFORMATION_MS; +import static com.android.server.hdmi.RequestSadAction.RETRY_COUNTER_MAX; import static com.google.common.truth.Truth.assertThat; @@ -273,13 +274,12 @@ public class HdmiCecLocalDeviceTvTest { assertThat(mNativeWrapper.getResultMessages()).doesNotContain(reportArcInitiated); // Finish querying SADs - assertThat(mNativeWrapper.getResultMessages()).contains(SAD_QUERY); - mNativeWrapper.clearResultMessages(); - mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS); - mTestLooper.dispatchAll(); - assertThat(mNativeWrapper.getResultMessages()).contains(SAD_QUERY); - mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS); - mTestLooper.dispatchAll(); + for (int i = 0; i <= RETRY_COUNTER_MAX; ++i) { + assertThat(mNativeWrapper.getResultMessages()).contains(SAD_QUERY); + mNativeWrapper.clearResultMessages(); + mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS); + mTestLooper.dispatchAll(); + } assertThat(mNativeWrapper.getResultMessages()).contains(reportArcInitiated); mNativeWrapper.clearResultMessages(); @@ -685,15 +685,43 @@ public class HdmiCecLocalDeviceTvTest { assertThat(mNativeWrapper.getResultMessages()).doesNotContain(reportArcInitiated); // Finish querying SADs - assertThat(mNativeWrapper.getResultMessages()).contains(SAD_QUERY); - mNativeWrapper.clearResultMessages(); - mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS); + for (int i = 0; i <= RETRY_COUNTER_MAX; ++i) { + assertThat(mNativeWrapper.getResultMessages()).contains(SAD_QUERY); + mNativeWrapper.clearResultMessages(); + mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS); + mTestLooper.dispatchAll(); + } + + assertThat(mNativeWrapper.getResultMessages()).contains(reportArcInitiated); + } + + @Test + public void handleInitiateArc_arcAlreadyEstablished_noRequestSad() { + // Emulate Audio device on port 0x2000 (supports ARC) + mNativeWrapper.setPortConnectionStatus(2, true); + HdmiCecMessage reportPhysicalAddress = + HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( + ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); + mNativeWrapper.onCecMessage(reportPhysicalAddress); mTestLooper.dispatchAll(); - assertThat(mNativeWrapper.getResultMessages()).contains(SAD_QUERY); - mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS); + + assertThat(mHdmiCecLocalDeviceTv.isArcEstablished()).isFalse(); + + HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildInitiateArc( + ADDR_AUDIO_SYSTEM, + ADDR_TV); + mNativeWrapper.onCecMessage(requestArcInitiation); mTestLooper.dispatchAll(); - assertThat(mNativeWrapper.getResultMessages()).contains(reportArcInitiated); + // Finish querying SADs + for (int i = 0; i <= RETRY_COUNTER_MAX; ++i) { + assertThat(mNativeWrapper.getResultMessages()).contains(SAD_QUERY); + mNativeWrapper.clearResultMessages(); + mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS); + mTestLooper.dispatchAll(); + } + + assertThat(mHdmiCecLocalDeviceTv.isArcEstablished()).isTrue(); } @Test @@ -970,13 +998,12 @@ public class HdmiCecLocalDeviceTvTest { // <Report ARC Initiated> should only be sent after SAD querying is done assertThat(mNativeWrapper.getResultMessages()).doesNotContain(reportArcInitiated); // Finish querying SADs - assertThat(mNativeWrapper.getResultMessages()).contains(SAD_QUERY); - mNativeWrapper.clearResultMessages(); - mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS); - mTestLooper.dispatchAll(); - assertThat(mNativeWrapper.getResultMessages()).contains(SAD_QUERY); - mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS); - mTestLooper.dispatchAll(); + for (int i = 0; i <= RETRY_COUNTER_MAX; ++i) { + assertThat(mNativeWrapper.getResultMessages()).contains(SAD_QUERY); + mNativeWrapper.clearResultMessages(); + mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS); + mTestLooper.dispatchAll(); + } assertThat(mNativeWrapper.getResultMessages()).contains(reportArcInitiated); mNativeWrapper.clearResultMessages(); @@ -1171,13 +1198,12 @@ public class HdmiCecLocalDeviceTvTest { mTestLooper.dispatchAll(); // Finish querying SADs - assertThat(mNativeWrapper.getResultMessages()).contains(SAD_QUERY); - mNativeWrapper.clearResultMessages(); - mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS); - mTestLooper.dispatchAll(); - assertThat(mNativeWrapper.getResultMessages()).contains(SAD_QUERY); - mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS); - mTestLooper.dispatchAll(); + for (int i = 0; i <= RETRY_COUNTER_MAX; ++i) { + assertThat(mNativeWrapper.getResultMessages()).contains(SAD_QUERY); + mNativeWrapper.clearResultMessages(); + mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS); + mTestLooper.dispatchAll(); + } // ARC should be established after RequestSadAction is finished assertThat(mNativeWrapper.getResultMessages()).contains(reportArcInitiated); @@ -1327,13 +1353,12 @@ public class HdmiCecLocalDeviceTvTest { assertThat(mNativeWrapper.getResultMessages()).doesNotContain(reportArcInitiated); // Finish querying SADs - assertThat(mNativeWrapper.getResultMessages()).contains(SAD_QUERY); - mNativeWrapper.clearResultMessages(); - mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS); - mTestLooper.dispatchAll(); - assertThat(mNativeWrapper.getResultMessages()).contains(SAD_QUERY); - mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS); - mTestLooper.dispatchAll(); + for (int i = 0; i <= RETRY_COUNTER_MAX; ++i) { + assertThat(mNativeWrapper.getResultMessages()).contains(SAD_QUERY); + mNativeWrapper.clearResultMessages(); + mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS); + mTestLooper.dispatchAll(); + } assertThat(mNativeWrapper.getResultMessages()).contains(reportArcInitiated); } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java index f8e465c4c36f..4cf293758519 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java @@ -18,6 +18,7 @@ package com.android.server.hdmi; import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY; import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM; +import static com.android.server.hdmi.RequestSadAction.RETRY_COUNTER_MAX; import static com.google.common.truth.Truth.assertThat; @@ -144,7 +145,7 @@ public class RequestSadActionTest { } @Test - public void noResponse_queryAgainOnce_emptyResult() { + public void noResponse_queryAgain_emptyResult() { RequestSadAction action = new RequestSadAction(mHdmiCecLocalDeviceTv, ADDR_AUDIO_SYSTEM, mCallback); action.start(); @@ -154,13 +155,13 @@ public class RequestSadActionTest { HdmiCecMessage expected1 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor( mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM, CODECS_TO_QUERY_1.stream().mapToInt(i -> i).toArray()); - assertThat(mNativeWrapper.getResultMessages()).contains(expected1); - mNativeWrapper.clearResultMessages(); - mTestLooper.moveTimeForward(TIMEOUT_MS); - mTestLooper.dispatchAll(); - assertThat(mNativeWrapper.getResultMessages()).contains(expected1); - mTestLooper.moveTimeForward(TIMEOUT_MS); - mTestLooper.dispatchAll(); + + for (int i = 0; i <= RETRY_COUNTER_MAX; ++i) { + assertThat(mNativeWrapper.getResultMessages()).contains(expected1); + mNativeWrapper.clearResultMessages(); + mTestLooper.moveTimeForward(TIMEOUT_MS); + mTestLooper.dispatchAll(); + } assertThat(mSupportedSads).isNotNull(); assertThat(mSupportedSads.size()).isEqualTo(0); @@ -507,7 +508,7 @@ public class RequestSadActionTest { } @Test - public void invalidMessageLength_queryAgainOnce() { + public void invalidMessageLength_queryAgain() { RequestSadAction action = new RequestSadAction(mHdmiCecLocalDeviceTv, ADDR_AUDIO_SYSTEM, mCallback); action.start(); @@ -524,16 +525,13 @@ public class RequestSadActionTest { 0x27, 0x20, 0x0A}; HdmiCecMessage response1 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor( Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_1); - assertThat(mNativeWrapper.getResultMessages()).contains(expected1); - mNativeWrapper.clearResultMessages(); - action.processCommand(response1); - mTestLooper.dispatchAll(); - mTestLooper.moveTimeForward(TIMEOUT_MS); - mTestLooper.dispatchAll(); - assertThat(mNativeWrapper.getResultMessages()).contains(expected1); - mNativeWrapper.clearResultMessages(); - mTestLooper.moveTimeForward(TIMEOUT_MS); - mTestLooper.dispatchAll(); + + for (int i = 0; i <= RETRY_COUNTER_MAX; ++i) { + assertThat(mNativeWrapper.getResultMessages()).contains(expected1); + mNativeWrapper.clearResultMessages(); + mTestLooper.moveTimeForward(TIMEOUT_MS); + mTestLooper.dispatchAll(); + } assertThat(mSupportedSads).isNotNull(); assertThat(mSupportedSads.size()).isEqualTo(0); diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index f0850af5fc2e..51e0c33ff705 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -1456,21 +1456,21 @@ public class SubscriptionManager { public static final int SERVICE_CAPABILITY_MAX = SERVICE_CAPABILITY_DATA; /** - * Bitmask for {@code SERVICE_CAPABILITY_VOICE}. + * Bitmask for {@link #SERVICE_CAPABILITY_VOICE}. * @hide */ public static final int SERVICE_CAPABILITY_VOICE_BITMASK = serviceCapabilityToBitmask(SERVICE_CAPABILITY_VOICE); /** - * Bitmask for {@code SERVICE_CAPABILITY_SMS}. + * Bitmask for {@link #SERVICE_CAPABILITY_SMS}. * @hide */ public static final int SERVICE_CAPABILITY_SMS_BITMASK = serviceCapabilityToBitmask(SERVICE_CAPABILITY_SMS); /** - * Bitmask for {@code SERVICE_CAPABILITY_DATA}. + * Bitmask for {@link #SERVICE_CAPABILITY_DATA}. * @hide */ public static final int SERVICE_CAPABILITY_DATA_BITMASK = diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 3ff1e2ca8dfb..3e226ccf2737 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -6907,7 +6907,6 @@ public class TelephonyManager { return false; } - // TODO(b/316183370): replace all @code with @link in javadoc after feature is released /** * @return true if the current device is "voice capable". * <p> @@ -6921,10 +6920,10 @@ public class TelephonyManager { * PackageManager.FEATURE_TELEPHONY system feature, which is available * on any device with a telephony radio, even if the device is * data-only. - * @deprecated Replaced by {@code #isDeviceVoiceCapable()}. Starting from Android 15, voice + * @deprecated Replaced by {@link #isDeviceVoiceCapable()}. Starting from Android 15, voice * capability may also be overridden by carriers for a given subscription. For voice capable - * device (when {@code #isDeviceVoiceCapable} return {@code true}), caller should check for - * subscription-level voice capability as well. See {@code #isDeviceVoiceCapable} for details. + * device (when {@link #isDeviceVoiceCapable} return {@code true}), caller should check for + * subscription-level voice capability as well. See {@link #isDeviceVoiceCapable} for details. */ @Deprecated public boolean isVoiceCapable() { @@ -6946,8 +6945,8 @@ public class TelephonyManager { * <p> * Starting from Android 15, voice capability may also be overridden by carrier for a given * subscription on a voice capable device. To check if a subscription is "voice capable", - * call method {@code SubscriptionInfo#getServiceCapabilities()} and check if - * {@code SubscriptionManager#SERVICE_CAPABILITY_VOICE} is included. + * call method {@link SubscriptionInfo#getServiceCapabilities()} and check if + * {@link SubscriptionManager#SERVICE_CAPABILITY_VOICE} is included. * * @see SubscriptionInfo#getServiceCapabilities() */ @@ -6964,10 +6963,10 @@ public class TelephonyManager { * <p> * Note: Voicemail waiting sms, cell broadcasting sms, and MMS are * disabled when device doesn't support sms. - * @deprecated Replaced by {@code #isDeviceSmsCapable()}. Starting from Android 15, SMS + * @deprecated Replaced by {@link #isDeviceSmsCapable()}. Starting from Android 15, SMS * capability may also be overridden by carriers for a given subscription. For SMS capable - * device (when {@code #isDeviceSmsCapable} return {@code true}), caller should check for - * subscription-level SMS capability as well. See {@code #isDeviceSmsCapable} for details. + * device (when {@link #isDeviceSmsCapable} return {@code true}), caller should check for + * subscription-level SMS capability as well. See {@link #isDeviceSmsCapable} for details. */ @Deprecated public boolean isSmsCapable() { @@ -6986,8 +6985,8 @@ public class TelephonyManager { * <p> * Starting from Android 15, SMS capability may also be overridden by carriers for a given * subscription on an SMS capable device. To check if a subscription is "SMS capable", - * call method {@code SubscriptionInfo#getServiceCapabilities()} and check if - * {@code SubscriptionManager#SERVICE_CAPABILITY_SMS} is included. + * call method {@link SubscriptionInfo#getServiceCapabilities()} and check if + * {@link SubscriptionManager#SERVICE_CAPABILITY_SMS} is included. * * @see SubscriptionInfo#getServiceCapabilities() */ diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java index 3944b8e0d0cc..284e2bd8aa6c 100644 --- a/telephony/java/android/telephony/satellite/SatelliteManager.java +++ b/telephony/java/android/telephony/satellite/SatelliteManager.java @@ -420,6 +420,14 @@ public final class SatelliteManager { @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_DISABLE_IN_PROGRESS = 28; + /** + * Enabling satellite is in progress. + * + * @hide + */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + public static final int SATELLITE_RESULT_ENABLE_IN_PROGRESS = 29; + /** @hide */ @IntDef(prefix = {"SATELLITE_RESULT_"}, value = { SATELLITE_RESULT_SUCCESS, @@ -450,7 +458,8 @@ public final class SatelliteManager { SATELLITE_RESULT_LOCATION_DISABLED, SATELLITE_RESULT_LOCATION_NOT_AVAILABLE, SATELLITE_RESULT_EMERGENCY_CALL_IN_PROGRESS, - SATELLITE_RESULT_DISABLE_IN_PROGRESS + SATELLITE_RESULT_DISABLE_IN_PROGRESS, + SATELLITE_RESULT_ENABLE_IN_PROGRESS }) @Retention(RetentionPolicy.SOURCE) public @interface SatelliteResult {} diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index e852e6bbb756..e57c207a0b3e 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -3394,4 +3394,19 @@ interface ITelephony { @JavaPassthrough(annotation="@android.annotation.RequiresPermission(" + "android.Manifest.permission.SATELLITE_COMMUNICATION)") void provisionSatellite(in List<SatelliteSubscriberInfo> list, in ResultReceiver result); + + /** + * This API can be used by only CTS to override the cached value for the device overlay config + * value : + * config_satellite_gateway_service_package and + * config_satellite_carrier_roaming_esos_provisioned_class. + * These values are set before sending an intent to broadcast there are any change to list of + * subscriber informations. + * + * @param name the name is one of the following that constitute an intent. + * Component package name, or component class name. + * @return {@code true} if the setting is successful, {@code false} otherwise. + * @hide + */ + boolean setSatelliteSubscriberIdListChangedIntentComponent(in String name); } diff --git a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java b/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java index 27cc923a97db..189de6bdb44a 100644 --- a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java +++ b/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java @@ -16,8 +16,6 @@ package com.android.internal.protolog; -import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; - import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; @@ -30,7 +28,6 @@ import static org.mockito.Mockito.when; import static java.io.File.createTempFile; -import android.content.Context; import android.os.SystemClock; import android.platform.test.annotations.Presubmit; import android.tools.ScenarioBuilder; @@ -45,6 +42,7 @@ import android.util.proto.ProtoInputStream; import androidx.test.platform.app.InstrumentationRegistry; +import com.android.internal.protolog.ProtoLogConfigurationService.ViewerConfigFileTracer; import com.android.internal.protolog.common.IProtoLogGroup; import com.android.internal.protolog.common.LogDataType; import com.android.internal.protolog.common.LogLevel; @@ -53,11 +51,11 @@ import com.google.common.truth.Truth; import org.junit.After; import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; import perfetto.protos.Protolog; import perfetto.protos.ProtologCommon; @@ -76,6 +74,7 @@ import java.util.concurrent.atomic.AtomicInteger; @RunWith(JUnit4.class) public class PerfettoProtoLogImplTest { private static final String TEST_PROTOLOG_DATASOURCE_NAME = "test.android.protolog"; + private static final String MOCK_VIEWER_CONFIG_FILE = "my/mock/viewer/config/file.pb"; private final File mTracingDirectory = InstrumentationRegistry.getInstrumentation() .getTargetContext().getFilesDir(); @@ -92,29 +91,19 @@ public class PerfettoProtoLogImplTest { new TraceConfig(false, true, false) ); - private ProtoLogConfigurationService mProtoLogConfigurationService; - private PerfettoProtoLogImpl mProtoLog; - private Protolog.ProtoLogViewerConfig.Builder mViewerConfigBuilder; - private File mFile; - private Runnable mCacheUpdater; + private static ProtoLogConfigurationService sProtoLogConfigurationService; + private static PerfettoProtoLogImpl sProtoLog; + private static Protolog.ProtoLogViewerConfig.Builder sViewerConfigBuilder; + private static Runnable sCacheUpdater; - private ProtoLogViewerConfigReader mReader; + private static ProtoLogViewerConfigReader sReader; public PerfettoProtoLogImplTest() throws IOException { } - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - final Context testContext = getInstrumentation().getContext(); - mFile = testContext.getFileStreamPath("tracing_test.dat"); - //noinspection ResultOfMethodCallIgnored - mFile.delete(); - - TestProtoLogGroup.TEST_GROUP.setLogToLogcat(false); - TestProtoLogGroup.TEST_GROUP.setLogToProto(false); - - mViewerConfigBuilder = Protolog.ProtoLogViewerConfig.newBuilder() + @BeforeClass + public static void setUp() throws Exception { + sViewerConfigBuilder = Protolog.ProtoLogViewerConfig.newBuilder() .addGroups( Protolog.ProtoLogViewerConfig.Group.newBuilder() .setId(1) @@ -160,33 +149,52 @@ public class PerfettoProtoLogImplTest { ViewerConfigInputStreamProvider viewerConfigInputStreamProvider = Mockito.mock( ViewerConfigInputStreamProvider.class); Mockito.when(viewerConfigInputStreamProvider.getInputStream()) - .thenAnswer(it -> new ProtoInputStream(mViewerConfigBuilder.build().toByteArray())); + .thenAnswer(it -> new ProtoInputStream(sViewerConfigBuilder.build().toByteArray())); - mCacheUpdater = () -> {}; - mReader = Mockito.spy(new ProtoLogViewerConfigReader(viewerConfigInputStreamProvider)); + sCacheUpdater = () -> {}; + sReader = Mockito.spy(new ProtoLogViewerConfigReader(viewerConfigInputStreamProvider)); final ProtoLogDataSourceBuilder dataSourceBuilder = (onStart, onFlush, onStop) -> new ProtoLogDataSource( onStart, onFlush, onStop, TEST_PROTOLOG_DATASOURCE_NAME); - mProtoLogConfigurationService = - new ProtoLogConfigurationService(dataSourceBuilder); - mProtoLog = new PerfettoProtoLogImpl( - viewerConfigInputStreamProvider, mReader, () -> mCacheUpdater.run(), - TestProtoLogGroup.values(), dataSourceBuilder, mProtoLogConfigurationService); + final ViewerConfigFileTracer tracer = (dataSource, viewerConfigFilePath) -> { + Utils.dumpViewerConfig(dataSource, () -> { + if (!viewerConfigFilePath.equals(MOCK_VIEWER_CONFIG_FILE)) { + throw new RuntimeException( + "Unexpected viewer config file path provided"); + } + return new ProtoInputStream(sViewerConfigBuilder.build().toByteArray()); + }); + }; + sProtoLogConfigurationService = new ProtoLogConfigurationService(dataSourceBuilder, tracer); + + if (android.tracing.Flags.clientSideProtoLogging()) { + sProtoLog = new PerfettoProtoLogImpl( + MOCK_VIEWER_CONFIG_FILE, sReader, () -> sCacheUpdater.run(), + TestProtoLogGroup.values(), dataSourceBuilder, sProtoLogConfigurationService); + } else { + sProtoLog = new PerfettoProtoLogImpl( + viewerConfigInputStreamProvider, sReader, () -> sCacheUpdater.run(), + TestProtoLogGroup.values(), dataSourceBuilder, sProtoLogConfigurationService); + } + } + + @Before + public void before() { + Mockito.reset(sReader); + + TestProtoLogGroup.TEST_GROUP.setLogToLogcat(false); + TestProtoLogGroup.TEST_GROUP.setLogToProto(false); } @After public void tearDown() { - if (mFile != null) { - //noinspection ResultOfMethodCallIgnored - mFile.delete(); - } ProtoLogImpl.setSingleInstance(null); } @Test public void isEnabled_returnsFalseByDefault() { - assertFalse(mProtoLog.isProtoEnabled()); + assertFalse(sProtoLog.isProtoEnabled()); } @Test @@ -196,7 +204,7 @@ public class PerfettoProtoLogImplTest { .build(); try { traceMonitor.start(); - assertTrue(mProtoLog.isProtoEnabled()); + assertTrue(sProtoLog.isProtoEnabled()); } finally { traceMonitor.stop(mWriter); } @@ -209,12 +217,12 @@ public class PerfettoProtoLogImplTest { .build(); try { traceMonitor.start(); - assertTrue(mProtoLog.isProtoEnabled()); + assertTrue(sProtoLog.isProtoEnabled()); } finally { traceMonitor.stop(mWriter); } - assertFalse(mProtoLog.isProtoEnabled()); + assertFalse(sProtoLog.isProtoEnabled()); } @Test @@ -226,15 +234,15 @@ public class PerfettoProtoLogImplTest { traceMonitor.start(); // Shouldn't be logging anything except WTF unless explicitly requested in the group // override. - mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1, + sProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1, LogDataType.BOOLEAN, new Object[]{true}); - mProtoLog.log(LogLevel.VERBOSE, TestProtoLogGroup.TEST_GROUP, 2, + sProtoLog.log(LogLevel.VERBOSE, TestProtoLogGroup.TEST_GROUP, 2, LogDataType.BOOLEAN, new Object[]{true}); - mProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP, 3, + sProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP, 3, LogDataType.BOOLEAN, new Object[]{true}); - mProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP, 4, + sProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP, 4, LogDataType.BOOLEAN, new Object[]{true}); - mProtoLog.log(LogLevel.WTF, TestProtoLogGroup.TEST_GROUP, 5, + sProtoLog.log(LogLevel.WTF, TestProtoLogGroup.TEST_GROUP, 5, LogDataType.BOOLEAN, new Object[]{true}); } finally { traceMonitor.stop(mWriter); @@ -258,15 +266,15 @@ public class PerfettoProtoLogImplTest { ).build(); try { traceMonitor.start(); - mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1, + sProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1, LogDataType.BOOLEAN, new Object[]{true}); - mProtoLog.log(LogLevel.VERBOSE, TestProtoLogGroup.TEST_GROUP, 2, + sProtoLog.log(LogLevel.VERBOSE, TestProtoLogGroup.TEST_GROUP, 2, LogDataType.BOOLEAN, new Object[]{true}); - mProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP, 3, + sProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP, 3, LogDataType.BOOLEAN, new Object[]{true}); - mProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP, 4, + sProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP, 4, LogDataType.BOOLEAN, new Object[]{true}); - mProtoLog.log(LogLevel.WTF, TestProtoLogGroup.TEST_GROUP, 5, + sProtoLog.log(LogLevel.WTF, TestProtoLogGroup.TEST_GROUP, 5, LogDataType.BOOLEAN, new Object[]{true}); } finally { traceMonitor.stop(mWriter); @@ -294,15 +302,15 @@ public class PerfettoProtoLogImplTest { ).build(); try { traceMonitor.start(); - mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1, + sProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1, LogDataType.BOOLEAN, new Object[]{true}); - mProtoLog.log(LogLevel.VERBOSE, TestProtoLogGroup.TEST_GROUP, 2, + sProtoLog.log(LogLevel.VERBOSE, TestProtoLogGroup.TEST_GROUP, 2, LogDataType.BOOLEAN, new Object[]{true}); - mProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP, 3, + sProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP, 3, LogDataType.BOOLEAN, new Object[]{true}); - mProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP, 4, + sProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP, 4, LogDataType.BOOLEAN, new Object[]{true}); - mProtoLog.log(LogLevel.WTF, TestProtoLogGroup.TEST_GROUP, 5, + sProtoLog.log(LogLevel.WTF, TestProtoLogGroup.TEST_GROUP, 5, LogDataType.BOOLEAN, new Object[]{true}); } finally { traceMonitor.stop(mWriter); @@ -324,15 +332,15 @@ public class PerfettoProtoLogImplTest { .build(); try { traceMonitor.start(); - mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1, + sProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1, LogDataType.BOOLEAN, new Object[]{true}); - mProtoLog.log(LogLevel.VERBOSE, TestProtoLogGroup.TEST_GROUP, 2, + sProtoLog.log(LogLevel.VERBOSE, TestProtoLogGroup.TEST_GROUP, 2, LogDataType.BOOLEAN, new Object[]{true}); - mProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP, 3, + sProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP, 3, LogDataType.BOOLEAN, new Object[]{true}); - mProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP, 4, + sProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP, 4, LogDataType.BOOLEAN, new Object[]{true}); - mProtoLog.log(LogLevel.WTF, TestProtoLogGroup.TEST_GROUP, 5, + sProtoLog.log(LogLevel.WTF, TestProtoLogGroup.TEST_GROUP, 5, LogDataType.BOOLEAN, new Object[]{true}); } finally { traceMonitor.stop(mWriter); @@ -351,8 +359,8 @@ public class PerfettoProtoLogImplTest { @Test public void log_logcatEnabled() { - when(mReader.getViewerString(anyLong())).thenReturn("test %b %d %% 0x%x %s %f"); - PerfettoProtoLogImpl implSpy = Mockito.spy(mProtoLog); + when(sReader.getViewerString(anyLong())).thenReturn("test %b %d %% 0x%x %s %f"); + PerfettoProtoLogImpl implSpy = Mockito.spy(sProtoLog); TestProtoLogGroup.TEST_GROUP.setLogToLogcat(true); TestProtoLogGroup.TEST_GROUP.setLogToProto(false); @@ -363,13 +371,13 @@ public class PerfettoProtoLogImplTest { verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq( LogLevel.INFO), eq("test true 10000 % 0x7530 test 3.0E-6")); - verify(mReader).getViewerString(eq(1234L)); + verify(sReader).getViewerString(eq(1234L)); } @Test public void log_logcatEnabledInvalidMessage() { - when(mReader.getViewerString(anyLong())).thenReturn("test %b %d %% %x %s %f"); - PerfettoProtoLogImpl implSpy = Mockito.spy(mProtoLog); + when(sReader.getViewerString(anyLong())).thenReturn("test %b %d %% %x %s %f"); + PerfettoProtoLogImpl implSpy = Mockito.spy(sProtoLog); TestProtoLogGroup.TEST_GROUP.setLogToLogcat(true); TestProtoLogGroup.TEST_GROUP.setLogToProto(false); @@ -381,29 +389,32 @@ public class PerfettoProtoLogImplTest { LogLevel.INFO), eq("FORMAT_ERROR \"test %b %d %% %x %s %f\", " + "args=(true, 10000, 1.0E-4, 2.0E-5, test)")); - verify(mReader).getViewerString(eq(1234L)); + verify(sReader).getViewerString(eq(1234L)); } @Test public void log_logcatEnabledNoMessage() { - when(mReader.getViewerString(anyLong())).thenReturn(null); - PerfettoProtoLogImpl implSpy = Mockito.spy(mProtoLog); + when(sReader.getViewerString(anyLong())).thenReturn(null); + PerfettoProtoLogImpl implSpy = Mockito.spy(sProtoLog); TestProtoLogGroup.TEST_GROUP.setLogToLogcat(true); TestProtoLogGroup.TEST_GROUP.setLogToProto(false); - implSpy.log( + var assertion = assertThrows(RuntimeException.class, () -> implSpy.log( LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, - new Object[]{5}); + new Object[]{5})); - verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq( - LogLevel.INFO), eq("UNKNOWN MESSAGE args = (5)")); - verify(mReader).getViewerString(eq(1234L)); + verify(implSpy, never()).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq( + LogLevel.INFO), any()); + verify(sReader).getViewerString(eq(1234L)); + + Truth.assertThat(assertion).hasMessageThat() + .contains("Failed to get log message with hash 1234 and args (5)"); } @Test public void log_logcatDisabled() { - when(mReader.getViewerString(anyLong())).thenReturn("test %d"); - PerfettoProtoLogImpl implSpy = Mockito.spy(mProtoLog); + when(sReader.getViewerString(anyLong())).thenReturn("test %d"); + PerfettoProtoLogImpl implSpy = Mockito.spy(sProtoLog); TestProtoLogGroup.TEST_GROUP.setLogToLogcat(false); implSpy.log( @@ -411,7 +422,7 @@ public class PerfettoProtoLogImplTest { new Object[]{5}); verify(implSpy, never()).passToLogcat(any(), any(), any()); - verify(mReader, never()).getViewerString(anyLong()); + verify(sReader, never()).getViewerString(anyLong()); } @Test @@ -426,11 +437,12 @@ public class PerfettoProtoLogImplTest { long before; long after; try { + assertFalse(sProtoLog.isProtoEnabled()); traceMonitor.start(); - assertTrue(mProtoLog.isProtoEnabled()); + assertTrue(sProtoLog.isProtoEnabled()); before = SystemClock.elapsedRealtimeNanos(); - mProtoLog.log( + sProtoLog.log( LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, messageHash, 0b1110101001010100, new Object[]{"test", 1, 2, 3, 0.4, 0.5, 0.6, true}); @@ -448,7 +460,8 @@ public class PerfettoProtoLogImplTest { Truth.assertThat(protolog.messages.getFirst().getTimestamp().getElapsedNanos()) .isAtMost(after); Truth.assertThat(protolog.messages.getFirst().getMessage()) - .isEqualTo("My test message :: test, 2, 4, 6, 0.400000, 5.000000e-01, 0.6, true"); + .isEqualTo( + "My test message :: test, 1, 2, 3, 0.400000, 5.000000e-01, 0.6, true"); } @Test @@ -460,10 +473,10 @@ public class PerfettoProtoLogImplTest { long after; try { traceMonitor.start(); - assertTrue(mProtoLog.isProtoEnabled()); + assertTrue(sProtoLog.isProtoEnabled()); before = SystemClock.elapsedRealtimeNanos(); - mProtoLog.log( + sProtoLog.log( LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, "My test message :: %s, %d, %x, %f, %b", "test", 1, 3, 0.4, true); @@ -481,7 +494,7 @@ public class PerfettoProtoLogImplTest { Truth.assertThat(protolog.messages.getFirst().getTimestamp().getElapsedNanos()) .isAtMost(after); Truth.assertThat(protolog.messages.getFirst().getMessage()) - .isEqualTo("My test message :: test, 2, 6, 0.400000, true"); + .isEqualTo("My test message :: test, 1, 3, 0.400000, true"); } @Test @@ -491,7 +504,7 @@ public class PerfettoProtoLogImplTest { .build(); try { traceMonitor.start(); - mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1, + sProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1, LogDataType.BOOLEAN, new Object[]{true}); } finally { traceMonitor.stop(mWriter); @@ -507,7 +520,7 @@ public class PerfettoProtoLogImplTest { private long addMessageToConfig(ProtologCommon.ProtoLogLevel logLevel, String message) { final long messageId = new Random().nextLong(); - mViewerConfigBuilder.addMessages(Protolog.ProtoLogViewerConfig.MessageData.newBuilder() + sViewerConfigBuilder.addMessages(Protolog.ProtoLogViewerConfig.MessageData.newBuilder() .setMessageId(messageId) .setMessage(message) .setLevel(logLevel) @@ -530,7 +543,7 @@ public class PerfettoProtoLogImplTest { try { traceMonitor.start(); before = SystemClock.elapsedRealtimeNanos(); - mProtoLog.log( + sProtoLog.log( LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, messageHash, 0b01100100, new Object[]{"test", 1, 0.1, true}); @@ -550,7 +563,7 @@ public class PerfettoProtoLogImplTest { .build(); try { traceMonitor.start(); - mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1, + sProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1, 0b11, new Object[]{true}); } finally { traceMonitor.stop(mWriter); @@ -575,7 +588,7 @@ public class PerfettoProtoLogImplTest { try { traceMonitor.start(); - ProtoLogImpl.setSingleInstance(mProtoLog); + ProtoLogImpl.setSingleInstance(sProtoLog); ProtoLogImpl.d(TestProtoLogGroup.TEST_GROUP, 1, 0b11, true); } finally { @@ -599,7 +612,7 @@ public class PerfettoProtoLogImplTest { @Test public void cacheIsUpdatedWhenTracesStartAndStop() { final AtomicInteger cacheUpdateCallCount = new AtomicInteger(0); - mCacheUpdater = cacheUpdateCallCount::incrementAndGet; + sCacheUpdater = cacheUpdateCallCount::incrementAndGet; PerfettoTraceMonitor traceMonitor1 = PerfettoTraceMonitor.newBuilder() .enableProtoLog(true, @@ -641,17 +654,17 @@ public class PerfettoProtoLogImplTest { @Test public void isEnabledUpdatesBasedOnRunningTraces() { - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.DEBUG)) + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.DEBUG)) .isFalse(); - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.VERBOSE)) + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.VERBOSE)) .isFalse(); - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.INFO)) + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.INFO)) .isFalse(); - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WARN)) + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WARN)) .isFalse(); - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR)) + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR)) .isFalse(); - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF)).isFalse(); + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF)).isFalse(); PerfettoTraceMonitor traceMonitor1 = PerfettoTraceMonitor.newBuilder().enableProtoLog(true, @@ -670,65 +683,65 @@ public class PerfettoProtoLogImplTest { try { traceMonitor1.start(); - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.DEBUG)) + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.DEBUG)) .isFalse(); - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.VERBOSE)) + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.VERBOSE)) .isFalse(); - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.INFO)) + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.INFO)) .isFalse(); - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WARN)) + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WARN)) .isTrue(); - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR)) + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR)) .isTrue(); - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF)) + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF)) .isTrue(); try { traceMonitor2.start(); - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.DEBUG)) + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.DEBUG)) .isTrue(); - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.VERBOSE)).isTrue(); - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.INFO)) + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.INFO)) .isTrue(); - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WARN)) + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WARN)) .isTrue(); - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR)) + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR)) .isTrue(); - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF)) + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF)) .isTrue(); } finally { traceMonitor2.stop(mWriter); } - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.DEBUG)) + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.DEBUG)) .isFalse(); - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.VERBOSE)) + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.VERBOSE)) .isFalse(); - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.INFO)) + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.INFO)) .isFalse(); - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WARN)) + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WARN)) .isTrue(); - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR)) + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR)) .isTrue(); - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF)) + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF)) .isTrue(); } finally { traceMonitor1.stop(mWriter); } - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.DEBUG)) + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.DEBUG)) .isFalse(); - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.VERBOSE)) + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.VERBOSE)) .isFalse(); - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.INFO)) + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.INFO)) .isFalse(); - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WARN)) + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WARN)) .isFalse(); - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR)) + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR)) .isFalse(); - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF)) + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF)) .isFalse(); } @@ -741,7 +754,7 @@ public class PerfettoProtoLogImplTest { try { traceMonitor.start(); - mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, + sProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, "My test null string: %s", (Object) null); } finally { traceMonitor.stop(mWriter); @@ -764,7 +777,7 @@ public class PerfettoProtoLogImplTest { try { traceMonitor.start(); - mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, + sProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, "My null args: %d, %f, %b", null, null, null); } finally { traceMonitor.stop(mWriter); @@ -775,7 +788,7 @@ public class PerfettoProtoLogImplTest { Truth.assertThat(protolog.messages).hasSize(1); Truth.assertThat(protolog.messages.get(0).getMessage()) - .isEqualTo("My null args: 0, 0, false"); + .isEqualTo("My null args: 0, 0.000000, false"); } @Test @@ -798,7 +811,7 @@ public class PerfettoProtoLogImplTest { traceMonitor1.start(); traceMonitor2.start(); - mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1, + sProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1, LogDataType.BOOLEAN, new Object[]{true}); } finally { traceMonitor1.stop(mWriter); @@ -827,12 +840,12 @@ public class PerfettoProtoLogImplTest { .build(); try { traceMonitor.start(); - mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, - "This message should not be logged"); - mProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP, - "This message should logged %d", 123); - mProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP, - "This message should also be logged %d", 567); + sProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, + "This message should not be logged"); + sProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP, + "This message should be logged %d", 123); + sProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP, + "This message should also be logged %d", 567); } finally { traceMonitor.stop(mWriter); } @@ -845,7 +858,7 @@ public class PerfettoProtoLogImplTest { Truth.assertThat(protolog.messages.get(0).getLevel()) .isEqualTo(LogLevel.WARN); Truth.assertThat(protolog.messages.get(0).getMessage()) - .isEqualTo("This message should logged 123"); + .isEqualTo("This message should be logged 123"); Truth.assertThat(protolog.messages.get(1).getLevel()) .isEqualTo(LogLevel.ERROR); @@ -853,6 +866,19 @@ public class PerfettoProtoLogImplTest { .isEqualTo("This message should also be logged 567"); } + @Test + public void throwsOnLogToLogcatForProcessedMessageMissingLoadedDefinition() { + TestProtoLogGroup.TEST_GROUP.setLogToLogcat(true); + var protolog = new PerfettoProtoLogImpl(TestProtoLogGroup.values()); + + var exception = assertThrows(RuntimeException.class, () -> { + protolog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 123, 0, new Object[0]); + }); + + Truth.assertThat(exception).hasMessageThat() + .contains("Failed to get log message with hash 123"); + } + private enum TestProtoLogGroup implements IProtoLogGroup { TEST_GROUP(true, true, false, "TEST_TAG"); diff --git a/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java b/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java index 887630b03a8c..b5cc5536532c 100644 --- a/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java +++ b/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java @@ -59,7 +59,6 @@ import android.os.HandlerExecutor; import android.os.ParcelUuid; import android.os.PersistableBundle; import android.os.test.TestLooper; -import android.platform.test.flag.junit.SetFlagsRule; import android.telephony.CarrierConfigManager; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; @@ -73,10 +72,7 @@ import android.util.ArraySet; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import com.android.internal.telephony.flags.Flags; - import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -133,8 +129,6 @@ public class TelephonySubscriptionTrackerTest { TEST_SUBID_TO_CARRIER_CONFIG_MAP = Collections.unmodifiableMap(subIdToCarrierConfigMap); } - @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); - @NonNull private final Context mContext; @NonNull private final TestLooper mTestLooper; @@ -193,7 +187,6 @@ public class TelephonySubscriptionTrackerTest { @Before public void setUp() throws Exception { - mSetFlagsRule.enableFlags(Flags.FLAG_FIX_CRASH_ON_GETTING_CONFIG_WHEN_PHONE_IS_GONE); doReturn(2).when(mTelephonyManager).getActiveModemCount(); mCallback = mock(TelephonySubscriptionTrackerCallback.class); diff --git a/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestRedirect.java b/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestRedirect.java new file mode 100644 index 000000000000..bc9471b84b97 --- /dev/null +++ b/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestRedirect.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.hosttest.annotation; + +import static java.lang.annotation.ElementType.METHOD; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * THIS ANNOTATION IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY + * QUESTIONS ABOUT IT. + * @hide + */ +@Target({METHOD}) +@Retention(RetentionPolicy.CLASS) +public @interface HostSideTestRedirect { +} diff --git a/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestNativeSubstitutionClass.java b/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestRedirectionClass.java index 9c8138351eb5..28ad236a66f3 100644 --- a/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestNativeSubstitutionClass.java +++ b/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestRedirectionClass.java @@ -30,6 +30,6 @@ import java.lang.annotation.Target; */ @Target({TYPE}) @Retention(RetentionPolicy.CLASS) -public @interface HostSideTestNativeSubstitutionClass { +public @interface HostSideTestRedirectionClass { String value(); } diff --git a/tools/hoststubgen/hoststubgen/hoststubgen-standard-options.txt b/tools/hoststubgen/hoststubgen/hoststubgen-standard-options.txt index e72c9a41d796..eba8e62c7270 100644 --- a/tools/hoststubgen/hoststubgen/hoststubgen-standard-options.txt +++ b/tools/hoststubgen/hoststubgen/hoststubgen-standard-options.txt @@ -27,8 +27,11 @@ --substitute-annotation android.hosttest.annotation.HostSideTestSubstitute ---native-substitute-annotation - android.hosttest.annotation.HostSideTestNativeSubstitutionClass +--redirect-annotation + android.hosttest.annotation.HostSideTestRedirect + +--redirection-class-annotation + android.hosttest.annotation.HostSideTestRedirectionClass --class-load-hook-annotation android.hosttest.annotation.HostSideTestClassLoadHook diff --git a/tools/hoststubgen/hoststubgen/invoketest/hoststubgen-invoke-test.sh b/tools/hoststubgen/hoststubgen/invoketest/hoststubgen-invoke-test.sh index 5f0368a48c09..084448d0a797 100755 --- a/tools/hoststubgen/hoststubgen/invoketest/hoststubgen-invoke-test.sh +++ b/tools/hoststubgen/hoststubgen/invoketest/hoststubgen-invoke-test.sh @@ -100,8 +100,10 @@ run_hoststubgen() { android.hosttest.annotation.HostSideTestRemove \ --substitute-annotation \ android.hosttest.annotation.HostSideTestSubstitute \ - --native-substitute-annotation \ - android.hosttest.annotation.HostSideTestNativeSubstitutionClass \ + --redirect-annotation \ + android.hosttest.annotation.HostSideTestRedirect \ + --redirection-class-annotation \ + android.hosttest.annotation.HostSideTestRedirectionClass \ --class-load-hook-annotation \ android.hosttest.annotation.HostSideTestClassLoadHook \ --keep-static-initializer-annotation \ @@ -223,4 +225,4 @@ EXTRA_ARGS="--in-jar abc" run_hoststubgen_for_failure "Duplicate arg" \ echo "All tests passed" -exit 0
\ No newline at end of file +exit 0 diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt index 0f38fe7d5068..34aaaa9cfa9f 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt @@ -24,8 +24,9 @@ import com.android.hoststubgen.filters.DefaultHookInjectingFilter import com.android.hoststubgen.filters.FilterPolicy import com.android.hoststubgen.filters.FilterRemapper import com.android.hoststubgen.filters.ImplicitOutputFilter -import com.android.hoststubgen.filters.NativeFilter +import com.android.hoststubgen.filters.KeepNativeFilter import com.android.hoststubgen.filters.OutputFilter +import com.android.hoststubgen.filters.SanitizationFilter import com.android.hoststubgen.filters.createFilterFromTextPolicyFile import com.android.hoststubgen.filters.printAsTextPolicy import com.android.hoststubgen.utils.ClassFilter @@ -134,7 +135,7 @@ class HostStubGen(val options: HostStubGenOptions) { var filter: OutputFilter = ConstantFilter(options.defaultPolicy.get, "default-by-options") // Next, we build a filter that preserves all native methods by default - filter = NativeFilter(allClasses, filter) + filter = KeepNativeFilter(allClasses, filter) // Next, we need a filter that resolves "class-wide" policies. // This is used when a member (methods, fields, nested classes) don't get any polices @@ -166,11 +167,12 @@ class HostStubGen(val options: HostStubGenOptions) { options.throwAnnotations, options.removeAnnotations, options.substituteAnnotations, - options.nativeSubstituteAnnotations, + options.redirectAnnotations, + options.redirectionClassAnnotations, options.classLoadHookAnnotations, options.keepStaticInitializerAnnotations, annotationAllowedClassesFilter, - filter, + filter ) // Next, "text based" filter, which allows to override polices without touching @@ -182,6 +184,9 @@ class HostStubGen(val options: HostStubGenOptions) { // Apply the implicit filter. filter = ImplicitOutputFilter(errors, allClasses, filter) + // Add a final sanitization step. + filter = SanitizationFilter(errors, allClasses, filter) + return filter } diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt index 1cedcc349c51..057a52cc06d0 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt @@ -85,9 +85,10 @@ class HostStubGenOptions( var throwAnnotations: MutableSet<String> = mutableSetOf(), var removeAnnotations: MutableSet<String> = mutableSetOf(), var keepClassAnnotations: MutableSet<String> = mutableSetOf(), + var redirectAnnotations: MutableSet<String> = mutableSetOf(), var substituteAnnotations: MutableSet<String> = mutableSetOf(), - var nativeSubstituteAnnotations: MutableSet<String> = mutableSetOf(), + var redirectionClassAnnotations: MutableSet<String> = mutableSetOf(), var classLoadHookAnnotations: MutableSet<String> = mutableSetOf(), var keepStaticInitializerAnnotations: MutableSet<String> = mutableSetOf(), @@ -186,8 +187,11 @@ class HostStubGenOptions( "--substitute-annotation" -> ret.substituteAnnotations.addUniqueAnnotationArg() - "--native-substitute-annotation" -> - ret.nativeSubstituteAnnotations.addUniqueAnnotationArg() + "--redirect-annotation" -> + ret.redirectAnnotations.addUniqueAnnotationArg() + + "--redirection-class-annotation" -> + ret.redirectionClassAnnotations.addUniqueAnnotationArg() "--class-load-hook-annotation" -> ret.classLoadHookAnnotations.addUniqueAnnotationArg() @@ -275,7 +279,7 @@ class HostStubGenOptions( removeAnnotations=$removeAnnotations, keepClassAnnotations=$keepClassAnnotations, substituteAnnotations=$substituteAnnotations, - nativeSubstituteAnnotations=$nativeSubstituteAnnotations, + nativeSubstituteAnnotations=$redirectionClassAnnotations, classLoadHookAnnotations=$classLoadHookAnnotations, keepStaticInitializerAnnotations=$keepStaticInitializerAnnotations, packageRedirects=$packageRedirects, 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 7197e0e79861..a02082d12934 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt @@ -29,33 +29,24 @@ import org.objectweb.asm.tree.MethodNode /** Name of the class initializer method. */ -val CLASS_INITIALIZER_NAME = "<clinit>" +const val CLASS_INITIALIZER_NAME = "<clinit>" /** Descriptor of the class initializer method. */ -val CLASS_INITIALIZER_DESC = "()V" +const val CLASS_INITIALIZER_DESC = "()V" /** Name of constructors. */ -val CTOR_NAME = "<init>" +const val CTOR_NAME = "<init>" /** - * Find any of [anyAnnotations] from the list of visible / invisible annotations. + * Find any of [set] from the list of visible / invisible annotations. */ fun findAnyAnnotation( - anyAnnotations: Set<String>, - visibleAnnotations: List<AnnotationNode>?, - invisibleAnnotations: List<AnnotationNode>?, - ): AnnotationNode? { - for (an in visibleAnnotations ?: emptyList()) { - if (anyAnnotations.contains(an.desc)) { - return an - } - } - for (an in invisibleAnnotations ?: emptyList()) { - if (anyAnnotations.contains(an.desc)) { - return an - } - } - return null + set: Set<String>, + visibleAnnotations: List<AnnotationNode>?, + invisibleAnnotations: List<AnnotationNode>?, +): AnnotationNode? { + return visibleAnnotations?.find { it.desc in set } + ?: invisibleAnnotations?.find { it.desc in set } } fun ClassNode.findAnyAnnotation(set: Set<String>): AnnotationNode? { @@ -70,6 +61,27 @@ fun FieldNode.findAnyAnnotation(set: Set<String>): AnnotationNode? { return findAnyAnnotation(set, this.visibleAnnotations, this.invisibleAnnotations) } +fun findAllAnnotations( + set: Set<String>, + visibleAnnotations: List<AnnotationNode>?, + invisibleAnnotations: List<AnnotationNode>? +): List<AnnotationNode> { + return (visibleAnnotations ?: emptyList()).filter { it.desc in set } + + (invisibleAnnotations ?: emptyList()).filter { it.desc in set } +} + +fun ClassNode.findAllAnnotations(set: Set<String>): List<AnnotationNode> { + return findAllAnnotations(set, this.visibleAnnotations, this.invisibleAnnotations) +} + +fun MethodNode.findAllAnnotations(set: Set<String>): List<AnnotationNode> { + return findAllAnnotations(set, this.visibleAnnotations, this.invisibleAnnotations) +} + +fun FieldNode.findAllAnnotations(set: Set<String>): List<AnnotationNode> { + return findAllAnnotations(set, this.visibleAnnotations, this.invisibleAnnotations) +} + fun <T> findAnnotationValueAsObject( an: AnnotationNode, propertyName: String, diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AnnotationBasedFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AnnotationBasedFilter.kt index 38a41b26dcfc..a6b8cdb0c80b 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AnnotationBasedFilter.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AnnotationBasedFilter.kt @@ -18,12 +18,15 @@ package com.android.hoststubgen.filters import com.android.hoststubgen.ClassParseException import com.android.hoststubgen.HostStubGenErrors import com.android.hoststubgen.InvalidAnnotationException -import com.android.hoststubgen.addNonNullElement +import com.android.hoststubgen.addLists import com.android.hoststubgen.asm.CLASS_INITIALIZER_DESC import com.android.hoststubgen.asm.CLASS_INITIALIZER_NAME import com.android.hoststubgen.asm.ClassNodes +import com.android.hoststubgen.asm.findAllAnnotations import com.android.hoststubgen.asm.findAnnotationValueAsString import com.android.hoststubgen.asm.findAnyAnnotation +import com.android.hoststubgen.asm.getPackageNameFromFullClassName +import com.android.hoststubgen.asm.resolveClassNameWithDefaultPackage import com.android.hoststubgen.asm.toHumanReadableClassName import com.android.hoststubgen.asm.toHumanReadableMethodName import com.android.hoststubgen.asm.toJvmClassName @@ -46,7 +49,8 @@ class AnnotationBasedFilter( throwAnnotations_: Set<String>, removeAnnotations_: Set<String>, substituteAnnotations_: Set<String>, - nativeSubstituteAnnotations_: Set<String>, + redirectAnnotations_: Set<String>, + redirectionClassAnnotations_: Set<String>, classLoadHookAnnotations_: Set<String>, keepStaticInitializerAnnotations_: Set<String>, private val annotationAllowedClassesFilter: ClassFilter, @@ -56,8 +60,10 @@ class AnnotationBasedFilter( private val keepClassAnnotations = convertToInternalNames(keepClassAnnotations_) private val throwAnnotations = convertToInternalNames(throwAnnotations_) private val removeAnnotations = convertToInternalNames(removeAnnotations_) + private val redirectAnnotations = convertToInternalNames(redirectAnnotations_) private val substituteAnnotations = convertToInternalNames(substituteAnnotations_) - private val nativeSubstituteAnnotations = convertToInternalNames(nativeSubstituteAnnotations_) + private val redirectionClassAnnotations = + convertToInternalNames(redirectionClassAnnotations_) private val classLoadHookAnnotations = convertToInternalNames(classLoadHookAnnotations_) private val keepStaticInitializerAnnotations = convertToInternalNames(keepStaticInitializerAnnotations_) @@ -67,11 +73,12 @@ class AnnotationBasedFilter( keepClassAnnotations + throwAnnotations + removeAnnotations + + redirectAnnotations + substituteAnnotations /** All the annotations we use. */ private val allAnnotations = visibilityAnnotations + - nativeSubstituteAnnotations + + redirectionClassAnnotations + classLoadHookAnnotations + keepStaticInitializerAnnotations @@ -84,8 +91,9 @@ class AnnotationBasedFilter( keepClassAnnotations_ + throwAnnotations_ + removeAnnotations_ + + redirectAnnotations_ + substituteAnnotations_ + - nativeSubstituteAnnotations_ + + redirectionClassAnnotations_ + classLoadHookAnnotations_ + keepStaticInitializerAnnotations_ ) @@ -99,6 +107,7 @@ class AnnotationBasedFilter( in substituteAnnotations -> FilterPolicy.Substitute.withReason(REASON_ANNOTATION) in throwAnnotations -> FilterPolicy.Throw.withReason(REASON_ANNOTATION) in removeAnnotations -> FilterPolicy.Remove.withReason(REASON_ANNOTATION) + in redirectAnnotations -> FilterPolicy.Redirect.withReason(REASON_ANNOTATION) else -> null } } @@ -129,13 +138,6 @@ class AnnotationBasedFilter( descriptor: String ): FilterPolicyWithReason { val cn = classes.getClass(className) - - if (methodName == CLASS_INITIALIZER_NAME && descriptor == CLASS_INITIALIZER_DESC) { - if (cn.findAnyAnnotation(keepStaticInitializerAnnotations) != null) { - return FilterPolicy.Keep.withReason(REASON_ANNOTATION) - } - } - return getAnnotationPolicy(cn).methodPolicies[MethodKey(methodName, descriptor)] ?: super.getPolicyForMethod(className, methodName, descriptor) } @@ -150,22 +152,14 @@ class AnnotationBasedFilter( ?: super.getRenameTo(className, methodName, descriptor) } - override fun getNativeSubstitutionClass(className: String): String? { - classes.getClass(className).let { cn -> - cn.findAnyAnnotation(nativeSubstituteAnnotations)?.let { an -> - return getAnnotationField(an, "value")?.toJvmClassName() - } - } - return null + override fun getRedirectionClass(className: String): String? { + val cn = classes.getClass(className) + return getAnnotationPolicy(cn).redirectionClass } override fun getClassLoadHooks(className: String): List<String> { - val e = classes.getClass(className).let { cn -> - cn.findAnyAnnotation(classLoadHookAnnotations)?.let { an -> - getAnnotationField(an, "value")?.toHumanReadableMethodName() - } - } - return addNonNullElement(super.getClassLoadHooks(className), e) + val cn = classes.getClass(className) + return addLists(super.getClassLoadHooks(className), getAnnotationPolicy(cn).classLoadHooks) } private data class MethodKey(val name: String, val desc: String) @@ -195,6 +189,8 @@ class AnnotationBasedFilter( val fieldPolicies = mutableMapOf<String, FilterPolicyWithReason>() val methodPolicies = mutableMapOf<MethodKey, FilterPolicyWithReason>() val renamedMethods = mutableMapOf<MethodKey, String>() + val redirectionClass: String? + val classLoadHooks: List<String> init { val allowAnnotation = annotationAllowedClassesFilter.matches(cn.name) @@ -204,6 +200,16 @@ class AnnotationBasedFilter( "class", cn.name ) classPolicy = cn.findAnyAnnotation(visibilityAnnotations)?.policy + redirectionClass = cn.findAnyAnnotation(redirectionClassAnnotations)?.let { an -> + getAnnotationField(an, "value")?.let { resolveRelativeClass(cn, it) } + } + classLoadHooks = cn.findAllAnnotations(classLoadHookAnnotations).mapNotNull { an -> + getAnnotationField(an, "value")?.toHumanReadableMethodName() + } + if (cn.findAnyAnnotation(keepStaticInitializerAnnotations) != null) { + methodPolicies[MethodKey(CLASS_INITIALIZER_NAME, CLASS_INITIALIZER_DESC)] = + FilterPolicy.Keep.withReason(REASON_ANNOTATION) + } for (fn in cn.fields ?: emptyList()) { detectInvalidAnnotations( @@ -297,25 +303,36 @@ class AnnotationBasedFilter( ) } } - } - /** - * Return the (String) value of 'value' parameter from an annotation. - */ - private fun getAnnotationField( - an: AnnotationNode, - name: String, - required: Boolean = true - ): String? { - try { - val suffix = findAnnotationValueAsString(an, name) - if (suffix == null && required) { - errors.onErrorFound("Annotation \"${an.desc}\" must have field $name") + /** + * Return the (String) value of 'value' parameter from an annotation. + */ + private fun getAnnotationField( + an: AnnotationNode, + name: String, + required: Boolean = true + ): String? { + try { + val suffix = findAnnotationValueAsString(an, name) + if (suffix == null && required) { + errors.onErrorFound("Annotation \"${an.desc}\" must have field $name") + } + return suffix + } catch (e: ClassParseException) { + errors.onErrorFound(e.message!!) + return null } - return suffix - } catch (e: ClassParseException) { - errors.onErrorFound(e.message!!) - return null + } + + /** + * Resolve the full class name if the class is relative + */ + private fun resolveRelativeClass( + cn: ClassNode, + name: String + ): String { + val packageName = getPackageNameFromFullClassName(cn.name) + return resolveClassNameWithDefaultPackage(name, packageName).toJvmClassName() } } 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 8ee3a946a21c..f8bb526d0a86 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.isNative /** * This is used as the second last fallback filter. This filter propagates the class-wide policy @@ -88,16 +87,7 @@ class ClassWidePolicyPropagatingFilter( methodName: String, descriptor: String ): FilterPolicyWithReason { - return outermostFilter.getNativeSubstitutionClass(className)?.let { - // First check native substitution - classes.findMethod(className, methodName, descriptor)?.let { mn -> - if (mn.isNative()) { - FilterPolicy.NativeSubstitute.withReason("class-wide in $className") - } else { - null - } - } - } ?: getClassWidePolicy(className, resolve = true) - ?: super.getPolicyForMethod(className, methodName, descriptor) + return getClassWidePolicy(className, resolve = true) + ?: super.getPolicyForMethod(className, methodName, descriptor) } -}
\ No newline at end of file +} diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/DelegatingFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/DelegatingFilter.kt index 6fcffb89924a..b8b0d8a31268 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/DelegatingFilter.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/DelegatingFilter.kt @@ -72,8 +72,8 @@ abstract class DelegatingFilter( return fallback.getRenameTo(className, methodName, descriptor) } - override fun getNativeSubstitutionClass(className: String): String? { - return fallback.getNativeSubstitutionClass(className) + override fun getRedirectionClass(className: String): String? { + return fallback.getRedirectionClass(className) } override fun getClassLoadHooks(className: String): List<String> { diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicy.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicy.kt index ab03874cf43d..2f2f81b05ad1 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicy.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicy.kt @@ -33,9 +33,9 @@ enum class FilterPolicy { Substitute, /** - * Only usable with methods. Replace a native method with a "substitution" method, + * Only usable with methods. Redirect a method to a method in the substitution class. */ - NativeSubstitute, + Redirect, /** * Only usable with methods. The item will be kept in the impl jar file, but when called, @@ -102,8 +102,7 @@ enum class FilterPolicy { val isSupported: Boolean get() { return when (this) { - // TODO: handle native method with no substitution as being unsupported - Keep, KeepClass, Substitute, NativeSubstitute -> true + Keep, KeepClass, Substitute, Redirect -> true else -> false } } @@ -111,7 +110,7 @@ enum class FilterPolicy { val isMethodRewriteBody: Boolean get() { return when (this) { - NativeSubstitute, Throw, Ignore -> true + Redirect, Throw, Ignore -> true else -> false } } diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/InMemoryOutputFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/InMemoryOutputFilter.kt index 2e144f5513bc..59fa464a7212 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/InMemoryOutputFilter.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/InMemoryOutputFilter.kt @@ -19,6 +19,7 @@ import com.android.hoststubgen.addNonNullElement import com.android.hoststubgen.asm.ClassNodes import com.android.hoststubgen.asm.toHumanReadableClassName import com.android.hoststubgen.asm.toHumanReadableMethodName +import com.android.hoststubgen.asm.toJvmClassName import com.android.hoststubgen.log // TODO: Validate all input names. @@ -29,7 +30,7 @@ class InMemoryOutputFilter( ) : DelegatingFilter(fallback) { private val mPolicies: MutableMap<String, FilterPolicyWithReason> = mutableMapOf() private val mRenames: MutableMap<String, String> = mutableMapOf() - private val mNativeSubstitutionClasses: MutableMap<String, String> = mutableMapOf() + private val mRedirectionClasses: MutableMap<String, String> = mutableMapOf() private val mClassLoadHooks: MutableMap<String, String> = mutableMapOf() private fun getClassKey(className: String): String { @@ -115,17 +116,17 @@ class InMemoryOutputFilter( mRenames[getMethodKey(className, methodName, descriptor)] = toName } - override fun getNativeSubstitutionClass(className: String): String? { - return mNativeSubstitutionClasses[getClassKey(className)] - ?: super.getNativeSubstitutionClass(className) + override fun getRedirectionClass(className: String): String? { + return mRedirectionClasses[getClassKey(className)] + ?: super.getRedirectionClass(className) } - fun setNativeSubstitutionClass(from: String, to: String) { + fun setRedirectionClass(from: String, to: String) { checkClass(from) - // Native substitute classes may be provided from other jars, so we can't do this check. + // Redirection classes may be provided from other jars, so we can't do this check. // ensureClassExists(to) - mNativeSubstitutionClasses[getClassKey(from)] = to.toHumanReadableClassName() + mRedirectionClasses[getClassKey(from)] = to.toJvmClassName() } override fun getClassLoadHooks(className: String): List<String> { @@ -136,4 +137,4 @@ class InMemoryOutputFilter( fun setClassLoadHook(className: String, methodName: String) { mClassLoadHooks[getClassKey(className)] = methodName.toHumanReadableMethodName() } -}
\ No newline at end of file +} diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/NativeFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/KeepNativeFilter.kt index bd719310719b..00e7d77fa6e7 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/NativeFilter.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/KeepNativeFilter.kt @@ -18,7 +18,12 @@ package com.android.hoststubgen.filters import com.android.hoststubgen.asm.ClassNodes import com.android.hoststubgen.asm.isNative -class NativeFilter( +/** + * For native methods that weren't handled by outer filters, we keep it so that + * native method registration will not crash at runtime. Ideally we shouldn't need + * this, but in practice unsupported native method registrations do occur. + */ +class KeepNativeFilter( private val classes: ClassNodes, fallback: OutputFilter ) : DelegatingFilter(fallback) { @@ -28,8 +33,6 @@ class NativeFilter( descriptor: String, ): FilterPolicyWithReason { return classes.findMethod(className, methodName, descriptor)?.let { mn -> - // For native methods that weren't handled by outer filters, - // we keep it so that native method registration will not crash. if (mn.isNative()) { FilterPolicy.Keep.withReason("native-preserve") } else { diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/OutputFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/OutputFilter.kt index 1049e2bf94cf..f99ce906240a 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/OutputFilter.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/OutputFilter.kt @@ -35,10 +35,6 @@ abstract class OutputFilter { * using it. */ open var outermostFilter: OutputFilter = this - get() = field - set(value) { - field = value - } abstract fun getPolicyForClass(className: String): FilterPolicyWithReason @@ -60,13 +56,13 @@ abstract class OutputFilter { } /** - * Return a "native substitution class" name for a given class. + * Return a "redirection class" name for a given class. * - * The result will be in a "human readable" form. (e.g. uses '.'s instead of '/'s) + * The result will be in a JVM internal form. (e.g. uses '/'s instead of '.'s) * - * (which corresponds to @HostSideTestNativeSubstitutionClass of the standard annotations.) + * (which corresponds to @HostSideTestRedirectClass of the standard annotations.) */ - open fun getNativeSubstitutionClass(className: String): String? { + open fun getRedirectionClass(className: String): String? { return null } diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/SanitizationFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/SanitizationFilter.kt new file mode 100644 index 000000000000..18a1e16bcf3a --- /dev/null +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/SanitizationFilter.kt @@ -0,0 +1,57 @@ +/* + * 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.filters + +import com.android.hoststubgen.HostStubGenErrors +import com.android.hoststubgen.asm.ClassNodes +import com.android.hoststubgen.asm.toHumanReadableClassName +import com.android.hoststubgen.log + +/** + * Check whether the policies in the inner layers make sense, and sanitize the results. + */ +class SanitizationFilter( + private val errors: HostStubGenErrors, + private val classes: ClassNodes, + fallback: OutputFilter +) : DelegatingFilter(fallback) { + override fun getPolicyForMethod( + className: String, + methodName: String, + descriptor: String + ): FilterPolicyWithReason { + val policy = super.getPolicyForMethod(className, methodName, descriptor) + if (policy.policy == FilterPolicy.Redirect) { + // Check whether the hosting class has a redirection class + if (getRedirectionClass(className) == null) { + errors.onErrorFound("Method $methodName$descriptor requires a redirection " + + "class set on ${className.toHumanReadableClassName()}") + } + } + return policy + } + + override fun getRedirectionClass(className: String): String? { + return super.getRedirectionClass(className)?.also { clazz -> + if (classes.findClass(clazz) == null) { + log.w("Redirection class $clazz not found. Class must be available at runtime.") + } else if (outermostFilter.getPolicyForClass(clazz).policy != FilterPolicy.KeepClass) { + // If the class exists, it must have a KeepClass policy. + errors.onErrorFound("Redirection class $clazz must have @KeepWholeClass.") + } + } + } +} diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt index 14fd82b271e1..073b503401b5 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt @@ -142,9 +142,9 @@ fun createFilterFromTextPolicyFile( throw ParseException( "Special class can't have a substitution") } - // It's a native-substitution. + // It's a redirection class. val toClass = fields[2].substring(1) - imf.setNativeSubstitutionClass(className, toClass) + imf.setRedirectionClass(className, toClass) } else if (fields[2].startsWith("~")) { if (classType != SpecialClass.NotSpecial) { // We could support it, but not needed at least for now. @@ -350,6 +350,7 @@ private fun parsePolicy(s: String): FilterPolicy { "r", "remove" -> FilterPolicy.Remove "kc", "keepclass" -> FilterPolicy.KeepClass "i", "ignore" -> FilterPolicy.Ignore + "rdr", "redirect" -> FilterPolicy.Redirect else -> { if (s.startsWith("@")) { FilterPolicy.Substitute diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt index 41ba9286ef1e..261ef59c45c7 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt @@ -21,8 +21,6 @@ import com.android.hoststubgen.LogLevel import com.android.hoststubgen.asm.ClassNodes import com.android.hoststubgen.asm.UnifiedVisitor import com.android.hoststubgen.asm.getPackageNameFromFullClassName -import com.android.hoststubgen.asm.resolveClassNameWithDefaultPackage -import com.android.hoststubgen.asm.toJvmClassName import com.android.hoststubgen.filters.FilterPolicy import com.android.hoststubgen.filters.FilterPolicyWithReason import com.android.hoststubgen.filters.OutputFilter @@ -57,7 +55,7 @@ abstract class BaseAdapter( protected lateinit var currentPackageName: String protected lateinit var currentClassName: String - protected var nativeSubstitutionClass: String? = null + protected var redirectionClass: String? = null protected lateinit var classPolicy: FilterPolicyWithReason override fun visit( @@ -72,34 +70,13 @@ abstract class BaseAdapter( currentClassName = name currentPackageName = getPackageNameFromFullClassName(name) classPolicy = filter.getPolicyForClass(currentClassName) + redirectionClass = filter.getRedirectionClass(currentClassName) log.d("[%s] visit: %s (package: %s)", this.javaClass.simpleName, name, currentPackageName) log.indent() log.v("Emitting class: %s", name) log.indent() - filter.getNativeSubstitutionClass(currentClassName)?.let { className -> - val fullClassName = resolveClassNameWithDefaultPackage(className, currentPackageName) - .toJvmClassName() - log.d(" NativeSubstitutionClass: $fullClassName") - if (classes.findClass(fullClassName) == null) { - log.w( - "Native substitution class $fullClassName not found. Class must be " + - "available at runtime." - ) - } else { - // If the class exists, it must have a KeepClass policy. - if (filter.getPolicyForClass(fullClassName).policy != FilterPolicy.KeepClass) { - // TODO: Use real annotation name. - options.errors.onErrorFound( - "Native substitution class $fullClassName should have @Keep." - ) - } - } - - nativeSubstitutionClass = fullClassName - } - // Inject annotations to generated classes. UnifiedVisitor.on(this).visitAnnotation(HostStubGenProcessedAsKeep.CLASS_DESCRIPTOR, true) } diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt index 057d653d7c45..567a69e43b58 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt @@ -184,9 +184,12 @@ class ImplGeneratingAdapter( return IgnoreMethodAdapter(descriptor, forceCreateBody, innerVisitor) .withAnnotation(HostStubGenProcessedAsIgnore.CLASS_DESCRIPTOR) } - FilterPolicy.NativeSubstitute -> { - log.v("Rewriting native method...") - return NativeSubstitutingMethodAdapter(access, name, descriptor, innerVisitor) + FilterPolicy.Redirect -> { + log.v("Redirecting method...") + return RedirectMethodAdapter( + access, name, descriptor, + forceCreateBody, innerVisitor + ) .withAnnotation(HostStubGenProcessedAsSubstitute.CLASS_DESCRIPTOR) } else -> {} @@ -274,15 +277,16 @@ class ImplGeneratingAdapter( } /** - * A method adapter that rewrite a native method body with a - * call to a method in the "native substitution" class. + * A method adapter that rewrite a method body with a + * call to a method in the redirection class. */ - private inner class NativeSubstitutingMethodAdapter( + private inner class RedirectMethodAdapter( access: Int, private val name: String, private val descriptor: String, + createBody: Boolean, next: MethodVisitor? - ) : BodyReplacingMethodVisitor(true, next) { + ) : BodyReplacingMethodVisitor(createBody, next) { private val isStatic = (access and Opcodes.ACC_STATIC) != 0 @@ -290,7 +294,7 @@ class ImplGeneratingAdapter( var targetDescriptor = descriptor var argOffset = 0 - // For non-static native method, we need to tweak it a bit. + // For non-static method, we need to tweak it a bit. if (!isStatic) { // Push `this` as the first argument. this.visitVarInsn(Opcodes.ALOAD, 0) @@ -310,7 +314,7 @@ class ImplGeneratingAdapter( visitMethodInsn( INVOKESTATIC, - nativeSubstitutionClass, + redirectionClass, name, targetDescriptor, false 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 5fde14ff525f..82586bb9fcdc 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 @@ -41,20 +41,40 @@ RuntimeVisibleAnnotations: java.lang.annotation.Retention( value=Ljava/lang/annotation/RetentionPolicy;.CLASS ) -## Class: android/hosttest/annotation/HostSideTestNativeSubstitutionClass.class - Compiled from "HostSideTestNativeSubstitutionClass.java" -public interface android.hosttest.annotation.HostSideTestNativeSubstitutionClass extends java.lang.annotation.Annotation +## Class: android/hosttest/annotation/HostSideTestRedirect.class + Compiled from "HostSideTestRedirect.java" +public interface android.hosttest.annotation.HostSideTestRedirect extends java.lang.annotation.Annotation minor version: 0 major version: 61 flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION - this_class: #x // android/hosttest/annotation/HostSideTestNativeSubstitutionClass + this_class: #x // android/hosttest/annotation/HostSideTestRedirect + super_class: #x // java/lang/Object + interfaces: 1, fields: 0, methods: 0, attributes: 2 +} +SourceFile: "HostSideTestRedirect.java" +RuntimeVisibleAnnotations: + x: #x(#x=[e#x.#x]) + java.lang.annotation.Target( + value=[Ljava/lang/annotation/ElementType;.METHOD] + ) + x: #x(#x=e#x.#x) + java.lang.annotation.Retention( + value=Ljava/lang/annotation/RetentionPolicy;.CLASS + ) +## Class: android/hosttest/annotation/HostSideTestRedirectionClass.class + Compiled from "HostSideTestRedirectionClass.java" +public interface android.hosttest.annotation.HostSideTestRedirectionClass extends java.lang.annotation.Annotation + minor version: 0 + major version: 61 + flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION + this_class: #x // android/hosttest/annotation/HostSideTestRedirectionClass super_class: #x // java/lang/Object interfaces: 1, fields: 0, methods: 1, attributes: 2 public abstract java.lang.String value(); descriptor: ()Ljava/lang/String; flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT } -SourceFile: "HostSideTestNativeSubstitutionClass.java" +SourceFile: "HostSideTestRedirectionClass.java" RuntimeVisibleAnnotations: x: #x(#x=[e#x.#x]) java.lang.annotation.Target( @@ -1925,7 +1945,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative super_class: #x // java/lang/Object - interfaces: 0, fields: 1, methods: 12, attributes: 2 + interfaces: 0, fields: 1, methods: 14, attributes: 2 int value; descriptor: I flags: (0x0000) @@ -1946,6 +1966,9 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative public static native int nativeAddTwo(int); descriptor: (I)I flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE + RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestRedirect public static int nativeAddTwo_should_be_like_this(int); descriptor: (I)I @@ -1963,6 +1986,9 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative public static native long nativeLongPlus(long, long); descriptor: (JJ)J flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE + RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestRedirect public static long nativeLongPlus_should_be_like_this(long, long); descriptor: (JJ)J @@ -1997,6 +2023,9 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative public native int nativeNonStaticAddToValue(int); descriptor: (I)I flags: (0x0101) ACC_PUBLIC, ACC_NATIVE + RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestRedirect public int nativeNonStaticAddToValue_should_be_like_this(int); descriptor: (I)I @@ -2023,9 +2052,6 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative public static native void nativeStillKeep(); descriptor: ()V flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE - RuntimeInvisibleAnnotations: - x: #x() - android.hosttest.annotation.HostSideTestKeep public static void nativeStillNotSupported_should_be_like_this(); descriptor: ()V @@ -2041,13 +2067,47 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative public static native byte nativeBytePlus(byte, byte); descriptor: (BB)B flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE + RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestRedirect + + public void notNativeRedirected(); + descriptor: ()V + flags: (0x0001) ACC_PUBLIC + Code: + stack=2, locals=1, args_size=1 + 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/TinyFrameworkNative; + RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestRedirect + + public static void notNativeStaticRedirected(); + descriptor: ()V + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=2, locals=0, args_size=0 + x: new #x // class java/lang/RuntimeException + x: dup + x: invokespecial #x // Method java/lang/RuntimeException."<init>":()V + x: athrow + LineNumberTable: + RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestRedirect } SourceFile: "TinyFrameworkNative.java" RuntimeInvisibleAnnotations: x: #x() android.hosttest.annotation.HostSideTestWholeClassKeep x: #x(#x=s#x) - android.hosttest.annotation.HostSideTestNativeSubstitutionClass( + android.hosttest.annotation.HostSideTestRedirectionClass( value="TinyFrameworkNative_host" ) ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.class @@ -2058,7 +2118,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host super_class: #x // java/lang/Object - interfaces: 0, fields: 0, methods: 5, attributes: 2 + interfaces: 0, fields: 0, methods: 7, attributes: 2 public com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -2132,6 +2192,25 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host Start Length Slot Name Signature 0 5 0 arg1 B 0 5 1 arg2 B + + public static void notNativeRedirected(com.android.hoststubgen.test.tinyframework.TinyFrameworkNative); + descriptor: (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;)V + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=0, locals=1, args_size=1 + x: return + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 0 1 0 source Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative; + + public static void notNativeStaticRedirected(); + descriptor: ()V + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=0, locals=0, args_size=0 + x: return + LineNumberTable: } SourceFile: "TinyFrameworkNative_host.java" RuntimeInvisibleAnnotations: diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-dump.txt index e41d46d4daaa..31bbcc57ca9c 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-dump.txt +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-dump.txt @@ -48,13 +48,35 @@ RuntimeVisibleAnnotations: java.lang.annotation.Retention( value=Ljava/lang/annotation/RetentionPolicy;.CLASS ) -## Class: android/hosttest/annotation/HostSideTestNativeSubstitutionClass.class - Compiled from "HostSideTestNativeSubstitutionClass.java" -public interface android.hosttest.annotation.HostSideTestNativeSubstitutionClass extends java.lang.annotation.Annotation +## Class: android/hosttest/annotation/HostSideTestRedirect.class + Compiled from "HostSideTestRedirect.java" +public interface android.hosttest.annotation.HostSideTestRedirect extends java.lang.annotation.Annotation minor version: 0 major version: 61 flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION - this_class: #x // android/hosttest/annotation/HostSideTestNativeSubstitutionClass + this_class: #x // android/hosttest/annotation/HostSideTestRedirect + super_class: #x // java/lang/Object + interfaces: 1, fields: 0, methods: 0, attributes: 2 +} +SourceFile: "HostSideTestRedirect.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep + x: #x(#x=[e#x.#x]) + java.lang.annotation.Target( + value=[Ljava/lang/annotation/ElementType;.METHOD] + ) + x: #x(#x=e#x.#x) + java.lang.annotation.Retention( + value=Ljava/lang/annotation/RetentionPolicy;.CLASS + ) +## Class: android/hosttest/annotation/HostSideTestRedirectionClass.class + Compiled from "HostSideTestRedirectionClass.java" +public interface android.hosttest.annotation.HostSideTestRedirectionClass extends java.lang.annotation.Annotation + minor version: 0 + major version: 61 + flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION + this_class: #x // android/hosttest/annotation/HostSideTestRedirectionClass super_class: #x // java/lang/Object interfaces: 1, fields: 0, methods: 1, attributes: 2 public abstract java.lang.String value(); @@ -64,7 +86,7 @@ public interface android.hosttest.annotation.HostSideTestNativeSubstitutionClass x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep } -SourceFile: "HostSideTestNativeSubstitutionClass.java" +SourceFile: "HostSideTestRedirectionClass.java" RuntimeVisibleAnnotations: x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep @@ -2076,7 +2098,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative super_class: #x // java/lang/Object - interfaces: 0, fields: 1, methods: 12, attributes: 3 + interfaces: 0, fields: 1, methods: 14, attributes: 3 int value; descriptor: I flags: (0x0000) @@ -2113,6 +2135,9 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep + RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestRedirect public static int nativeAddTwo_should_be_like_this(int); descriptor: (I)I @@ -2144,6 +2169,9 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep + RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestRedirect public static long nativeLongPlus_should_be_like_this(long, long); descriptor: (JJ)J @@ -2195,6 +2223,9 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep + RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestRedirect public int nativeNonStaticAddToValue_should_be_like_this(int); descriptor: (I)I @@ -2240,9 +2271,6 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative RuntimeVisibleAnnotations: x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep - RuntimeInvisibleAnnotations: - x: #x() - android.hosttest.annotation.HostSideTestKeep public static void nativeStillNotSupported_should_be_like_this(); descriptor: ()V @@ -2272,6 +2300,42 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep + RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestRedirect + + public void notNativeRedirected(); + descriptor: ()V + flags: (0x0001) ACC_PUBLIC + Code: + stack=1, locals=1, args_size=1 + x: aload_0 + x: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.notNativeRedirected:(Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;)V + x: return + RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep + RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestRedirect + + public static void notNativeStaticRedirected(); + descriptor: ()V + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=0, locals=0, args_size=0 + x: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.notNativeStaticRedirected:()V + x: return + RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep + RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestRedirect } SourceFile: "TinyFrameworkNative.java" RuntimeVisibleAnnotations: @@ -2281,7 +2345,7 @@ RuntimeInvisibleAnnotations: x: #x() android.hosttest.annotation.HostSideTestWholeClassKeep x: #x(#x=s#x) - android.hosttest.annotation.HostSideTestNativeSubstitutionClass( + android.hosttest.annotation.HostSideTestRedirectionClass( value="TinyFrameworkNative_host" ) ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.class @@ -2292,7 +2356,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host super_class: #x // java/lang/Object - interfaces: 0, fields: 0, methods: 5, attributes: 3 + interfaces: 0, fields: 0, methods: 7, attributes: 3 public com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -2381,6 +2445,31 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host RuntimeVisibleAnnotations: x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep + + public static void notNativeRedirected(com.android.hoststubgen.test.tinyframework.TinyFrameworkNative); + descriptor: (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;)V + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=0, locals=1, args_size=1 + x: return + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 0 1 0 source Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative; + RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep + + public static void notNativeStaticRedirected(); + descriptor: ()V + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=0, locals=0, args_size=0 + x: return + LineNumberTable: + RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep } SourceFile: "TinyFrameworkNative_host.java" RuntimeVisibleAnnotations: diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-dump.txt index 2ca723bea232..41f459afe78d 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-dump.txt +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-dump.txt @@ -67,13 +67,44 @@ RuntimeVisibleAnnotations: java.lang.annotation.Retention( value=Ljava/lang/annotation/RetentionPolicy;.CLASS ) -## Class: android/hosttest/annotation/HostSideTestNativeSubstitutionClass.class - Compiled from "HostSideTestNativeSubstitutionClass.java" -public interface android.hosttest.annotation.HostSideTestNativeSubstitutionClass extends java.lang.annotation.Annotation +## Class: android/hosttest/annotation/HostSideTestRedirect.class + Compiled from "HostSideTestRedirect.java" +public interface android.hosttest.annotation.HostSideTestRedirect extends java.lang.annotation.Annotation minor version: 0 major version: 61 flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION - this_class: #x // android/hosttest/annotation/HostSideTestNativeSubstitutionClass + this_class: #x // android/hosttest/annotation/HostSideTestRedirect + super_class: #x // java/lang/Object + interfaces: 1, fields: 0, methods: 1, attributes: 2 + private static {}; + descriptor: ()V + flags: (0x000a) ACC_PRIVATE, ACC_STATIC + Code: + stack=2, locals=0, args_size=0 + x: ldc #x // class android/hosttest/annotation/HostSideTestRedirect + 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 +} +SourceFile: "HostSideTestRedirect.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep + x: #x(#x=[e#x.#x]) + java.lang.annotation.Target( + value=[Ljava/lang/annotation/ElementType;.METHOD] + ) + x: #x(#x=e#x.#x) + java.lang.annotation.Retention( + value=Ljava/lang/annotation/RetentionPolicy;.CLASS + ) +## Class: android/hosttest/annotation/HostSideTestRedirectionClass.class + Compiled from "HostSideTestRedirectionClass.java" +public interface android.hosttest.annotation.HostSideTestRedirectionClass extends java.lang.annotation.Annotation + minor version: 0 + major version: 61 + flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION + this_class: #x // android/hosttest/annotation/HostSideTestRedirectionClass super_class: #x // java/lang/Object interfaces: 1, fields: 0, methods: 2, attributes: 2 private static {}; @@ -81,7 +112,7 @@ public interface android.hosttest.annotation.HostSideTestNativeSubstitutionClass flags: (0x000a) ACC_PRIVATE, ACC_STATIC Code: stack=2, locals=0, args_size=0 - x: ldc #x // class android/hosttest/annotation/HostSideTestNativeSubstitutionClass + x: ldc #x // class android/hosttest/annotation/HostSideTestRedirectionClass 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 @@ -93,7 +124,7 @@ public interface android.hosttest.annotation.HostSideTestNativeSubstitutionClass x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep } -SourceFile: "HostSideTestNativeSubstitutionClass.java" +SourceFile: "HostSideTestRedirectionClass.java" RuntimeVisibleAnnotations: x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep @@ -2612,7 +2643,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative super_class: #x // java/lang/Object - interfaces: 0, fields: 1, methods: 13, attributes: 3 + interfaces: 0, fields: 1, methods: 15, attributes: 3 int value; descriptor: I flags: (0x0000) @@ -2669,6 +2700,9 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep + RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestRedirect public static int nativeAddTwo_should_be_like_this(int); descriptor: (I)I @@ -2710,6 +2744,9 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep + RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestRedirect public static long nativeLongPlus_should_be_like_this(long, long); descriptor: (JJ)J @@ -2776,6 +2813,9 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep + RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestRedirect public int nativeNonStaticAddToValue_should_be_like_this(int); descriptor: (I)I @@ -2831,9 +2871,6 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative RuntimeVisibleAnnotations: x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep - RuntimeInvisibleAnnotations: - x: #x() - android.hosttest.annotation.HostSideTestKeep public static void nativeStillNotSupported_should_be_like_this(); descriptor: ()V @@ -2873,6 +2910,52 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep + RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestRedirect + + public void notNativeRedirected(); + descriptor: ()V + flags: (0x0001) ACC_PUBLIC + Code: + stack=4, locals=1, args_size=1 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative + x: ldc #x // String notNativeRedirected + 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: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.notNativeRedirected:(Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;)V + x: return + RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep + RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestRedirect + + public static void notNativeStaticRedirected(); + descriptor: ()V + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=4, locals=0, args_size=0 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative + x: ldc #x // String notNativeStaticRedirected + 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: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.notNativeStaticRedirected:()V + x: return + RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep + RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestRedirect } SourceFile: "TinyFrameworkNative.java" RuntimeVisibleAnnotations: @@ -2882,7 +2965,7 @@ RuntimeInvisibleAnnotations: x: #x() android.hosttest.annotation.HostSideTestWholeClassKeep x: #x(#x=s#x) - android.hosttest.annotation.HostSideTestNativeSubstitutionClass( + android.hosttest.annotation.HostSideTestRedirectionClass( value="TinyFrameworkNative_host" ) ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.class @@ -2893,7 +2976,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host super_class: #x // java/lang/Object - interfaces: 0, fields: 0, methods: 6, attributes: 3 + interfaces: 0, fields: 0, methods: 8, attributes: 3 private static {}; descriptor: ()V flags: (0x000a) ACC_PRIVATE, ACC_STATIC @@ -3017,6 +3100,41 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host RuntimeVisibleAnnotations: x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep + + public static void notNativeRedirected(com.android.hoststubgen.test.tinyframework.TinyFrameworkNative); + descriptor: (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;)V + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=4, locals=1, args_size=1 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host + x: ldc #x // String notNativeRedirected + x: ldc #x // String (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;)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: return + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 11 1 0 source Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative; + RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep + + public static void notNativeStaticRedirected(); + descriptor: ()V + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=4, locals=0, args_size=0 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host + x: ldc #x // String notNativeStaticRedirected + 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: return + LineNumberTable: + RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep } SourceFile: "TinyFrameworkNative_host.java" RuntimeVisibleAnnotations: diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java index 73b5e2fadbad..04a551c8c46e 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java @@ -15,20 +15,23 @@ */ package com.android.hoststubgen.test.tinyframework; -import android.hosttest.annotation.HostSideTestKeep; -import android.hosttest.annotation.HostSideTestNativeSubstitutionClass; +import android.hosttest.annotation.HostSideTestRedirect; +import android.hosttest.annotation.HostSideTestRedirectionClass; import android.hosttest.annotation.HostSideTestThrow; import android.hosttest.annotation.HostSideTestWholeClassKeep; @HostSideTestWholeClassKeep -@HostSideTestNativeSubstitutionClass("TinyFrameworkNative_host") +@HostSideTestRedirectionClass("TinyFrameworkNative_host") public class TinyFrameworkNative { + + @HostSideTestRedirect public static native int nativeAddTwo(int arg); public static int nativeAddTwo_should_be_like_this(int arg) { return TinyFrameworkNative_host.nativeAddTwo(arg); } + @HostSideTestRedirect public static native long nativeLongPlus(long arg1, long arg2); public static long nativeLongPlus_should_be_like_this(long arg1, long arg2) { @@ -41,6 +44,7 @@ public class TinyFrameworkNative { this.value = v; } + @HostSideTestRedirect public native int nativeNonStaticAddToValue(int arg); public int nativeNonStaticAddToValue_should_be_like_this(int arg) { @@ -50,12 +54,22 @@ public class TinyFrameworkNative { @HostSideTestThrow public static native void nativeStillNotSupported(); - @HostSideTestKeep public static native void nativeStillKeep(); public static void nativeStillNotSupported_should_be_like_this() { throw new RuntimeException(); } + @HostSideTestRedirect public static native byte nativeBytePlus(byte arg1, byte arg2); + + @HostSideTestRedirect + public void notNativeRedirected() { + throw new RuntimeException(); + } + + @HostSideTestRedirect + public static void notNativeStaticRedirected() { + throw new RuntimeException(); + } } diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.java index b23c21602967..c7a29a1cc0f9 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.java +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.java @@ -17,8 +17,6 @@ package com.android.hoststubgen.test.tinyframework; import android.hosttest.annotation.HostSideTestWholeClassKeep; -// TODO: This annotation shouldn't be needed. -// We should infer it from HostSideTestNativeSubstitutionClass. @HostSideTestWholeClassKeep public class TinyFrameworkNative_host { public static int nativeAddTwo(int arg) { @@ -38,4 +36,10 @@ public class TinyFrameworkNative_host { public static byte nativeBytePlus(byte arg1, byte arg2) { return (byte) (arg1 + arg2); } + + public static void notNativeRedirected(TinyFrameworkNative source) { + } + + public static void notNativeStaticRedirected() { + } } 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 14229a0ede5f..68673dc2a5b8 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 @@ -164,6 +164,12 @@ public class TinyFrameworkClassTest { } @Test + public void testNotNativeRedirect() { + TinyFrameworkNative.notNativeStaticRedirected(); + new TinyFrameworkNative().notNativeRedirected(); + } + + @Test public void testExitLog() { thrown.expect(RuntimeException.class); thrown.expectMessage("Outer exception"); diff --git a/tools/protologtool/src/com/android/protolog/tool/ViewerConfigProtoBuilder.kt b/tools/protologtool/src/com/android/protolog/tool/ViewerConfigProtoBuilder.kt index 0115339a68b7..245e802df49b 100644 --- a/tools/protologtool/src/com/android/protolog/tool/ViewerConfigProtoBuilder.kt +++ b/tools/protologtool/src/com/android/protolog/tool/ViewerConfigProtoBuilder.kt @@ -53,7 +53,7 @@ class ViewerConfigProtoBuilder : ProtoLogTool.ProtologViewerConfigBuilder { .setMessageId(key) .setMessage(log.messageString) .setLevel( - ProtoLogLevel.forNumber(log.logLevel.ordinal + 1)) + ProtoLogLevel.forNumber(log.logLevel.id)) .setGroupId(groupId) .setLocation(log.position) ) |