diff options
484 files changed, 12669 insertions, 8558 deletions
diff --git a/Android.bp b/Android.bp index 7f4871f5032a..af205d8f0646 100644 --- a/Android.bp +++ b/Android.bp @@ -261,7 +261,6 @@ java_library { "devicepolicyprotosnano", "ImmutabilityAnnotation", - "com.android.sysprop.init", "com.android.sysprop.localization", "PlatformProperties", ], diff --git a/apct-tests/perftests/core/src/android/libcore/SystemArrayCopyPerfTest.java b/apct-tests/perftests/core/src/android/libcore/SystemArrayCopyPerfTest.java index 6363e9c3ef21..25e4c4341b43 100644 --- a/apct-tests/perftests/core/src/android/libcore/SystemArrayCopyPerfTest.java +++ b/apct-tests/perftests/core/src/android/libcore/SystemArrayCopyPerfTest.java @@ -16,7 +16,8 @@ package android.libcore; -import android.perftests.utils.BenchmarkState; +import androidx.benchmark.BenchmarkState; +import androidx.benchmark.junit4.BenchmarkRule; import android.perftests.utils.PerfStatusReporter; import androidx.test.filters.LargeTest; @@ -34,7 +35,8 @@ import java.util.Collection; @RunWith(JUnitParamsRunner.class) @LargeTest public class SystemArrayCopyPerfTest { - @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); + @Rule + public BenchmarkRule mBenchmarkRule = new BenchmarkRule(); public static Collection<Object[]> getData() { return Arrays.asList( @@ -51,7 +53,7 @@ public class SystemArrayCopyPerfTest { final int len = arrayLength; char[] src = new char[len]; char[] dst = new char[len]; - BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + final BenchmarkState state = mBenchmarkRule.getState(); while (state.keepRunning()) { System.arraycopy(src, 0, dst, 0, len); } @@ -63,7 +65,7 @@ public class SystemArrayCopyPerfTest { final int len = arrayLength; byte[] src = new byte[len]; byte[] dst = new byte[len]; - BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + final BenchmarkState state = mBenchmarkRule.getState(); while (state.keepRunning()) { System.arraycopy(src, 0, dst, 0, len); } @@ -75,7 +77,7 @@ public class SystemArrayCopyPerfTest { final int len = arrayLength; short[] src = new short[len]; short[] dst = new short[len]; - BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + final BenchmarkState state = mBenchmarkRule.getState(); while (state.keepRunning()) { System.arraycopy(src, 0, dst, 0, len); } @@ -87,7 +89,7 @@ public class SystemArrayCopyPerfTest { final int len = arrayLength; int[] src = new int[len]; int[] dst = new int[len]; - BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + final BenchmarkState state = mBenchmarkRule.getState(); while (state.keepRunning()) { System.arraycopy(src, 0, dst, 0, len); } @@ -99,7 +101,7 @@ public class SystemArrayCopyPerfTest { final int len = arrayLength; long[] src = new long[len]; long[] dst = new long[len]; - BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + final BenchmarkState state = mBenchmarkRule.getState(); while (state.keepRunning()) { System.arraycopy(src, 0, dst, 0, len); } @@ -111,7 +113,7 @@ public class SystemArrayCopyPerfTest { final int len = arrayLength; float[] src = new float[len]; float[] dst = new float[len]; - BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + final BenchmarkState state = mBenchmarkRule.getState(); while (state.keepRunning()) { System.arraycopy(src, 0, dst, 0, len); } @@ -123,7 +125,7 @@ public class SystemArrayCopyPerfTest { final int len = arrayLength; double[] src = new double[len]; double[] dst = new double[len]; - BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + final BenchmarkState state = mBenchmarkRule.getState(); while (state.keepRunning()) { System.arraycopy(src, 0, dst, 0, len); } @@ -135,7 +137,7 @@ public class SystemArrayCopyPerfTest { final int len = arrayLength; boolean[] src = new boolean[len]; boolean[] dst = new boolean[len]; - BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + final BenchmarkState state = mBenchmarkRule.getState(); while (state.keepRunning()) { System.arraycopy(src, 0, dst, 0, len); } diff --git a/apct-tests/perftests/core/src/android/text/VariableFontPerfTest.java b/apct-tests/perftests/core/src/android/text/VariableFontPerfTest.java index fbe67a477f5d..c34936f930f9 100644 --- a/apct-tests/perftests/core/src/android/text/VariableFontPerfTest.java +++ b/apct-tests/perftests/core/src/android/text/VariableFontPerfTest.java @@ -19,6 +19,7 @@ package android.text; import android.graphics.Paint; import android.graphics.RecordingCanvas; import android.graphics.RenderNode; +import android.graphics.Typeface; import android.perftests.utils.BenchmarkState; import android.perftests.utils.PerfStatusReporter; @@ -120,13 +121,34 @@ public class VariableFontPerfTest { public void testSetFontVariationSettings() { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); final Paint paint = new Paint(PAINT); - final Random random = new Random(0); while (state.keepRunning()) { state.pauseTiming(); - int weight = random.nextInt(1000); + paint.setTypeface(null); + paint.setFontVariationSettings(null); + Typeface.clearTypefaceCachesForTestingPurpose(); state.resumeTiming(); - paint.setFontVariationSettings("'wght' " + weight); + paint.setFontVariationSettings("'wght' 450"); + } + Typeface.clearTypefaceCachesForTestingPurpose(); + } + + @Test + public void testSetFontVariationSettings_Cached() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + final Paint paint = new Paint(PAINT); + Typeface.clearTypefaceCachesForTestingPurpose(); + + while (state.keepRunning()) { + state.pauseTiming(); + paint.setTypeface(null); + paint.setFontVariationSettings(null); + state.resumeTiming(); + + paint.setFontVariationSettings("'wght' 450"); } + + Typeface.clearTypefaceCachesForTestingPurpose(); } + } diff --git a/core/api/current.txt b/core/api/current.txt index 7f2b00485417..c42d1ffcba6d 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -8718,6 +8718,30 @@ package android.app.admin { } +package android.app.appfunctions { + + @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public final class AppFunctionManager { + } + + @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public final class ExecuteAppFunctionRequest implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public android.os.Bundle getExtras(); + method @NonNull public String getFunctionIdentifier(); + method @NonNull public android.app.appsearch.GenericDocument getParameters(); + method @NonNull public String getTargetPackageName(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.appfunctions.ExecuteAppFunctionRequest> CREATOR; + } + + public static final class ExecuteAppFunctionRequest.Builder { + ctor public ExecuteAppFunctionRequest.Builder(@NonNull String, @NonNull String); + method @NonNull public android.app.appfunctions.ExecuteAppFunctionRequest build(); + method @NonNull public android.app.appfunctions.ExecuteAppFunctionRequest.Builder setExtras(@NonNull android.os.Bundle); + method @NonNull public android.app.appfunctions.ExecuteAppFunctionRequest.Builder setParameters(@NonNull android.app.appsearch.GenericDocument); + } + +} + package android.app.assist { public class AssistContent implements android.os.Parcelable { diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 8dc9652db58c..0db91f5667ee 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -2579,9 +2579,10 @@ package android.os { @FlaggedApi("android.os.vibrator.vendor_vibration_effects") public static final class VibrationEffect.VendorEffect extends android.os.VibrationEffect { method @Nullable public long[] computeCreateWaveformOffOnTimingsOrNull(); + method public float getAdaptiveScale(); method public long getDuration(); method public int getEffectStrength(); - method public float getLinearScale(); + method public float getScale(); method @NonNull public android.os.PersistableBundle getVendorData(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.os.VibrationEffect.VendorEffect> CREATOR; diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index 65acd49d44fa..91aa225039a4 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -1587,6 +1587,16 @@ public class ActivityOptions extends ComponentOptions { } } + /** @hide */ + public static boolean hasLaunchTargetContainer(ActivityOptions options) { + return options.getLaunchDisplayId() != INVALID_DISPLAY + || options.getLaunchTaskDisplayArea() != null + || options.getLaunchTaskDisplayAreaFeatureId() != FEATURE_UNDEFINED + || options.getLaunchRootTask() != null + || options.getLaunchTaskId() != -1 + || options.getLaunchTaskFragmentToken() != null; + } + /** * Gets whether the activity is to be launched into LockTask mode. * @return {@code true} if the activity is to be launched into LockTask mode. diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 7e2a580bec73..90fba2962a23 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -1160,7 +1160,7 @@ class ContextImpl extends Context { } mMainThread.getInstrumentation().execStartActivity( getOuterContext(), mMainThread.getApplicationThread(), null, - (Activity) null, intent, -1, options); + (Activity) null, intent, -1, applyLaunchDisplayIfNeeded(options)); } /** @hide */ @@ -1170,8 +1170,8 @@ class ContextImpl extends Context { ActivityTaskManager.getService().startActivityAsUser( mMainThread.getApplicationThread(), getOpPackageName(), getAttributionTag(), intent, intent.resolveTypeIfNeeded(getContentResolver()), - null, null, 0, Intent.FLAG_ACTIVITY_NEW_TASK, null, options, - user.getIdentifier()); + null, null, 0, Intent.FLAG_ACTIVITY_NEW_TASK, null, + applyLaunchDisplayIfNeeded(options), user.getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1194,7 +1194,8 @@ class ContextImpl extends Context { } return mMainThread.getInstrumentation().execStartActivitiesAsUser( getOuterContext(), mMainThread.getApplicationThread(), null, - (Activity) null, intents, options, userHandle.getIdentifier()); + (Activity) null, intents, applyLaunchDisplayIfNeeded(options), + userHandle.getIdentifier()); } @Override @@ -1208,7 +1209,26 @@ class ContextImpl extends Context { } mMainThread.getInstrumentation().execStartActivities( getOuterContext(), mMainThread.getApplicationThread(), null, - (Activity) null, intents, options); + (Activity) null, intents, applyLaunchDisplayIfNeeded(options)); + } + + private Bundle applyLaunchDisplayIfNeeded(@Nullable Bundle options) { + if (!isAssociatedWithDisplay()) { + // return if this Context has no associated display. + return options; + } + + final ActivityOptions activityOptions; + if (options != null) { + activityOptions = ActivityOptions.fromBundle(options); + if (ActivityOptions.hasLaunchTargetContainer(activityOptions)) { + // return if the options already has launching target. + return options; + } + } else { + activityOptions = ActivityOptions.makeBasic(); + } + return activityOptions.setLaunchDisplayId(getAssociatedDisplayId()).toBundle(); } @Override diff --git a/core/java/android/app/appfunctions/AppFunctionManager.java b/core/java/android/app/appfunctions/AppFunctionManager.java index a01e373c5e83..bf21549198f3 100644 --- a/core/java/android/app/appfunctions/AppFunctionManager.java +++ b/core/java/android/app/appfunctions/AppFunctionManager.java @@ -16,6 +16,9 @@ package android.app.appfunctions; +import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER; + +import android.annotation.FlaggedApi; import android.annotation.SystemService; import android.content.Context; @@ -25,8 +28,8 @@ import android.content.Context; * <p>App function is a specific piece of functionality that an app offers to the system. These * functionalities can be integrated into various system features. * - * @hide */ +@FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER) @SystemService(Context.APP_FUNCTION_SERVICE) public final class AppFunctionManager { private final IAppFunctionManager mService; diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.aidl b/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.aidl new file mode 100644 index 000000000000..a0b889e1bb68 --- /dev/null +++ b/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.aidl @@ -0,0 +1,21 @@ +/* + * 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 android.app.appfunctions.ExecuteAppFunctionRequest; + +parcelable ExecuteAppFunctionRequest;
\ No newline at end of file diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java b/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java new file mode 100644 index 000000000000..a50425e4db91 --- /dev/null +++ b/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.appfunctions; + + +import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.app.appsearch.GenericDocument; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * A request to execute an app function. + */ +@FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER) +public final class ExecuteAppFunctionRequest implements Parcelable { + @NonNull + public static final Creator<ExecuteAppFunctionRequest> CREATOR = + new Creator<>() { + @Override + public ExecuteAppFunctionRequest createFromParcel(Parcel parcel) { + String targetPackageName = parcel.readString8(); + String functionIdentifier = parcel.readString8(); + GenericDocument parameters; + parameters = GenericDocument.createFromParcel(parcel); + Bundle extras = parcel.readBundle(Bundle.class.getClassLoader()); + return new ExecuteAppFunctionRequest( + targetPackageName, functionIdentifier, extras, parameters); + } + + @Override + public ExecuteAppFunctionRequest[] newArray(int size) { + return new ExecuteAppFunctionRequest[size]; + } + }; + /** + * Returns the package name of the app that hosts the function. + */ + @NonNull + private final String mTargetPackageName; + /** + * Returns the unique string identifier of the app function to be executed. + * TODO(b/357551503): Document how callers can get the available function identifiers. + */ + @NonNull + private final String mFunctionIdentifier; + /** + * Returns additional metadata relevant to this function execution request. + */ + @NonNull + private final Bundle mExtras; + /** + * Returns the parameters required to invoke this function. Within this [GenericDocument], + * the property names are the names of the function parameters and the property values are the + * values of those parameters. + * + * <p>The document may have missing parameters. Developers are advised to implement defensive + * handling measures. + * + * TODO(b/357551503): Document how function parameters can be obtained for function execution + */ + @NonNull + private final GenericDocument mParameters; + + private ExecuteAppFunctionRequest( + @NonNull String targetPackageName, + @NonNull String functionIdentifier, + @NonNull Bundle extras, + @NonNull GenericDocument parameters) { + mTargetPackageName = Objects.requireNonNull(targetPackageName); + mFunctionIdentifier = Objects.requireNonNull(functionIdentifier); + mExtras = Objects.requireNonNull(extras); + mParameters = Objects.requireNonNull(parameters); + } + + /** + * Returns the package name of the app that hosts the function. + */ + @NonNull + public String getTargetPackageName() { + return mTargetPackageName; + } + + /** + * Returns the unique string identifier of the app function to be executed. + */ + @NonNull + public String getFunctionIdentifier() { + return mFunctionIdentifier; + } + + /** + * Returns the function parameters. The key is the parameter name, and the value is the + * parameter value. + * <p> + * The bundle may have missing parameters. Developers are advised to implement defensive + * handling measures. + */ + @NonNull + public GenericDocument getParameters() { + return mParameters; + } + + /** + * Returns the additional data relevant to this function execution. + */ + @NonNull + public Bundle getExtras() { + return mExtras; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString8(mTargetPackageName); + dest.writeString8(mFunctionIdentifier); + mParameters.writeToParcel(dest, flags); + dest.writeBundle(mExtras); + } + + @Override + public int describeContents() { + return 0; + } + + /** + * Builder for {@link ExecuteAppFunctionRequest}. + */ + public static final class Builder { + @NonNull + private final String mTargetPackageName; + @NonNull + private final String mFunctionIdentifier; + @NonNull + private Bundle mExtras = Bundle.EMPTY; + @NonNull + private GenericDocument mParameters = new GenericDocument.Builder<>("", "", "").build(); + + public Builder(@NonNull String targetPackageName, @NonNull String functionIdentifier) { + mTargetPackageName = Objects.requireNonNull(targetPackageName); + mFunctionIdentifier = Objects.requireNonNull(functionIdentifier); + } + + /** + * Sets the additional data relevant to this function execution. + */ + @NonNull + public Builder setExtras(@NonNull Bundle extras) { + mExtras = Objects.requireNonNull(extras); + return this; + } + + /** + * Sets the function parameters. + */ + @NonNull + public Builder setParameters(@NonNull GenericDocument parameters) { + mParameters = Objects.requireNonNull(parameters); + return this; + } + + /** + * Builds the {@link ExecuteAppFunctionRequest}. + */ + @NonNull + public ExecuteAppFunctionRequest build() { + return new ExecuteAppFunctionRequest( + mTargetPackageName, mFunctionIdentifier, mExtras, mParameters); + } + } +} diff --git a/core/java/android/app/appfunctions/ServiceCallHelper.java b/core/java/android/app/appfunctions/ServiceCallHelper.java new file mode 100644 index 000000000000..cc882bd4ba4a --- /dev/null +++ b/core/java/android/app/appfunctions/ServiceCallHelper.java @@ -0,0 +1,86 @@ +/* + * 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 android.annotation.NonNull; +import android.content.Intent; +import android.os.UserHandle; + +/** + * Defines a contract for establishing temporary connections to services and executing operations + * within a specified timeout. Implementations of this interface provide mechanisms to ensure that + * services are properly unbound after the operation completes or a timeout occurs. + * + * @param <T> Class of wrapped service. + * @hide + */ +public interface ServiceCallHelper<T> { + + /** + * Initiates service binding and executes a provided method when the service connects. Unbinds + * the service after execution or upon timeout. Returns the result of the bindService API. + * + * <p>When the service connection was made successfully, it's the caller responsibility to + * report the usage is completed and can be unbound by calling {@link + * ServiceUsageCompleteListener#onCompleted()}. + * + * <p>This method includes a timeout mechanism to prevent the system from being stuck in a state + * where a service is bound indefinitely (for example, if the binder method never returns). This + * helps ensure that the calling app does not remain alive unnecessarily. + * + * @param intent An Intent object that describes the service that should be bound. + * @param bindFlags Flags used to control the binding process See {@link + * android.content.Context#bindService}. + * @param timeoutInMillis The maximum time in milliseconds to wait for the service connection. + * @param userHandle The UserHandle of the user for which the service should be bound. + * @param callback A callback to be invoked for various events. See {@link + * RunServiceCallCallback}. + */ + boolean runServiceCall( + @NonNull Intent intent, + int bindFlags, + long timeoutInMillis, + @NonNull UserHandle userHandle, + @NonNull RunServiceCallCallback<T> callback); + + /** An interface for clients to signal that they have finished using a bound service. */ + interface ServiceUsageCompleteListener { + /** + * Called when a client has finished using a bound service. This indicates that the service + * can be safely unbound. + */ + void onCompleted(); + } + + interface RunServiceCallCallback<T> { + /** + * Called when the service connection has been established. Uses {@code + * serviceUsageCompleteListener} to report finish using the connected service. + */ + void onServiceConnected( + @NonNull T service, + @NonNull ServiceUsageCompleteListener serviceUsageCompleteListener); + + /** Called when the service connection was failed to establish. */ + void onFailedToConnect(); + + /** + * Called when the whole operation(i.e. binding and the service call) takes longer than + * allowed. + */ + void onTimedOut(); + } +} diff --git a/core/java/android/app/appfunctions/ServiceCallHelperImpl.java b/core/java/android/app/appfunctions/ServiceCallHelperImpl.java new file mode 100644 index 000000000000..2e585467f437 --- /dev/null +++ b/core/java/android/app/appfunctions/ServiceCallHelperImpl.java @@ -0,0 +1,157 @@ +/* + * 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 android.annotation.NonNull; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.UserHandle; +import android.util.Log; + +import java.util.concurrent.Executor; +import java.util.function.Function; + +/** + * An implementation of {@link android.app.appfunctions.ServiceCallHelper} that that is based on + * {@link Context#bindService}. + * + * @param <T> Class of wrapped service. + * @hide + */ +public class ServiceCallHelperImpl<T> implements ServiceCallHelper<T> { + private static final String TAG = "AppFunctionsServiceCall"; + + @NonNull private final Context mContext; + @NonNull private final Function<IBinder, T> mInterfaceConverter; + private final Handler mHandler = new Handler(Looper.getMainLooper()); + private final Executor mExecutor; + + /** + * @param interfaceConverter A function responsible for converting an IBinder object into the + * desired service interface. + * @param executor An Executor instance to dispatch callback. + * @param context The system context. + */ + public ServiceCallHelperImpl( + @NonNull Context context, + @NonNull Function<IBinder, T> interfaceConverter, + @NonNull Executor executor) { + mContext = context; + mInterfaceConverter = interfaceConverter; + mExecutor = executor; + } + + @Override + public boolean runServiceCall( + @NonNull Intent intent, + int bindFlags, + long timeoutInMillis, + @NonNull UserHandle userHandle, + @NonNull RunServiceCallCallback<T> callback) { + OneOffServiceConnection serviceConnection = + new OneOffServiceConnection( + intent, bindFlags, timeoutInMillis, userHandle, callback); + + return serviceConnection.bindAndRun(); + } + + private class OneOffServiceConnection + implements ServiceConnection, ServiceUsageCompleteListener { + private final Intent mIntent; + private final int mFlags; + private final long mTimeoutMillis; + private final UserHandle mUserHandle; + private final RunServiceCallCallback<T> mCallback; + private final Runnable mTimeoutCallback; + + OneOffServiceConnection( + @NonNull Intent intent, + int flags, + long timeoutMillis, + @NonNull UserHandle userHandle, + @NonNull RunServiceCallCallback<T> callback) { + mIntent = intent; + mFlags = flags; + mTimeoutMillis = timeoutMillis; + mCallback = callback; + mTimeoutCallback = + () -> + mExecutor.execute( + () -> { + safeUnbind(); + mCallback.onTimedOut(); + }); + mUserHandle = userHandle; + } + + public boolean bindAndRun() { + boolean bindServiceResult = + mContext.bindServiceAsUser(mIntent, this, mFlags, mUserHandle); + + if (bindServiceResult) { + mHandler.postDelayed(mTimeoutCallback, mTimeoutMillis); + } else { + safeUnbind(); + } + + return bindServiceResult; + } + + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + T serviceInterface = mInterfaceConverter.apply(service); + + mExecutor.execute(() -> mCallback.onServiceConnected(serviceInterface, this)); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + safeUnbind(); + mExecutor.execute(mCallback::onFailedToConnect); + } + + @Override + public void onBindingDied(ComponentName name) { + safeUnbind(); + mExecutor.execute(mCallback::onFailedToConnect); + } + + @Override + public void onNullBinding(ComponentName name) { + safeUnbind(); + mExecutor.execute(mCallback::onFailedToConnect); + } + + private void safeUnbind() { + try { + mHandler.removeCallbacks(mTimeoutCallback); + mContext.unbindService(this); + } catch (Exception ex) { + Log.w(TAG, "Failed to unbind", ex); + } + } + + @Override + public void onCompleted() { + safeUnbind(); + } + } +} diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING index 570fdfbe9490..c7716e5a8d20 100644 --- a/core/java/android/content/pm/TEST_MAPPING +++ b/core/java/android/content/pm/TEST_MAPPING @@ -112,17 +112,6 @@ "exclude-annotation":"org.junit.Ignore" } ] - }, - { - "name":"CtsPackageInstallerCUJTestCases", - "options":[ - { - "exclude-annotation":"androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation":"org.junit.Ignore" - } - ] } ], "presubmit-large":[ @@ -184,14 +173,47 @@ ] }, { - "name":"CtsPackageInstallerCUJTestCases", + "name": "CtsPackageInstallerCUJInstallationTestCases", "options":[ - { - "exclude-annotation":"androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation":"org.junit.Ignore" - } + { + "exclude-annotation":"androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation":"org.junit.Ignore" + } + ] + }, + { + "name": "CtsPackageInstallerCUJUninstallationTestCases", + "options":[ + { + "exclude-annotation":"androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation":"org.junit.Ignore" + } + ] + }, + { + "name": "CtsPackageInstallerCUJUpdateOwnerShipTestCases", + "options":[ + { + "exclude-annotation":"androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation":"org.junit.Ignore" + } + ] + }, + { + "name": "CtsPackageInstallerCUJUpdateSelfTestCases", + "options":[ + { + "exclude-annotation":"androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation":"org.junit.Ignore" + } ] } ] diff --git a/core/java/android/hardware/biometrics/BiometricConstants.java b/core/java/android/hardware/biometrics/BiometricConstants.java index 61d87026b6e9..8975191b54c1 100644 --- a/core/java/android/hardware/biometrics/BiometricConstants.java +++ b/core/java/android/hardware/biometrics/BiometricConstants.java @@ -162,6 +162,13 @@ public interface BiometricConstants { * @hide */ int BIOMETRIC_ERROR_POWER_PRESSED = 19; + + /** + * Mandatory biometrics is not in effect. + * @hide + */ + int BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE = 20; + /** * This constant is only used by SystemUI. It notifies SystemUI that authentication was paused * because the authentication attempt was unsuccessful. diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java index de1cac47ff46..9bc46b9f382a 100644 --- a/core/java/android/hardware/biometrics/BiometricManager.java +++ b/core/java/android/hardware/biometrics/BiometricManager.java @@ -80,6 +80,20 @@ public class BiometricManager { BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT; /** + * Lockout error. + * @hide + */ + public static final int BIOMETRIC_ERROR_LOCKOUT = + BiometricConstants.BIOMETRIC_ERROR_LOCKOUT; + + /** + * Mandatory biometrics is not effective. + * @hide + */ + public static final int BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE = + BiometricConstants.BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE; + + /** * A security vulnerability has been discovered and the sensor is unavailable until a * security update has addressed this issue. This error can be received if for example, * authentication was requested with {@link Authenticators#BIOMETRIC_STRONG}, but the @@ -113,7 +127,9 @@ public class BiometricManager { BIOMETRIC_ERROR_HW_UNAVAILABLE, BIOMETRIC_ERROR_NONE_ENROLLED, BIOMETRIC_ERROR_NO_HARDWARE, - BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED}) + BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED, + BIOMETRIC_ERROR_LOCKOUT, + BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE}) @Retention(RetentionPolicy.SOURCE) public @interface BiometricError {} diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl index 98e11375f077..83f268517dab 100644 --- a/core/java/android/hardware/input/IInputManager.aidl +++ b/core/java/android/hardware/input/IInputManager.aidl @@ -25,7 +25,7 @@ import android.hardware.input.IInputDeviceBatteryListener; import android.hardware.input.IInputDeviceBatteryState; import android.hardware.input.IKeyboardBacklightListener; import android.hardware.input.IKeyboardBacklightState; -import android.hardware.input.IKeyboardSystemShortcutListener; +import android.hardware.input.IKeyGestureEventListener; import android.hardware.input.IStickyModifierStateListener; import android.hardware.input.ITabletModeChangedListener; import android.hardware.input.KeyboardLayoutSelectionResult; @@ -241,13 +241,13 @@ interface IInputManager { KeyGlyphMap getKeyGlyphMap(int deviceId); - @EnforcePermission("MONITOR_KEYBOARD_SYSTEM_SHORTCUTS") + @EnforcePermission("MANAGE_KEY_GESTURES") @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = " - + "android.Manifest.permission.MONITOR_KEYBOARD_SYSTEM_SHORTCUTS)") - void registerKeyboardSystemShortcutListener(IKeyboardSystemShortcutListener listener); + + "android.Manifest.permission.MANAGE_KEY_GESTURES)") + void registerKeyGestureEventListener(IKeyGestureEventListener listener); - @EnforcePermission("MONITOR_KEYBOARD_SYSTEM_SHORTCUTS") + @EnforcePermission("MANAGE_KEY_GESTURES") @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = " - + "android.Manifest.permission.MONITOR_KEYBOARD_SYSTEM_SHORTCUTS)") - void unregisterKeyboardSystemShortcutListener(IKeyboardSystemShortcutListener listener); + + "android.Manifest.permission.MANAGE_KEY_GESTURES)") + void unregisterKeyGestureEventListener(IKeyGestureEventListener listener); } diff --git a/core/java/android/hardware/input/IKeyboardSystemShortcutListener.aidl b/core/java/android/hardware/input/IKeyGestureEventListener.aidl index 8d44917845f4..2c430f1a1c9d 100644 --- a/core/java/android/hardware/input/IKeyboardSystemShortcutListener.aidl +++ b/core/java/android/hardware/input/IKeyGestureEventListener.aidl @@ -17,11 +17,10 @@ package android.hardware.input; /** @hide */ -oneway interface IKeyboardSystemShortcutListener { +oneway interface IKeyGestureEventListener { /** - * Called when the keyboard system shortcut is triggered. + * Called when a key gesture event occurs. */ - void onKeyboardSystemShortcutTriggered(int deviceId, in int[] keycodes, int modifierState, - int shortcut); + void onKeyGestureEvent(int deviceId, in int[] keycodes, int modifierState, int shortcut); } diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java index 6bc522b2b386..04cfcd880f52 100644 --- a/core/java/android/hardware/input/InputManager.java +++ b/core/java/android/hardware/input/InputManager.java @@ -1378,33 +1378,31 @@ public final class InputManager { } /** - * Registers a keyboard system shortcut listener for {@link KeyboardSystemShortcut} being - * triggered. + * Registers a key gesture event listener for {@link KeyGestureEvent} being triggered. * * @param executor an executor on which the callback will be called - * @param listener the {@link KeyboardSystemShortcutListener} + * @param listener the {@link KeyGestureEventListener} * @throws IllegalArgumentException if {@code listener} has already been registered previously. * @throws NullPointerException if {@code listener} or {@code executor} is null. * @hide - * @see #unregisterKeyboardSystemShortcutListener(KeyboardSystemShortcutListener) + * @see #unregisterKeyGestureEventListener(KeyGestureEventListener) */ - @RequiresPermission(Manifest.permission.MONITOR_KEYBOARD_SYSTEM_SHORTCUTS) - public void registerKeyboardSystemShortcutListener(@NonNull Executor executor, - @NonNull KeyboardSystemShortcutListener listener) throws IllegalArgumentException { - mGlobal.registerKeyboardSystemShortcutListener(executor, listener); + @RequiresPermission(Manifest.permission.MANAGE_KEY_GESTURES) + public void registerKeyGestureEventListener(@NonNull Executor executor, + @NonNull KeyGestureEventListener listener) throws IllegalArgumentException { + mGlobal.registerKeyGestureEventListener(executor, listener); } /** - * Unregisters a previously added keyboard system shortcut listener. + * Unregisters a previously added key gesture event listener. * - * @param listener the {@link KeyboardSystemShortcutListener} + * @param listener the {@link KeyGestureEventListener} * @hide - * @see #registerKeyboardSystemShortcutListener(Executor, KeyboardSystemShortcutListener) + * @see #registerKeyGestureEventListener(Executor, KeyGestureEventListener) */ - @RequiresPermission(Manifest.permission.MONITOR_KEYBOARD_SYSTEM_SHORTCUTS) - public void unregisterKeyboardSystemShortcutListener( - @NonNull KeyboardSystemShortcutListener listener) { - mGlobal.unregisterKeyboardSystemShortcutListener(listener); + @RequiresPermission(Manifest.permission.MANAGE_KEY_GESTURES) + public void unregisterKeyGestureEventListener(@NonNull KeyGestureEventListener listener) { + mGlobal.unregisterKeyGestureEventListener(listener); } /** @@ -1510,19 +1508,18 @@ public final class InputManager { } /** - * A callback used to be notified about keyboard system shortcuts being triggered. + * A callback used to notify about key gesture event on completion. * - * @see #registerKeyboardSystemShortcutListener(Executor, KeyboardSystemShortcutListener) - * @see #unregisterKeyboardSystemShortcutListener(KeyboardSystemShortcutListener) + * @see #registerKeyGestureEventListener(Executor, KeyGestureEventListener) + * @see #unregisterKeyGestureEventListener(KeyGestureEventListener) * @hide */ - public interface KeyboardSystemShortcutListener { + public interface KeyGestureEventListener { /** - * Called when a keyboard system shortcut is triggered. + * Called when a key gesture event occurs. * - * @param systemShortcut the shortcut info about the shortcut that was triggered. + * @param event the gesture event that occurred. */ - void onKeyboardSystemShortcutTriggered(int deviceId, - @NonNull KeyboardSystemShortcut systemShortcut); + void onKeyGestureEvent(@NonNull KeyGestureEvent event); } } diff --git a/core/java/android/hardware/input/InputManagerGlobal.java b/core/java/android/hardware/input/InputManagerGlobal.java index f7fa5577a047..03cf7c5f926c 100644 --- a/core/java/android/hardware/input/InputManagerGlobal.java +++ b/core/java/android/hardware/input/InputManagerGlobal.java @@ -25,8 +25,8 @@ import android.hardware.BatteryState; import android.hardware.SensorManager; import android.hardware.input.InputManager.InputDeviceBatteryListener; import android.hardware.input.InputManager.InputDeviceListener; +import android.hardware.input.InputManager.KeyGestureEventListener; import android.hardware.input.InputManager.KeyboardBacklightListener; -import android.hardware.input.InputManager.KeyboardSystemShortcutListener; import android.hardware.input.InputManager.OnTabletModeChangedListener; import android.hardware.input.InputManager.StickyModifierStateListener; import android.hardware.lights.Light; @@ -111,13 +111,13 @@ public final class InputManagerGlobal { @Nullable private IStickyModifierStateListener mStickyModifierStateListener; - private final Object mKeyboardSystemShortcutListenerLock = new Object(); - @GuardedBy("mKeyboardSystemShortcutListenerLock") + private final Object mKeyGestureEventListenerLock = new Object(); + @GuardedBy("mKeyGestureEventListenerLock") @Nullable - private ArrayList<KeyboardSystemShortcutListenerDelegate> mKeyboardSystemShortcutListeners; - @GuardedBy("mKeyboardSystemShortcutListenerLock") + private ArrayList<KeyGestureEventListenerDelegate> mKeyGestureEventListeners; + @GuardedBy("mKeyGestureEventListenerLock") @Nullable - private IKeyboardSystemShortcutListener mKeyboardSystemShortcutListener; + private IKeyGestureEventListener mKeyGestureEventListener; // InputDeviceSensorManager gets notified synchronously from the binder thread when input // devices change, so it must be synchronized with the input device listeners. @@ -1064,94 +1064,92 @@ public final class InputManagerGlobal { } } - private static final class KeyboardSystemShortcutListenerDelegate { - final KeyboardSystemShortcutListener mListener; + private static final class KeyGestureEventListenerDelegate { + final KeyGestureEventListener mListener; final Executor mExecutor; - KeyboardSystemShortcutListenerDelegate(KeyboardSystemShortcutListener listener, + KeyGestureEventListenerDelegate(KeyGestureEventListener listener, Executor executor) { mListener = listener; mExecutor = executor; } - void onKeyboardSystemShortcutTriggered(int deviceId, - KeyboardSystemShortcut systemShortcut) { - mExecutor.execute(() -> - mListener.onKeyboardSystemShortcutTriggered(deviceId, systemShortcut)); + void onKeyGestureEvent(KeyGestureEvent event) { + mExecutor.execute(() -> mListener.onKeyGestureEvent(event)); } } - private class LocalKeyboardSystemShortcutListener extends IKeyboardSystemShortcutListener.Stub { + private class LocalKeyGestureEventListener extends IKeyGestureEventListener.Stub { @Override - public void onKeyboardSystemShortcutTriggered(int deviceId, int[] keycodes, - int modifierState, int shortcut) { - synchronized (mKeyboardSystemShortcutListenerLock) { - if (mKeyboardSystemShortcutListeners == null) return; - final int numListeners = mKeyboardSystemShortcutListeners.size(); + public void onKeyGestureEvent(int deviceId, int[] keycodes, int modifierState, + int gestureType) { + synchronized (mKeyGestureEventListenerLock) { + if (mKeyGestureEventListeners == null) return; + final int numListeners = mKeyGestureEventListeners.size(); for (int i = 0; i < numListeners; i++) { - mKeyboardSystemShortcutListeners.get(i) - .onKeyboardSystemShortcutTriggered(deviceId, - new KeyboardSystemShortcut(keycodes, modifierState, shortcut)); + mKeyGestureEventListeners.get(i) + .onKeyGestureEvent( + new KeyGestureEvent(deviceId, keycodes, modifierState, + gestureType)); } } } } /** - * @see InputManager#registerKeyboardSystemShortcutListener(Executor, - * KeyboardSystemShortcutListener) + * @see InputManager#registerKeyGestureEventListener(Executor, + * KeyGestureEventListener) */ - @RequiresPermission(Manifest.permission.MONITOR_KEYBOARD_SYSTEM_SHORTCUTS) - void registerKeyboardSystemShortcutListener(@NonNull Executor executor, - @NonNull KeyboardSystemShortcutListener listener) throws IllegalArgumentException { + @RequiresPermission(Manifest.permission.MANAGE_KEY_GESTURES) + void registerKeyGestureEventListener(@NonNull Executor executor, + @NonNull KeyGestureEventListener listener) throws IllegalArgumentException { Objects.requireNonNull(executor, "executor should not be null"); Objects.requireNonNull(listener, "listener should not be null"); - synchronized (mKeyboardSystemShortcutListenerLock) { - if (mKeyboardSystemShortcutListener == null) { - mKeyboardSystemShortcutListeners = new ArrayList<>(); - mKeyboardSystemShortcutListener = new LocalKeyboardSystemShortcutListener(); + synchronized (mKeyGestureEventListenerLock) { + if (mKeyGestureEventListener == null) { + mKeyGestureEventListeners = new ArrayList<>(); + mKeyGestureEventListener = new LocalKeyGestureEventListener(); try { - mIm.registerKeyboardSystemShortcutListener(mKeyboardSystemShortcutListener); + mIm.registerKeyGestureEventListener(mKeyGestureEventListener); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } - final int numListeners = mKeyboardSystemShortcutListeners.size(); + final int numListeners = mKeyGestureEventListeners.size(); for (int i = 0; i < numListeners; i++) { - if (mKeyboardSystemShortcutListeners.get(i).mListener == listener) { + if (mKeyGestureEventListeners.get(i).mListener == listener) { throw new IllegalArgumentException("Listener has already been registered!"); } } - KeyboardSystemShortcutListenerDelegate delegate = - new KeyboardSystemShortcutListenerDelegate(listener, executor); - mKeyboardSystemShortcutListeners.add(delegate); + KeyGestureEventListenerDelegate delegate = + new KeyGestureEventListenerDelegate(listener, executor); + mKeyGestureEventListeners.add(delegate); } } /** - * @see InputManager#unregisterKeyboardSystemShortcutListener(KeyboardSystemShortcutListener) + * @see InputManager#unregisterKeyGestureEventListener(KeyGestureEventListener) */ - @RequiresPermission(Manifest.permission.MONITOR_KEYBOARD_SYSTEM_SHORTCUTS) - void unregisterKeyboardSystemShortcutListener( - @NonNull KeyboardSystemShortcutListener listener) { + @RequiresPermission(Manifest.permission.MANAGE_KEY_GESTURES) + void unregisterKeyGestureEventListener(@NonNull KeyGestureEventListener listener) { Objects.requireNonNull(listener, "listener should not be null"); - synchronized (mKeyboardSystemShortcutListenerLock) { - if (mKeyboardSystemShortcutListeners == null) { + synchronized (mKeyGestureEventListenerLock) { + if (mKeyGestureEventListeners == null) { return; } - mKeyboardSystemShortcutListeners.removeIf((delegate) -> delegate.mListener == listener); - if (mKeyboardSystemShortcutListeners.isEmpty()) { + mKeyGestureEventListeners.removeIf((delegate) -> delegate.mListener == listener); + if (mKeyGestureEventListeners.isEmpty()) { try { - mIm.unregisterKeyboardSystemShortcutListener(mKeyboardSystemShortcutListener); + mIm.unregisterKeyGestureEventListener(mKeyGestureEventListener); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } - mKeyboardSystemShortcutListeners = null; - mKeyboardSystemShortcutListener = null; + mKeyGestureEventListeners = null; + mKeyGestureEventListener = null; } } } diff --git a/core/java/android/hardware/input/KeyGestureEvent.java b/core/java/android/hardware/input/KeyGestureEvent.java new file mode 100644 index 000000000000..7a8dd3395d21 --- /dev/null +++ b/core/java/android/hardware/input/KeyGestureEvent.java @@ -0,0 +1,531 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.input; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; + +import com.android.internal.util.DataClass; +import com.android.internal.util.FrameworkStatsLog; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Provides information about the keyboard gesture event being triggered by an external keyboard. + * + * @hide + */ +@DataClass(genToString = true, genEqualsHashCode = true) +public class KeyGestureEvent { + + private final int mDeviceId; + @NonNull + private final int[] mKeycodes; + private final int mModifierState; + @KeyGestureType + private final int mKeyGestureType; + + + public static final int KEY_GESTURE_TYPE_UNSPECIFIED = + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__UNSPECIFIED; + public static final int KEY_GESTURE_TYPE_HOME = + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__HOME; + public static final int KEY_GESTURE_TYPE_RECENT_APPS = + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__RECENT_APPS; + public static final int KEY_GESTURE_TYPE_BACK = + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__BACK; + public static final int KEY_GESTURE_TYPE_APP_SWITCH = + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__APP_SWITCH; + public static final int KEY_GESTURE_TYPE_LAUNCH_ASSISTANT = + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_ASSISTANT; + public static final int KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT = + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_VOICE_ASSISTANT; + public static final int KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS = + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_SYSTEM_SETTINGS; + public static final int KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL = + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_NOTIFICATION_PANEL; + public static final int KEY_GESTURE_TYPE_TOGGLE_TASKBAR = + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_TASKBAR; + public static final int KEY_GESTURE_TYPE_TAKE_SCREENSHOT = + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TAKE_SCREENSHOT; + public static final int KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER = + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__OPEN_SHORTCUT_HELPER; + public static final int KEY_GESTURE_TYPE_BRIGHTNESS_UP = + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__BRIGHTNESS_UP; + public static final int KEY_GESTURE_TYPE_BRIGHTNESS_DOWN = + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__BRIGHTNESS_DOWN; + public static final int KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP = + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__KEYBOARD_BACKLIGHT_UP; + public static final int KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN = + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__KEYBOARD_BACKLIGHT_DOWN; + public static final int KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE = + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__KEYBOARD_BACKLIGHT_TOGGLE; + public static final int KEY_GESTURE_TYPE_VOLUME_UP = + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__VOLUME_UP; + public static final int KEY_GESTURE_TYPE_VOLUME_DOWN = + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__VOLUME_DOWN; + public static final int KEY_GESTURE_TYPE_VOLUME_MUTE = + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__VOLUME_MUTE; + public static final int KEY_GESTURE_TYPE_ALL_APPS = + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__ALL_APPS; + public static final int KEY_GESTURE_TYPE_LAUNCH_SEARCH = + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_SEARCH; + public static final int KEY_GESTURE_TYPE_LANGUAGE_SWITCH = + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LANGUAGE_SWITCH; + public static final int KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS = + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__ACCESSIBILITY_ALL_APPS; + public static final int KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK = + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_CAPS_LOCK; + public static final int KEY_GESTURE_TYPE_SYSTEM_MUTE = + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SYSTEM_MUTE; + public static final int KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION = + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SPLIT_SCREEN_NAVIGATION; + public static final int KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS = + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__CHANGE_SPLITSCREEN_FOCUS; + public static final int KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT = + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TRIGGER_BUG_REPORT; + public static final int KEY_GESTURE_TYPE_LOCK_SCREEN = + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LOCK_SCREEN; + public static final int KEY_GESTURE_TYPE_OPEN_NOTES = + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__OPEN_NOTES; + public static final int KEY_GESTURE_TYPE_TOGGLE_POWER = + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_POWER; + public static final int KEY_GESTURE_TYPE_SYSTEM_NAVIGATION = + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SYSTEM_NAVIGATION; + public static final int KEY_GESTURE_TYPE_SLEEP = + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SLEEP; + public static final int KEY_GESTURE_TYPE_WAKEUP = + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__WAKEUP; + public static final int KEY_GESTURE_TYPE_MEDIA_KEY = + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__MEDIA_KEY; + public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER = + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_BROWSER; + public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL = + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_EMAIL; + public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS = + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_CONTACTS; + public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR = + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_CALENDAR; + public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR = + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_CALCULATOR; + public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC = + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_MUSIC; + public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS = + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_MAPS; + public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING = + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_MESSAGING; + public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_GALLERY = + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_GALLERY; + public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FILES = + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_FILES; + public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_WEATHER = + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_WEATHER; + public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FITNESS = + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_FITNESS; + public static final int KEY_GESTURE_TYPE_LAUNCH_APPLICATION_BY_PACKAGE_NAME = + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_APPLICATION_BY_PACKAGE_NAME; + public static final int KEY_GESTURE_TYPE_DESKTOP_MODE = + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__DESKTOP_MODE; + public static final int KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION = + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__MULTI_WINDOW_NAVIGATION; + + + + // Code below generated by codegen v1.0.23. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/hardware/input/KeyGestureEvent.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + @IntDef(prefix = "KEY_GESTURE_TYPE_", value = { + KEY_GESTURE_TYPE_UNSPECIFIED, + KEY_GESTURE_TYPE_HOME, + KEY_GESTURE_TYPE_RECENT_APPS, + KEY_GESTURE_TYPE_BACK, + KEY_GESTURE_TYPE_APP_SWITCH, + KEY_GESTURE_TYPE_LAUNCH_ASSISTANT, + KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT, + KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS, + KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL, + KEY_GESTURE_TYPE_TOGGLE_TASKBAR, + KEY_GESTURE_TYPE_TAKE_SCREENSHOT, + KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER, + KEY_GESTURE_TYPE_BRIGHTNESS_UP, + KEY_GESTURE_TYPE_BRIGHTNESS_DOWN, + KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP, + KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN, + KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE, + KEY_GESTURE_TYPE_VOLUME_UP, + KEY_GESTURE_TYPE_VOLUME_DOWN, + KEY_GESTURE_TYPE_VOLUME_MUTE, + KEY_GESTURE_TYPE_ALL_APPS, + KEY_GESTURE_TYPE_LAUNCH_SEARCH, + KEY_GESTURE_TYPE_LANGUAGE_SWITCH, + KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS, + KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK, + KEY_GESTURE_TYPE_SYSTEM_MUTE, + KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION, + KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS, + KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT, + KEY_GESTURE_TYPE_LOCK_SCREEN, + KEY_GESTURE_TYPE_OPEN_NOTES, + KEY_GESTURE_TYPE_TOGGLE_POWER, + KEY_GESTURE_TYPE_SYSTEM_NAVIGATION, + KEY_GESTURE_TYPE_SLEEP, + KEY_GESTURE_TYPE_WAKEUP, + KEY_GESTURE_TYPE_MEDIA_KEY, + KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER, + KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL, + KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS, + KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR, + KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR, + KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC, + KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS, + KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING, + KEY_GESTURE_TYPE_LAUNCH_DEFAULT_GALLERY, + KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FILES, + KEY_GESTURE_TYPE_LAUNCH_DEFAULT_WEATHER, + KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FITNESS, + KEY_GESTURE_TYPE_LAUNCH_APPLICATION_BY_PACKAGE_NAME, + KEY_GESTURE_TYPE_DESKTOP_MODE, + KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION + }) + @Retention(RetentionPolicy.SOURCE) + @DataClass.Generated.Member + public @interface KeyGestureType {} + + @DataClass.Generated.Member + public static String keyGestureTypeToString(@KeyGestureType int value) { + switch (value) { + case KEY_GESTURE_TYPE_UNSPECIFIED: + return "KEY_GESTURE_TYPE_UNSPECIFIED"; + case KEY_GESTURE_TYPE_HOME: + return "KEY_GESTURE_TYPE_HOME"; + case KEY_GESTURE_TYPE_RECENT_APPS: + return "KEY_GESTURE_TYPE_RECENT_APPS"; + case KEY_GESTURE_TYPE_BACK: + return "KEY_GESTURE_TYPE_BACK"; + case KEY_GESTURE_TYPE_APP_SWITCH: + return "KEY_GESTURE_TYPE_APP_SWITCH"; + case KEY_GESTURE_TYPE_LAUNCH_ASSISTANT: + return "KEY_GESTURE_TYPE_LAUNCH_ASSISTANT"; + case KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT: + return "KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT"; + case KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS: + return "KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS"; + case KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL: + return "KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL"; + case KEY_GESTURE_TYPE_TOGGLE_TASKBAR: + return "KEY_GESTURE_TYPE_TOGGLE_TASKBAR"; + case KEY_GESTURE_TYPE_TAKE_SCREENSHOT: + return "KEY_GESTURE_TYPE_TAKE_SCREENSHOT"; + case KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER: + return "KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER"; + case KEY_GESTURE_TYPE_BRIGHTNESS_UP: + return "KEY_GESTURE_TYPE_BRIGHTNESS_UP"; + case KEY_GESTURE_TYPE_BRIGHTNESS_DOWN: + return "KEY_GESTURE_TYPE_BRIGHTNESS_DOWN"; + case KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP: + return "KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP"; + case KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN: + return "KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN"; + case KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE: + return "KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE"; + case KEY_GESTURE_TYPE_VOLUME_UP: + return "KEY_GESTURE_TYPE_VOLUME_UP"; + case KEY_GESTURE_TYPE_VOLUME_DOWN: + return "KEY_GESTURE_TYPE_VOLUME_DOWN"; + case KEY_GESTURE_TYPE_VOLUME_MUTE: + return "KEY_GESTURE_TYPE_VOLUME_MUTE"; + case KEY_GESTURE_TYPE_ALL_APPS: + return "KEY_GESTURE_TYPE_ALL_APPS"; + case KEY_GESTURE_TYPE_LAUNCH_SEARCH: + return "KEY_GESTURE_TYPE_LAUNCH_SEARCH"; + case KEY_GESTURE_TYPE_LANGUAGE_SWITCH: + return "KEY_GESTURE_TYPE_LANGUAGE_SWITCH"; + case KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS: + return "KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS"; + case KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK: + return "KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK"; + case KEY_GESTURE_TYPE_SYSTEM_MUTE: + return "KEY_GESTURE_TYPE_SYSTEM_MUTE"; + case KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION: + return "KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION"; + case KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS: + return "KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS"; + case KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT: + return "KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT"; + case KEY_GESTURE_TYPE_LOCK_SCREEN: + return "KEY_GESTURE_TYPE_LOCK_SCREEN"; + case KEY_GESTURE_TYPE_OPEN_NOTES: + return "KEY_GESTURE_TYPE_OPEN_NOTES"; + case KEY_GESTURE_TYPE_TOGGLE_POWER: + return "KEY_GESTURE_TYPE_TOGGLE_POWER"; + case KEY_GESTURE_TYPE_SYSTEM_NAVIGATION: + return "KEY_GESTURE_TYPE_SYSTEM_NAVIGATION"; + case KEY_GESTURE_TYPE_SLEEP: + return "KEY_GESTURE_TYPE_SLEEP"; + case KEY_GESTURE_TYPE_WAKEUP: + return "KEY_GESTURE_TYPE_WAKEUP"; + case KEY_GESTURE_TYPE_MEDIA_KEY: + return "KEY_GESTURE_TYPE_MEDIA_KEY"; + case KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER: + return "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER"; + case KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL: + return "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL"; + case KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS: + return "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS"; + case KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR: + return "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR"; + case KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR: + return "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR"; + case KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC: + return "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC"; + case KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS: + return "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS"; + case KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING: + return "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING"; + case KEY_GESTURE_TYPE_LAUNCH_DEFAULT_GALLERY: + return "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_GALLERY"; + case KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FILES: + return "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FILES"; + case KEY_GESTURE_TYPE_LAUNCH_DEFAULT_WEATHER: + return "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_WEATHER"; + case KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FITNESS: + return "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FITNESS"; + case KEY_GESTURE_TYPE_LAUNCH_APPLICATION_BY_PACKAGE_NAME: + return "KEY_GESTURE_TYPE_LAUNCH_APPLICATION_BY_PACKAGE_NAME"; + case KEY_GESTURE_TYPE_DESKTOP_MODE: + return "KEY_GESTURE_TYPE_DESKTOP_MODE"; + case KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION: + return "KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION"; + default: return Integer.toHexString(value); + } + } + + @DataClass.Generated.Member + public KeyGestureEvent( + int deviceId, + @NonNull int[] keycodes, + int modifierState, + @KeyGestureType int keyGestureType) { + this.mDeviceId = deviceId; + this.mKeycodes = keycodes; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mKeycodes); + this.mModifierState = modifierState; + this.mKeyGestureType = keyGestureType; + + if (!(mKeyGestureType == KEY_GESTURE_TYPE_UNSPECIFIED) + && !(mKeyGestureType == KEY_GESTURE_TYPE_HOME) + && !(mKeyGestureType == KEY_GESTURE_TYPE_RECENT_APPS) + && !(mKeyGestureType == KEY_GESTURE_TYPE_BACK) + && !(mKeyGestureType == KEY_GESTURE_TYPE_APP_SWITCH) + && !(mKeyGestureType == KEY_GESTURE_TYPE_LAUNCH_ASSISTANT) + && !(mKeyGestureType == KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT) + && !(mKeyGestureType == KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS) + && !(mKeyGestureType == KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL) + && !(mKeyGestureType == KEY_GESTURE_TYPE_TOGGLE_TASKBAR) + && !(mKeyGestureType == KEY_GESTURE_TYPE_TAKE_SCREENSHOT) + && !(mKeyGestureType == KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER) + && !(mKeyGestureType == KEY_GESTURE_TYPE_BRIGHTNESS_UP) + && !(mKeyGestureType == KEY_GESTURE_TYPE_BRIGHTNESS_DOWN) + && !(mKeyGestureType == KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP) + && !(mKeyGestureType == KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN) + && !(mKeyGestureType == KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE) + && !(mKeyGestureType == KEY_GESTURE_TYPE_VOLUME_UP) + && !(mKeyGestureType == KEY_GESTURE_TYPE_VOLUME_DOWN) + && !(mKeyGestureType == KEY_GESTURE_TYPE_VOLUME_MUTE) + && !(mKeyGestureType == KEY_GESTURE_TYPE_ALL_APPS) + && !(mKeyGestureType == KEY_GESTURE_TYPE_LAUNCH_SEARCH) + && !(mKeyGestureType == KEY_GESTURE_TYPE_LANGUAGE_SWITCH) + && !(mKeyGestureType == KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS) + && !(mKeyGestureType == KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK) + && !(mKeyGestureType == KEY_GESTURE_TYPE_SYSTEM_MUTE) + && !(mKeyGestureType == KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION) + && !(mKeyGestureType == KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS) + && !(mKeyGestureType == KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT) + && !(mKeyGestureType == KEY_GESTURE_TYPE_LOCK_SCREEN) + && !(mKeyGestureType == KEY_GESTURE_TYPE_OPEN_NOTES) + && !(mKeyGestureType == KEY_GESTURE_TYPE_TOGGLE_POWER) + && !(mKeyGestureType == KEY_GESTURE_TYPE_SYSTEM_NAVIGATION) + && !(mKeyGestureType == KEY_GESTURE_TYPE_SLEEP) + && !(mKeyGestureType == KEY_GESTURE_TYPE_WAKEUP) + && !(mKeyGestureType == KEY_GESTURE_TYPE_MEDIA_KEY) + && !(mKeyGestureType == KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER) + && !(mKeyGestureType == KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL) + && !(mKeyGestureType == KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS) + && !(mKeyGestureType == KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR) + && !(mKeyGestureType == KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR) + && !(mKeyGestureType == KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC) + && !(mKeyGestureType == KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS) + && !(mKeyGestureType == KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING) + && !(mKeyGestureType == KEY_GESTURE_TYPE_LAUNCH_DEFAULT_GALLERY) + && !(mKeyGestureType == KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FILES) + && !(mKeyGestureType == KEY_GESTURE_TYPE_LAUNCH_DEFAULT_WEATHER) + && !(mKeyGestureType == KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FITNESS) + && !(mKeyGestureType == KEY_GESTURE_TYPE_LAUNCH_APPLICATION_BY_PACKAGE_NAME) + && !(mKeyGestureType == KEY_GESTURE_TYPE_DESKTOP_MODE) + && !(mKeyGestureType == KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION)) { + throw new java.lang.IllegalArgumentException( + "keyGestureType was " + mKeyGestureType + " but must be one of: " + + "KEY_GESTURE_TYPE_UNSPECIFIED(" + KEY_GESTURE_TYPE_UNSPECIFIED + "), " + + "KEY_GESTURE_TYPE_HOME(" + KEY_GESTURE_TYPE_HOME + "), " + + "KEY_GESTURE_TYPE_RECENT_APPS(" + KEY_GESTURE_TYPE_RECENT_APPS + "), " + + "KEY_GESTURE_TYPE_BACK(" + KEY_GESTURE_TYPE_BACK + "), " + + "KEY_GESTURE_TYPE_APP_SWITCH(" + KEY_GESTURE_TYPE_APP_SWITCH + "), " + + "KEY_GESTURE_TYPE_LAUNCH_ASSISTANT(" + KEY_GESTURE_TYPE_LAUNCH_ASSISTANT + "), " + + "KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT(" + KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT + "), " + + "KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS(" + KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS + "), " + + "KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL(" + KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL + "), " + + "KEY_GESTURE_TYPE_TOGGLE_TASKBAR(" + KEY_GESTURE_TYPE_TOGGLE_TASKBAR + "), " + + "KEY_GESTURE_TYPE_TAKE_SCREENSHOT(" + KEY_GESTURE_TYPE_TAKE_SCREENSHOT + "), " + + "KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER(" + KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER + "), " + + "KEY_GESTURE_TYPE_BRIGHTNESS_UP(" + KEY_GESTURE_TYPE_BRIGHTNESS_UP + "), " + + "KEY_GESTURE_TYPE_BRIGHTNESS_DOWN(" + KEY_GESTURE_TYPE_BRIGHTNESS_DOWN + "), " + + "KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP(" + KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP + "), " + + "KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN(" + KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN + "), " + + "KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE(" + KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE + "), " + + "KEY_GESTURE_TYPE_VOLUME_UP(" + KEY_GESTURE_TYPE_VOLUME_UP + "), " + + "KEY_GESTURE_TYPE_VOLUME_DOWN(" + KEY_GESTURE_TYPE_VOLUME_DOWN + "), " + + "KEY_GESTURE_TYPE_VOLUME_MUTE(" + KEY_GESTURE_TYPE_VOLUME_MUTE + "), " + + "KEY_GESTURE_TYPE_ALL_APPS(" + KEY_GESTURE_TYPE_ALL_APPS + "), " + + "KEY_GESTURE_TYPE_LAUNCH_SEARCH(" + KEY_GESTURE_TYPE_LAUNCH_SEARCH + "), " + + "KEY_GESTURE_TYPE_LANGUAGE_SWITCH(" + KEY_GESTURE_TYPE_LANGUAGE_SWITCH + "), " + + "KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS(" + KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS + "), " + + "KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK(" + KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK + "), " + + "KEY_GESTURE_TYPE_SYSTEM_MUTE(" + KEY_GESTURE_TYPE_SYSTEM_MUTE + "), " + + "KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION(" + KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION + "), " + + "KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS(" + KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS + "), " + + "KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT(" + KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT + "), " + + "KEY_GESTURE_TYPE_LOCK_SCREEN(" + KEY_GESTURE_TYPE_LOCK_SCREEN + "), " + + "KEY_GESTURE_TYPE_OPEN_NOTES(" + KEY_GESTURE_TYPE_OPEN_NOTES + "), " + + "KEY_GESTURE_TYPE_TOGGLE_POWER(" + KEY_GESTURE_TYPE_TOGGLE_POWER + "), " + + "KEY_GESTURE_TYPE_SYSTEM_NAVIGATION(" + KEY_GESTURE_TYPE_SYSTEM_NAVIGATION + "), " + + "KEY_GESTURE_TYPE_SLEEP(" + KEY_GESTURE_TYPE_SLEEP + "), " + + "KEY_GESTURE_TYPE_WAKEUP(" + KEY_GESTURE_TYPE_WAKEUP + "), " + + "KEY_GESTURE_TYPE_MEDIA_KEY(" + KEY_GESTURE_TYPE_MEDIA_KEY + "), " + + "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER(" + KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER + "), " + + "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL(" + KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL + "), " + + "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS(" + KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS + "), " + + "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR(" + KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR + "), " + + "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR(" + KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR + "), " + + "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC(" + KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC + "), " + + "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS(" + KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS + "), " + + "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING(" + KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING + "), " + + "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_GALLERY(" + KEY_GESTURE_TYPE_LAUNCH_DEFAULT_GALLERY + "), " + + "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FILES(" + KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FILES + "), " + + "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_WEATHER(" + KEY_GESTURE_TYPE_LAUNCH_DEFAULT_WEATHER + "), " + + "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FITNESS(" + KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FITNESS + "), " + + "KEY_GESTURE_TYPE_LAUNCH_APPLICATION_BY_PACKAGE_NAME(" + KEY_GESTURE_TYPE_LAUNCH_APPLICATION_BY_PACKAGE_NAME + "), " + + "KEY_GESTURE_TYPE_DESKTOP_MODE(" + KEY_GESTURE_TYPE_DESKTOP_MODE + "), " + + "KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION(" + KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION + ")"); + } + + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public int getDeviceId() { + return mDeviceId; + } + + @DataClass.Generated.Member + public @NonNull int[] getKeycodes() { + return mKeycodes; + } + + @DataClass.Generated.Member + public int getModifierState() { + return mModifierState; + } + + @DataClass.Generated.Member + public @KeyGestureType int getKeyGestureType() { + return mKeyGestureType; + } + + @Override + @DataClass.Generated.Member + public String toString() { + // You can override field toString logic by defining methods like: + // String fieldNameToString() { ... } + + return "KeyGestureEvent { " + + "deviceId = " + mDeviceId + ", " + + "keycodes = " + java.util.Arrays.toString(mKeycodes) + ", " + + "modifierState = " + mModifierState + ", " + + "keyGestureType = " + keyGestureTypeToString(mKeyGestureType) + + " }"; + } + + @Override + @DataClass.Generated.Member + public boolean equals(@Nullable Object o) { + // You can override field equality logic by defining either of the methods like: + // boolean fieldNameEquals(KeyGestureEvent other) { ... } + // boolean fieldNameEquals(FieldType otherValue) { ... } + + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + @SuppressWarnings("unchecked") + KeyGestureEvent that = (KeyGestureEvent) o; + //noinspection PointlessBooleanExpression + return true + && mDeviceId == that.mDeviceId + && java.util.Arrays.equals(mKeycodes, that.mKeycodes) + && mModifierState == that.mModifierState + && mKeyGestureType == that.mKeyGestureType; + } + + @Override + @DataClass.Generated.Member + public int hashCode() { + // You can override field hashCode logic by defining methods like: + // int fieldNameHashCode() { ... } + + int _hash = 1; + _hash = 31 * _hash + mDeviceId; + _hash = 31 * _hash + java.util.Arrays.hashCode(mKeycodes); + _hash = 31 * _hash + mModifierState; + _hash = 31 * _hash + mKeyGestureType; + return _hash; + } + + @DataClass.Generated( + time = 1723409092192L, + codegenVersion = "1.0.23", + sourceFile = "frameworks/base/core/java/android/hardware/input/KeyGestureEvent.java", + inputSignatures = "private final int mDeviceId\nprivate final @android.annotation.NonNull int[] mKeycodes\nprivate final int mModifierState\nprivate final @android.hardware.input.KeyGestureEvent.KeyGestureType int mKeyGestureType\npublic static final int KEY_GESTURE_TYPE_UNSPECIFIED\npublic static final int KEY_GESTURE_TYPE_HOME\npublic static final int KEY_GESTURE_TYPE_RECENT_APPS\npublic static final int KEY_GESTURE_TYPE_BACK\npublic static final int KEY_GESTURE_TYPE_APP_SWITCH\npublic static final int KEY_GESTURE_TYPE_LAUNCH_ASSISTANT\npublic static final int KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT\npublic static final int KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS\npublic static final int KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL\npublic static final int KEY_GESTURE_TYPE_TOGGLE_TASKBAR\npublic static final int KEY_GESTURE_TYPE_TAKE_SCREENSHOT\npublic static final int KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER\npublic static final int KEY_GESTURE_TYPE_BRIGHTNESS_UP\npublic static final int KEY_GESTURE_TYPE_BRIGHTNESS_DOWN\npublic static final int KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP\npublic static final int KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN\npublic static final int KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE\npublic static final int KEY_GESTURE_TYPE_VOLUME_UP\npublic static final int KEY_GESTURE_TYPE_VOLUME_DOWN\npublic static final int KEY_GESTURE_TYPE_VOLUME_MUTE\npublic static final int KEY_GESTURE_TYPE_ALL_APPS\npublic static final int KEY_GESTURE_TYPE_LAUNCH_SEARCH\npublic static final int KEY_GESTURE_TYPE_LANGUAGE_SWITCH\npublic static final int KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS\npublic static final int KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK\npublic static final int KEY_GESTURE_TYPE_SYSTEM_MUTE\npublic static final int KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION\npublic static final int KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS\npublic static final int KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT\npublic static final int KEY_GESTURE_TYPE_LOCK_SCREEN\npublic static final int KEY_GESTURE_TYPE_OPEN_NOTES\npublic static final int KEY_GESTURE_TYPE_TOGGLE_POWER\npublic static final int KEY_GESTURE_TYPE_SYSTEM_NAVIGATION\npublic static final int KEY_GESTURE_TYPE_SLEEP\npublic static final int KEY_GESTURE_TYPE_WAKEUP\npublic static final int KEY_GESTURE_TYPE_MEDIA_KEY\npublic static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER\npublic static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL\npublic static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS\npublic static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR\npublic static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR\npublic static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC\npublic static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS\npublic static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING\npublic static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_GALLERY\npublic static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FILES\npublic static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_WEATHER\npublic static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FITNESS\npublic static final int KEY_GESTURE_TYPE_LAUNCH_APPLICATION_BY_PACKAGE_NAME\npublic static final int KEY_GESTURE_TYPE_DESKTOP_MODE\npublic static final int KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION\nclass KeyGestureEvent extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + +} diff --git a/core/java/android/hardware/input/KeyboardSystemShortcut.java b/core/java/android/hardware/input/KeyboardSystemShortcut.java deleted file mode 100644 index 89cf877c3aa8..000000000000 --- a/core/java/android/hardware/input/KeyboardSystemShortcut.java +++ /dev/null @@ -1,522 +0,0 @@ -/* - * Copyright 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.hardware.input; - -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.Nullable; - -import com.android.internal.util.DataClass; -import com.android.internal.util.FrameworkStatsLog; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * Provides information about the keyboard shortcut being triggered by an external keyboard. - * - * @hide - */ -@DataClass(genToString = true, genEqualsHashCode = true) -public class KeyboardSystemShortcut { - - private static final String TAG = "KeyboardSystemShortcut"; - - @NonNull - private final int[] mKeycodes; - private final int mModifierState; - @SystemShortcut - private final int mSystemShortcut; - - - public static final int SYSTEM_SHORTCUT_UNSPECIFIED = - FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__UNSPECIFIED; - public static final int SYSTEM_SHORTCUT_HOME = - FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__HOME; - public static final int SYSTEM_SHORTCUT_RECENT_APPS = - FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__RECENT_APPS; - public static final int SYSTEM_SHORTCUT_BACK = - FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__BACK; - public static final int SYSTEM_SHORTCUT_APP_SWITCH = - FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__APP_SWITCH; - public static final int SYSTEM_SHORTCUT_LAUNCH_ASSISTANT = - FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_ASSISTANT; - public static final int SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT = - FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_VOICE_ASSISTANT; - public static final int SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS = - FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_SYSTEM_SETTINGS; - public static final int SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL = - FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_NOTIFICATION_PANEL; - public static final int SYSTEM_SHORTCUT_TOGGLE_TASKBAR = - FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_TASKBAR; - public static final int SYSTEM_SHORTCUT_TAKE_SCREENSHOT = - FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TAKE_SCREENSHOT; - public static final int SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER = - FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__OPEN_SHORTCUT_HELPER; - public static final int SYSTEM_SHORTCUT_BRIGHTNESS_UP = - FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__BRIGHTNESS_UP; - public static final int SYSTEM_SHORTCUT_BRIGHTNESS_DOWN = - FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__BRIGHTNESS_DOWN; - public static final int SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP = - FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__KEYBOARD_BACKLIGHT_UP; - public static final int SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN = - FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__KEYBOARD_BACKLIGHT_DOWN; - public static final int SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE = - FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__KEYBOARD_BACKLIGHT_TOGGLE; - public static final int SYSTEM_SHORTCUT_VOLUME_UP = - FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__VOLUME_UP; - public static final int SYSTEM_SHORTCUT_VOLUME_DOWN = - FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__VOLUME_DOWN; - public static final int SYSTEM_SHORTCUT_VOLUME_MUTE = - FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__VOLUME_MUTE; - public static final int SYSTEM_SHORTCUT_ALL_APPS = - FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__ALL_APPS; - public static final int SYSTEM_SHORTCUT_LAUNCH_SEARCH = - FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_SEARCH; - public static final int SYSTEM_SHORTCUT_LANGUAGE_SWITCH = - FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LANGUAGE_SWITCH; - public static final int SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS = - FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__ACCESSIBILITY_ALL_APPS; - public static final int SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK = - FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_CAPS_LOCK; - public static final int SYSTEM_SHORTCUT_SYSTEM_MUTE = - FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SYSTEM_MUTE; - public static final int SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION = - FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SPLIT_SCREEN_NAVIGATION; - public static final int SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS = - FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__CHANGE_SPLITSCREEN_FOCUS; - public static final int SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT = - FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TRIGGER_BUG_REPORT; - public static final int SYSTEM_SHORTCUT_LOCK_SCREEN = - FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LOCK_SCREEN; - public static final int SYSTEM_SHORTCUT_OPEN_NOTES = - FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__OPEN_NOTES; - public static final int SYSTEM_SHORTCUT_TOGGLE_POWER = - FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_POWER; - public static final int SYSTEM_SHORTCUT_SYSTEM_NAVIGATION = - FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SYSTEM_NAVIGATION; - public static final int SYSTEM_SHORTCUT_SLEEP = - FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SLEEP; - public static final int SYSTEM_SHORTCUT_WAKEUP = - FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__WAKEUP; - public static final int SYSTEM_SHORTCUT_MEDIA_KEY = - FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__MEDIA_KEY; - public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER = - FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_BROWSER; - public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL = - FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_EMAIL; - public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS = - FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_CONTACTS; - public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR = - FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_CALENDAR; - public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR = - FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_CALCULATOR; - public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC = - FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_MUSIC; - public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS = - FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_MAPS; - public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING = - FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_MESSAGING; - public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_GALLERY = - FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_GALLERY; - public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FILES = - FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_FILES; - public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_WEATHER = - FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_WEATHER; - public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FITNESS = - FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_FITNESS; - public static final int SYSTEM_SHORTCUT_LAUNCH_APPLICATION_BY_PACKAGE_NAME = - FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_APPLICATION_BY_PACKAGE_NAME; - public static final int SYSTEM_SHORTCUT_DESKTOP_MODE = - FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__DESKTOP_MODE; - public static final int SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION = - FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__MULTI_WINDOW_NAVIGATION; - - - - // Code below generated by codegen v1.0.23. - // - // DO NOT MODIFY! - // CHECKSTYLE:OFF Generated code - // - // To regenerate run: - // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/hardware/input/KeyboardSystemShortcut.java - // - // To exclude the generated code from IntelliJ auto-formatting enable (one-time): - // Settings > Editor > Code Style > Formatter Control - //@formatter:off - - - @IntDef(prefix = "SYSTEM_SHORTCUT_", value = { - SYSTEM_SHORTCUT_UNSPECIFIED, - SYSTEM_SHORTCUT_HOME, - SYSTEM_SHORTCUT_RECENT_APPS, - SYSTEM_SHORTCUT_BACK, - SYSTEM_SHORTCUT_APP_SWITCH, - SYSTEM_SHORTCUT_LAUNCH_ASSISTANT, - SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT, - SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS, - SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL, - SYSTEM_SHORTCUT_TOGGLE_TASKBAR, - SYSTEM_SHORTCUT_TAKE_SCREENSHOT, - SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER, - SYSTEM_SHORTCUT_BRIGHTNESS_UP, - SYSTEM_SHORTCUT_BRIGHTNESS_DOWN, - SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP, - SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN, - SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE, - SYSTEM_SHORTCUT_VOLUME_UP, - SYSTEM_SHORTCUT_VOLUME_DOWN, - SYSTEM_SHORTCUT_VOLUME_MUTE, - SYSTEM_SHORTCUT_ALL_APPS, - SYSTEM_SHORTCUT_LAUNCH_SEARCH, - SYSTEM_SHORTCUT_LANGUAGE_SWITCH, - SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS, - SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK, - SYSTEM_SHORTCUT_SYSTEM_MUTE, - SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION, - SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS, - SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT, - SYSTEM_SHORTCUT_LOCK_SCREEN, - SYSTEM_SHORTCUT_OPEN_NOTES, - SYSTEM_SHORTCUT_TOGGLE_POWER, - SYSTEM_SHORTCUT_SYSTEM_NAVIGATION, - SYSTEM_SHORTCUT_SLEEP, - SYSTEM_SHORTCUT_WAKEUP, - SYSTEM_SHORTCUT_MEDIA_KEY, - SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER, - SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL, - SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS, - SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR, - SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR, - SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC, - SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS, - SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING, - SYSTEM_SHORTCUT_LAUNCH_DEFAULT_GALLERY, - SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FILES, - SYSTEM_SHORTCUT_LAUNCH_DEFAULT_WEATHER, - SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FITNESS, - SYSTEM_SHORTCUT_LAUNCH_APPLICATION_BY_PACKAGE_NAME, - SYSTEM_SHORTCUT_DESKTOP_MODE, - SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION - }) - @Retention(RetentionPolicy.SOURCE) - @DataClass.Generated.Member - public @interface SystemShortcut {} - - @DataClass.Generated.Member - public static String systemShortcutToString(@SystemShortcut int value) { - switch (value) { - case SYSTEM_SHORTCUT_UNSPECIFIED: - return "SYSTEM_SHORTCUT_UNSPECIFIED"; - case SYSTEM_SHORTCUT_HOME: - return "SYSTEM_SHORTCUT_HOME"; - case SYSTEM_SHORTCUT_RECENT_APPS: - return "SYSTEM_SHORTCUT_RECENT_APPS"; - case SYSTEM_SHORTCUT_BACK: - return "SYSTEM_SHORTCUT_BACK"; - case SYSTEM_SHORTCUT_APP_SWITCH: - return "SYSTEM_SHORTCUT_APP_SWITCH"; - case SYSTEM_SHORTCUT_LAUNCH_ASSISTANT: - return "SYSTEM_SHORTCUT_LAUNCH_ASSISTANT"; - case SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT: - return "SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT"; - case SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS: - return "SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS"; - case SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL: - return "SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL"; - case SYSTEM_SHORTCUT_TOGGLE_TASKBAR: - return "SYSTEM_SHORTCUT_TOGGLE_TASKBAR"; - case SYSTEM_SHORTCUT_TAKE_SCREENSHOT: - return "SYSTEM_SHORTCUT_TAKE_SCREENSHOT"; - case SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER: - return "SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER"; - case SYSTEM_SHORTCUT_BRIGHTNESS_UP: - return "SYSTEM_SHORTCUT_BRIGHTNESS_UP"; - case SYSTEM_SHORTCUT_BRIGHTNESS_DOWN: - return "SYSTEM_SHORTCUT_BRIGHTNESS_DOWN"; - case SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP: - return "SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP"; - case SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN: - return "SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN"; - case SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE: - return "SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE"; - case SYSTEM_SHORTCUT_VOLUME_UP: - return "SYSTEM_SHORTCUT_VOLUME_UP"; - case SYSTEM_SHORTCUT_VOLUME_DOWN: - return "SYSTEM_SHORTCUT_VOLUME_DOWN"; - case SYSTEM_SHORTCUT_VOLUME_MUTE: - return "SYSTEM_SHORTCUT_VOLUME_MUTE"; - case SYSTEM_SHORTCUT_ALL_APPS: - return "SYSTEM_SHORTCUT_ALL_APPS"; - case SYSTEM_SHORTCUT_LAUNCH_SEARCH: - return "SYSTEM_SHORTCUT_LAUNCH_SEARCH"; - case SYSTEM_SHORTCUT_LANGUAGE_SWITCH: - return "SYSTEM_SHORTCUT_LANGUAGE_SWITCH"; - case SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS: - return "SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS"; - case SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK: - return "SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK"; - case SYSTEM_SHORTCUT_SYSTEM_MUTE: - return "SYSTEM_SHORTCUT_SYSTEM_MUTE"; - case SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION: - return "SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION"; - case SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS: - return "SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS"; - case SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT: - return "SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT"; - case SYSTEM_SHORTCUT_LOCK_SCREEN: - return "SYSTEM_SHORTCUT_LOCK_SCREEN"; - case SYSTEM_SHORTCUT_OPEN_NOTES: - return "SYSTEM_SHORTCUT_OPEN_NOTES"; - case SYSTEM_SHORTCUT_TOGGLE_POWER: - return "SYSTEM_SHORTCUT_TOGGLE_POWER"; - case SYSTEM_SHORTCUT_SYSTEM_NAVIGATION: - return "SYSTEM_SHORTCUT_SYSTEM_NAVIGATION"; - case SYSTEM_SHORTCUT_SLEEP: - return "SYSTEM_SHORTCUT_SLEEP"; - case SYSTEM_SHORTCUT_WAKEUP: - return "SYSTEM_SHORTCUT_WAKEUP"; - case SYSTEM_SHORTCUT_MEDIA_KEY: - return "SYSTEM_SHORTCUT_MEDIA_KEY"; - case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER: - return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER"; - case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL: - return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL"; - case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS: - return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS"; - case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR: - return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR"; - case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR: - return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR"; - case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC: - return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC"; - case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS: - return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS"; - case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING: - return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING"; - case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_GALLERY: - return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_GALLERY"; - case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FILES: - return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FILES"; - case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_WEATHER: - return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_WEATHER"; - case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FITNESS: - return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FITNESS"; - case SYSTEM_SHORTCUT_LAUNCH_APPLICATION_BY_PACKAGE_NAME: - return "SYSTEM_SHORTCUT_LAUNCH_APPLICATION_BY_PACKAGE_NAME"; - case SYSTEM_SHORTCUT_DESKTOP_MODE: - return "SYSTEM_SHORTCUT_DESKTOP_MODE"; - case SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION: - return "SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION"; - default: return Integer.toHexString(value); - } - } - - @DataClass.Generated.Member - public KeyboardSystemShortcut( - @NonNull int[] keycodes, - int modifierState, - @SystemShortcut int systemShortcut) { - this.mKeycodes = keycodes; - com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mKeycodes); - this.mModifierState = modifierState; - this.mSystemShortcut = systemShortcut; - - if (!(mSystemShortcut == SYSTEM_SHORTCUT_UNSPECIFIED) - && !(mSystemShortcut == SYSTEM_SHORTCUT_HOME) - && !(mSystemShortcut == SYSTEM_SHORTCUT_RECENT_APPS) - && !(mSystemShortcut == SYSTEM_SHORTCUT_BACK) - && !(mSystemShortcut == SYSTEM_SHORTCUT_APP_SWITCH) - && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_ASSISTANT) - && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT) - && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS) - && !(mSystemShortcut == SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL) - && !(mSystemShortcut == SYSTEM_SHORTCUT_TOGGLE_TASKBAR) - && !(mSystemShortcut == SYSTEM_SHORTCUT_TAKE_SCREENSHOT) - && !(mSystemShortcut == SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER) - && !(mSystemShortcut == SYSTEM_SHORTCUT_BRIGHTNESS_UP) - && !(mSystemShortcut == SYSTEM_SHORTCUT_BRIGHTNESS_DOWN) - && !(mSystemShortcut == SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP) - && !(mSystemShortcut == SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN) - && !(mSystemShortcut == SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE) - && !(mSystemShortcut == SYSTEM_SHORTCUT_VOLUME_UP) - && !(mSystemShortcut == SYSTEM_SHORTCUT_VOLUME_DOWN) - && !(mSystemShortcut == SYSTEM_SHORTCUT_VOLUME_MUTE) - && !(mSystemShortcut == SYSTEM_SHORTCUT_ALL_APPS) - && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_SEARCH) - && !(mSystemShortcut == SYSTEM_SHORTCUT_LANGUAGE_SWITCH) - && !(mSystemShortcut == SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS) - && !(mSystemShortcut == SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK) - && !(mSystemShortcut == SYSTEM_SHORTCUT_SYSTEM_MUTE) - && !(mSystemShortcut == SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION) - && !(mSystemShortcut == SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS) - && !(mSystemShortcut == SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT) - && !(mSystemShortcut == SYSTEM_SHORTCUT_LOCK_SCREEN) - && !(mSystemShortcut == SYSTEM_SHORTCUT_OPEN_NOTES) - && !(mSystemShortcut == SYSTEM_SHORTCUT_TOGGLE_POWER) - && !(mSystemShortcut == SYSTEM_SHORTCUT_SYSTEM_NAVIGATION) - && !(mSystemShortcut == SYSTEM_SHORTCUT_SLEEP) - && !(mSystemShortcut == SYSTEM_SHORTCUT_WAKEUP) - && !(mSystemShortcut == SYSTEM_SHORTCUT_MEDIA_KEY) - && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER) - && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL) - && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS) - && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR) - && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR) - && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC) - && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS) - && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING) - && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_GALLERY) - && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FILES) - && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_WEATHER) - && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FITNESS) - && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_APPLICATION_BY_PACKAGE_NAME) - && !(mSystemShortcut == SYSTEM_SHORTCUT_DESKTOP_MODE) - && !(mSystemShortcut == SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION)) { - throw new java.lang.IllegalArgumentException( - "systemShortcut was " + mSystemShortcut + " but must be one of: " - + "SYSTEM_SHORTCUT_UNSPECIFIED(" + SYSTEM_SHORTCUT_UNSPECIFIED + "), " - + "SYSTEM_SHORTCUT_HOME(" + SYSTEM_SHORTCUT_HOME + "), " - + "SYSTEM_SHORTCUT_RECENT_APPS(" + SYSTEM_SHORTCUT_RECENT_APPS + "), " - + "SYSTEM_SHORTCUT_BACK(" + SYSTEM_SHORTCUT_BACK + "), " - + "SYSTEM_SHORTCUT_APP_SWITCH(" + SYSTEM_SHORTCUT_APP_SWITCH + "), " - + "SYSTEM_SHORTCUT_LAUNCH_ASSISTANT(" + SYSTEM_SHORTCUT_LAUNCH_ASSISTANT + "), " - + "SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT(" + SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT + "), " - + "SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS(" + SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS + "), " - + "SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL(" + SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL + "), " - + "SYSTEM_SHORTCUT_TOGGLE_TASKBAR(" + SYSTEM_SHORTCUT_TOGGLE_TASKBAR + "), " - + "SYSTEM_SHORTCUT_TAKE_SCREENSHOT(" + SYSTEM_SHORTCUT_TAKE_SCREENSHOT + "), " - + "SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER(" + SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER + "), " - + "SYSTEM_SHORTCUT_BRIGHTNESS_UP(" + SYSTEM_SHORTCUT_BRIGHTNESS_UP + "), " - + "SYSTEM_SHORTCUT_BRIGHTNESS_DOWN(" + SYSTEM_SHORTCUT_BRIGHTNESS_DOWN + "), " - + "SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP(" + SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP + "), " - + "SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN(" + SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN + "), " - + "SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE(" + SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE + "), " - + "SYSTEM_SHORTCUT_VOLUME_UP(" + SYSTEM_SHORTCUT_VOLUME_UP + "), " - + "SYSTEM_SHORTCUT_VOLUME_DOWN(" + SYSTEM_SHORTCUT_VOLUME_DOWN + "), " - + "SYSTEM_SHORTCUT_VOLUME_MUTE(" + SYSTEM_SHORTCUT_VOLUME_MUTE + "), " - + "SYSTEM_SHORTCUT_ALL_APPS(" + SYSTEM_SHORTCUT_ALL_APPS + "), " - + "SYSTEM_SHORTCUT_LAUNCH_SEARCH(" + SYSTEM_SHORTCUT_LAUNCH_SEARCH + "), " - + "SYSTEM_SHORTCUT_LANGUAGE_SWITCH(" + SYSTEM_SHORTCUT_LANGUAGE_SWITCH + "), " - + "SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS(" + SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS + "), " - + "SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK(" + SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK + "), " - + "SYSTEM_SHORTCUT_SYSTEM_MUTE(" + SYSTEM_SHORTCUT_SYSTEM_MUTE + "), " - + "SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION(" + SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION + "), " - + "SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS(" + SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS + "), " - + "SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT(" + SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT + "), " - + "SYSTEM_SHORTCUT_LOCK_SCREEN(" + SYSTEM_SHORTCUT_LOCK_SCREEN + "), " - + "SYSTEM_SHORTCUT_OPEN_NOTES(" + SYSTEM_SHORTCUT_OPEN_NOTES + "), " - + "SYSTEM_SHORTCUT_TOGGLE_POWER(" + SYSTEM_SHORTCUT_TOGGLE_POWER + "), " - + "SYSTEM_SHORTCUT_SYSTEM_NAVIGATION(" + SYSTEM_SHORTCUT_SYSTEM_NAVIGATION + "), " - + "SYSTEM_SHORTCUT_SLEEP(" + SYSTEM_SHORTCUT_SLEEP + "), " - + "SYSTEM_SHORTCUT_WAKEUP(" + SYSTEM_SHORTCUT_WAKEUP + "), " - + "SYSTEM_SHORTCUT_MEDIA_KEY(" + SYSTEM_SHORTCUT_MEDIA_KEY + "), " - + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER + "), " - + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL + "), " - + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS + "), " - + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR + "), " - + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR + "), " - + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC + "), " - + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS + "), " - + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING + "), " - + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_GALLERY(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_GALLERY + "), " - + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FILES(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FILES + "), " - + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_WEATHER(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_WEATHER + "), " - + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FITNESS(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FITNESS + "), " - + "SYSTEM_SHORTCUT_LAUNCH_APPLICATION_BY_PACKAGE_NAME(" + SYSTEM_SHORTCUT_LAUNCH_APPLICATION_BY_PACKAGE_NAME + "), " - + "SYSTEM_SHORTCUT_DESKTOP_MODE(" + SYSTEM_SHORTCUT_DESKTOP_MODE + "), " - + "SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION(" + SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION + ")"); - } - - - // onConstructed(); // You can define this method to get a callback - } - - @DataClass.Generated.Member - public @NonNull int[] getKeycodes() { - return mKeycodes; - } - - @DataClass.Generated.Member - public int getModifierState() { - return mModifierState; - } - - @DataClass.Generated.Member - public @SystemShortcut int getSystemShortcut() { - return mSystemShortcut; - } - - @Override - @DataClass.Generated.Member - public String toString() { - // You can override field toString logic by defining methods like: - // String fieldNameToString() { ... } - - return "KeyboardSystemShortcut { " + - "keycodes = " + java.util.Arrays.toString(mKeycodes) + ", " + - "modifierState = " + mModifierState + ", " + - "systemShortcut = " + systemShortcutToString(mSystemShortcut) + - " }"; - } - - @Override - @DataClass.Generated.Member - public boolean equals(@Nullable Object o) { - // You can override field equality logic by defining either of the methods like: - // boolean fieldNameEquals(KeyboardSystemShortcut other) { ... } - // boolean fieldNameEquals(FieldType otherValue) { ... } - - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - @SuppressWarnings("unchecked") - KeyboardSystemShortcut that = (KeyboardSystemShortcut) o; - //noinspection PointlessBooleanExpression - return true - && java.util.Arrays.equals(mKeycodes, that.mKeycodes) - && mModifierState == that.mModifierState - && mSystemShortcut == that.mSystemShortcut; - } - - @Override - @DataClass.Generated.Member - public int hashCode() { - // You can override field hashCode logic by defining methods like: - // int fieldNameHashCode() { ... } - - int _hash = 1; - _hash = 31 * _hash + java.util.Arrays.hashCode(mKeycodes); - _hash = 31 * _hash + mModifierState; - _hash = 31 * _hash + mSystemShortcut; - return _hash; - } - - @DataClass.Generated( - time = 1722890917041L, - codegenVersion = "1.0.23", - sourceFile = "frameworks/base/core/java/android/hardware/input/KeyboardSystemShortcut.java", - inputSignatures = "private static final java.lang.String TAG\nprivate final @android.annotation.NonNull int[] mKeycodes\nprivate final int mModifierState\nprivate final @android.hardware.input.KeyboardSystemShortcut.SystemShortcut int mSystemShortcut\npublic static final int SYSTEM_SHORTCUT_UNSPECIFIED\npublic static final int SYSTEM_SHORTCUT_HOME\npublic static final int SYSTEM_SHORTCUT_RECENT_APPS\npublic static final int SYSTEM_SHORTCUT_BACK\npublic static final int SYSTEM_SHORTCUT_APP_SWITCH\npublic static final int SYSTEM_SHORTCUT_LAUNCH_ASSISTANT\npublic static final int SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT\npublic static final int SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS\npublic static final int SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL\npublic static final int SYSTEM_SHORTCUT_TOGGLE_TASKBAR\npublic static final int SYSTEM_SHORTCUT_TAKE_SCREENSHOT\npublic static final int SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER\npublic static final int SYSTEM_SHORTCUT_BRIGHTNESS_UP\npublic static final int SYSTEM_SHORTCUT_BRIGHTNESS_DOWN\npublic static final int SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP\npublic static final int SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN\npublic static final int SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE\npublic static final int SYSTEM_SHORTCUT_VOLUME_UP\npublic static final int SYSTEM_SHORTCUT_VOLUME_DOWN\npublic static final int SYSTEM_SHORTCUT_VOLUME_MUTE\npublic static final int SYSTEM_SHORTCUT_ALL_APPS\npublic static final int SYSTEM_SHORTCUT_LAUNCH_SEARCH\npublic static final int SYSTEM_SHORTCUT_LANGUAGE_SWITCH\npublic static final int SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS\npublic static final int SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK\npublic static final int SYSTEM_SHORTCUT_SYSTEM_MUTE\npublic static final int SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION\npublic static final int SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS\npublic static final int SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT\npublic static final int SYSTEM_SHORTCUT_LOCK_SCREEN\npublic static final int SYSTEM_SHORTCUT_OPEN_NOTES\npublic static final int SYSTEM_SHORTCUT_TOGGLE_POWER\npublic static final int SYSTEM_SHORTCUT_SYSTEM_NAVIGATION\npublic static final int SYSTEM_SHORTCUT_SLEEP\npublic static final int SYSTEM_SHORTCUT_WAKEUP\npublic static final int SYSTEM_SHORTCUT_MEDIA_KEY\npublic static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER\npublic static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL\npublic static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS\npublic static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR\npublic static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR\npublic static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC\npublic static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS\npublic static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING\npublic static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_GALLERY\npublic static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FILES\npublic static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_WEATHER\npublic static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FITNESS\npublic static final int SYSTEM_SHORTCUT_LAUNCH_APPLICATION_BY_PACKAGE_NAME\npublic static final int SYSTEM_SHORTCUT_DESKTOP_MODE\npublic static final int SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION\nclass KeyboardSystemShortcut extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true)") - @Deprecated - private void __metadata() {} - - - //@formatter:on - // End of generated code - -} diff --git a/core/java/android/os/VibrationAttributes.java b/core/java/android/os/VibrationAttributes.java index da863e58a882..589622710f3e 100644 --- a/core/java/android/os/VibrationAttributes.java +++ b/core/java/android/os/VibrationAttributes.java @@ -151,31 +151,6 @@ public final class VibrationAttributes implements Parcelable { */ public static final int USAGE_MEDIA = 0x10 | USAGE_CLASS_MEDIA; - /** @hide */ - @IntDef(prefix = { "CATEGORY_" }, value = { - CATEGORY_UNKNOWN, - CATEGORY_KEYBOARD, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface Category {} - - /** - * Category value when the vibration category is unknown. - * - * @hide - */ - public static final int CATEGORY_UNKNOWN = 0x0; - - /** - * Category value for keyboard vibrations. - * - * <p>Most typical keyboard vibrations are haptic feedback for virtual keyboard key - * press/release, for example. - * - * @hide - */ - public static final int CATEGORY_KEYBOARD = 1; - /** * @hide */ @@ -252,14 +227,12 @@ public final class VibrationAttributes implements Parcelable { private final int mUsage; private final int mFlags; private final int mOriginalAudioUsage; - private final int mCategory; private VibrationAttributes(@Usage int usage, @AudioAttributes.AttributeUsage int audioUsage, - @Flag int flags, @Category int category) { + @Flag int flags) { mUsage = usage; mOriginalAudioUsage = audioUsage; mFlags = flags & FLAG_ALL_SUPPORTED; - mCategory = category; } /** @@ -297,20 +270,6 @@ public final class VibrationAttributes implements Parcelable { } /** - * Return the vibration category. - * - * <p>Vibration categories describe the source of the vibration, and it can be combined with - * the vibration usage to best match to a user setting, e.g. a vibration with usage touch and - * category keyboard can be used to control keyboard haptic feedback independently. - * - * @hide - */ - @Category - public int getCategory() { - return mCategory; - } - - /** * Check whether a flag is set * @return true if a flag is set and false otherwise */ @@ -362,14 +321,12 @@ public final class VibrationAttributes implements Parcelable { dest.writeInt(mUsage); dest.writeInt(mOriginalAudioUsage); dest.writeInt(mFlags); - dest.writeInt(mCategory); } private VibrationAttributes(Parcel src) { mUsage = src.readInt(); mOriginalAudioUsage = src.readInt(); mFlags = src.readInt(); - mCategory = src.readInt(); } public static final @NonNull Parcelable.Creator<VibrationAttributes> @@ -392,12 +349,12 @@ public final class VibrationAttributes implements Parcelable { } VibrationAttributes rhs = (VibrationAttributes) o; return mUsage == rhs.mUsage && mOriginalAudioUsage == rhs.mOriginalAudioUsage - && mFlags == rhs.mFlags && mCategory == rhs.mCategory; + && mFlags == rhs.mFlags; } @Override public int hashCode() { - return Objects.hash(mUsage, mOriginalAudioUsage, mFlags, mCategory); + return Objects.hash(mUsage, mOriginalAudioUsage, mFlags); } @Override @@ -405,7 +362,6 @@ public final class VibrationAttributes implements Parcelable { return "VibrationAttributes{" + "mUsage=" + usageToString() + ", mAudioUsage= " + AudioAttributes.usageToString(mOriginalAudioUsage) - + ", mCategory=" + categoryToString() + ", mFlags=" + mFlags + '}'; } @@ -445,23 +401,6 @@ public final class VibrationAttributes implements Parcelable { } } - /** @hide */ - public String categoryToString() { - return categoryToString(mCategory); - } - - /** @hide */ - public static String categoryToString(@Category int category) { - switch (category) { - case CATEGORY_UNKNOWN: - return "UNKNOWN"; - case CATEGORY_KEYBOARD: - return "KEYBOARD"; - default: - return "unknown category " + category; - } - } - /** * Builder class for {@link VibrationAttributes} objects. * By default, all information is set to UNKNOWN. @@ -471,7 +410,6 @@ public final class VibrationAttributes implements Parcelable { private int mUsage = USAGE_UNKNOWN; private int mOriginalAudioUsage = AudioAttributes.USAGE_UNKNOWN; private int mFlags = 0x0; - private int mCategory = CATEGORY_UNKNOWN; /** * Constructs a new Builder with the defaults. @@ -487,7 +425,6 @@ public final class VibrationAttributes implements Parcelable { mUsage = vib.mUsage; mOriginalAudioUsage = vib.mOriginalAudioUsage; mFlags = vib.mFlags; - mCategory = vib.mCategory; } } @@ -554,7 +491,7 @@ public final class VibrationAttributes implements Parcelable { */ public @NonNull VibrationAttributes build() { VibrationAttributes ans = new VibrationAttributes( - mUsage, mOriginalAudioUsage, mFlags, mCategory); + mUsage, mOriginalAudioUsage, mFlags); return ans; } @@ -570,19 +507,6 @@ public final class VibrationAttributes implements Parcelable { } /** - * Sets the attribute describing the category of the corresponding vibration. - * - * @param category The category for the vibration - * @return the same Builder instance. - * - * @hide - */ - public @NonNull Builder setCategory(@Category int category) { - mCategory = category; - return this; - } - - /** * Sets only the flags specified in the bitmask, leaving the other supported flag values * unchanged in the builder. * diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java index e68b74683292..f02d4a9ce4a7 100644 --- a/core/java/android/os/VibrationEffect.java +++ b/core/java/android/os/VibrationEffect.java @@ -346,7 +346,7 @@ public abstract class VibrationEffect implements Parcelable { @RequiresPermission(android.Manifest.permission.VIBRATE_VENDOR_EFFECTS) public static VibrationEffect createVendorEffect(@NonNull PersistableBundle effect) { VibrationEffect vendorEffect = new VendorEffect(effect, VendorEffect.DEFAULT_STRENGTH, - VendorEffect.DEFAULT_SCALE); + VendorEffect.DEFAULT_SCALE, VendorEffect.DEFAULT_SCALE); vendorEffect.validate(); return vendorEffect; } @@ -623,7 +623,7 @@ public abstract class VibrationEffect implements Parcelable { * @hide */ @NonNull - public abstract VibrationEffect scaleLinearly(float scaleFactor); + public abstract VibrationEffect applyAdaptiveScale(float scaleFactor); /** * Ensures that the effect is repeating indefinitely or not. This is a lossy operation and @@ -948,7 +948,7 @@ public abstract class VibrationEffect implements Parcelable { /** @hide */ @NonNull @Override - public Composed scaleLinearly(float scaleFactor) { + public Composed applyAdaptiveScale(float scaleFactor) { return applyToSegments(VibrationEffectSegment::scaleLinearly, scaleFactor); } @@ -1100,21 +1100,23 @@ public abstract class VibrationEffect implements Parcelable { private final PersistableBundle mVendorData; private final int mEffectStrength; - private final float mLinearScale; + private final float mScale; + private final float mAdaptiveScale; /** @hide */ VendorEffect(@NonNull Parcel in) { this(Objects.requireNonNull( in.readPersistableBundle(VibrationEffect.class.getClassLoader())), - in.readInt(), in.readFloat()); + in.readInt(), in.readFloat(), in.readFloat()); } /** @hide */ public VendorEffect(@NonNull PersistableBundle vendorData, int effectStrength, - float linearScale) { + float scale, float adaptiveScale) { mVendorData = vendorData; mEffectStrength = effectStrength; - mLinearScale = linearScale; + mScale = scale; + mAdaptiveScale = adaptiveScale; } @NonNull @@ -1126,8 +1128,12 @@ public abstract class VibrationEffect implements Parcelable { return mEffectStrength; } - public float getLinearScale() { - return mLinearScale; + public float getScale() { + return mScale; + } + + public float getAdaptiveScale() { + return mAdaptiveScale; } /** @hide */ @@ -1175,7 +1181,8 @@ public abstract class VibrationEffect implements Parcelable { if (mEffectStrength == effectStrength) { return this; } - VendorEffect updated = new VendorEffect(mVendorData, effectStrength, mLinearScale); + VendorEffect updated = new VendorEffect(mVendorData, effectStrength, mScale, + mAdaptiveScale); updated.validate(); return updated; } @@ -1184,18 +1191,24 @@ public abstract class VibrationEffect implements Parcelable { @NonNull @Override public VendorEffect scale(float scaleFactor) { - // Vendor effect strength cannot be scaled with this method. - return this; + if (Float.compare(mScale, scaleFactor) == 0) { + return this; + } + VendorEffect updated = new VendorEffect(mVendorData, mEffectStrength, scaleFactor, + mAdaptiveScale); + updated.validate(); + return updated; } /** @hide */ @NonNull @Override - public VibrationEffect scaleLinearly(float scaleFactor) { - if (Float.compare(mLinearScale, scaleFactor) == 0) { + public VibrationEffect applyAdaptiveScale(float scaleFactor) { + if (Float.compare(mAdaptiveScale, scaleFactor) == 0) { return this; } - VendorEffect updated = new VendorEffect(mVendorData, mEffectStrength, scaleFactor); + VendorEffect updated = new VendorEffect(mVendorData, mEffectStrength, mScale, + scaleFactor); updated.validate(); return updated; } @@ -1216,29 +1229,31 @@ public abstract class VibrationEffect implements Parcelable { return false; } return mEffectStrength == other.mEffectStrength - && (Float.compare(mLinearScale, other.mLinearScale) == 0) + && (Float.compare(mScale, other.mScale) == 0) + && (Float.compare(mAdaptiveScale, other.mAdaptiveScale) == 0) && isPersistableBundleEquals(mVendorData, other.mVendorData); } @Override public int hashCode() { // PersistableBundle does not implement hashCode, so use its size as a shortcut. - return Objects.hash(mVendorData.size(), mEffectStrength, mLinearScale); + return Objects.hash(mVendorData.size(), mEffectStrength, mScale, mAdaptiveScale); } @Override public String toString() { return String.format(Locale.ROOT, - "VendorEffect{vendorData=%s, strength=%s, scale=%.2f}", - mVendorData, effectStrengthToString(mEffectStrength), mLinearScale); + "VendorEffect{vendorData=%s, strength=%s, scale=%.2f, adaptiveScale=%.2f}", + mVendorData, effectStrengthToString(mEffectStrength), mScale, mAdaptiveScale); } /** @hide */ @Override public String toDebugString() { - return String.format(Locale.ROOT, "vendorEffect=%s, strength=%s, scale=%.2f", + return String.format(Locale.ROOT, + "vendorEffect=%s, strength=%s, scale=%.2f, adaptiveScale=%.2f", mVendorData.toShortString(), effectStrengthToString(mEffectStrength), - mLinearScale); + mScale, mAdaptiveScale); } @Override @@ -1251,7 +1266,8 @@ public abstract class VibrationEffect implements Parcelable { out.writeInt(PARCEL_TOKEN_VENDOR_EFFECT); out.writePersistableBundle(mVendorData); out.writeInt(mEffectStrength); - out.writeFloat(mLinearScale); + out.writeFloat(mScale); + out.writeFloat(mAdaptiveScale); } /** diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig index 3fe063df0e1b..4c4aa6cc4e68 100644 --- a/core/java/android/permission/flags.aconfig +++ b/core/java/android/permission/flags.aconfig @@ -223,3 +223,14 @@ flag { description: "Show access entry of location bypass permission in the Privacy Dashboard" bug: "325536053" } + +flag { + name: "dont_remove_existing_uid_states" + is_fixed_read_only: true + namespace: "permissions" + description: "Double check if the uid still exists before attempting to remove its appops state" + bug: "353474742" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/text/Selection.java b/core/java/android/text/Selection.java index 6a54d2378f6c..711578c1482f 100644 --- a/core/java/android/text/Selection.java +++ b/core/java/android/text/Selection.java @@ -350,7 +350,7 @@ public class Selection { private static final char PARAGRAPH_SEPARATOR = '\n'; /** - * Move the cusrot to the closest paragraph start offset. + * Move the cursor to the closest paragraph start offset. * * @param text the spannable text * @param layout layout to be used for drawing. diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig index 3d190fe43626..1c3d73824e7e 100644 --- a/core/java/android/text/flags/flags.aconfig +++ b/core/java/android/text/flags/flags.aconfig @@ -267,3 +267,23 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "typeface_cache_for_var_settings" + namespace: "text" + description: "Cache Typeface instance for font variation settings." + bug: "355462362" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "context_menu_hide_unavailable_items" + namespace: "text" + description: "Hide rather than disable unavailable Editor context menu items." + bug: "345709107" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index 42d66ce6bf1b..f7745d14188e 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -325,17 +325,62 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall private String mTag = TAG; - private final ISurfaceControlViewHostParent mSurfaceControlViewHostParent = - new ISurfaceControlViewHostParent.Stub() { + private static class SurfaceControlViewHostParent extends ISurfaceControlViewHostParent.Stub { + + /** + * mSurfaceView is set in {@link #attach} and cleared in {@link #detach} to prevent + * temporary memory leaks. The remote process's ISurfaceControlViewHostParent binder + * reference extends this object's lifetime. If mSurfaceView is not cleared in + * {@link #detach}, then the SurfaceView and anything it references will not be promptly + * garbage collected. + */ + @Nullable + private SurfaceView mSurfaceView; + + void attach(SurfaceView sv) { + synchronized (this) { + try { + sv.mSurfacePackage.getRemoteInterface().attachParentInterface(this); + mSurfaceView = sv; + } catch (RemoteException e) { + Log.d(TAG, "Failed to attach parent interface to SCVH. Likely SCVH is alraedy " + + "dead."); + } + } + } + + void detach() { + synchronized (this) { + if (mSurfaceView == null) { + return; + } + try { + mSurfaceView.mSurfacePackage.getRemoteInterface().attachParentInterface(null); + } catch (RemoteException e) { + Log.d(TAG, "Failed to remove parent interface from SCVH. Likely SCVH is " + + "already dead"); + } + mSurfaceView = null; + } + } + @Override public void updateParams(WindowManager.LayoutParams[] childAttrs) { - mEmbeddedWindowParams.clear(); - mEmbeddedWindowParams.addAll(Arrays.asList(childAttrs)); + SurfaceView sv; + synchronized (this) { + sv = mSurfaceView; + } + if (sv == null) { + return; + } + + sv.mEmbeddedWindowParams.clear(); + sv.mEmbeddedWindowParams.addAll(Arrays.asList(childAttrs)); - if (isAttachedToWindow()) { - runOnUiThread(() -> { - if (mParent != null) { - mParent.recomputeViewAttributes(SurfaceView.this); + if (sv.isAttachedToWindow()) { + sv.runOnUiThread(() -> { + if (sv.mParent != null) { + sv.mParent.recomputeViewAttributes(sv); } }); } @@ -343,34 +388,45 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall @Override public void forwardBackKeyToParent(@NonNull KeyEvent keyEvent) { - runOnUiThread(() -> { - if (!isAttachedToWindow() || keyEvent.getKeyCode() != KeyEvent.KEYCODE_BACK) { - return; - } - final ViewRootImpl vri = getViewRootImpl(); - if (vri == null) { - return; - } - final InputManager inputManager = mContext.getSystemService(InputManager.class); - if (inputManager == null) { - return; - } - // Check that the event was created recently. - final long timeDiff = SystemClock.uptimeMillis() - keyEvent.getEventTime(); - if (timeDiff > FORWARD_BACK_KEY_TOLERANCE_MS) { - Log.e(TAG, "Ignore the input event that exceed the tolerance time, " - + "exceed " + timeDiff + "ms"); - return; - } - if (inputManager.verifyInputEvent(keyEvent) == null) { - Log.e(TAG, "Received invalid input event"); - return; - } - vri.enqueueInputEvent(keyEvent, null /* receiver */, 0 /* flags */, - true /* processImmediately */); - }); + SurfaceView sv; + synchronized (this) { + sv = mSurfaceView; + } + if (sv == null) { + return; + } + + sv.runOnUiThread(() -> { + if (!sv.isAttachedToWindow() || keyEvent.getKeyCode() != KeyEvent.KEYCODE_BACK) { + return; + } + final ViewRootImpl vri = sv.getViewRootImpl(); + if (vri == null) { + return; + } + final InputManager inputManager = sv.mContext.getSystemService(InputManager.class); + if (inputManager == null) { + return; + } + // Check that the event was created recently. + final long timeDiff = SystemClock.uptimeMillis() - keyEvent.getEventTime(); + if (timeDiff > FORWARD_BACK_KEY_TOLERANCE_MS) { + Log.e(TAG, "Ignore the input event that exceed the tolerance time, " + + "exceed " + timeDiff + "ms"); + return; + } + if (inputManager.verifyInputEvent(keyEvent) == null) { + Log.e(TAG, "Received invalid input event"); + return; + } + vri.enqueueInputEvent(keyEvent, null /* receiver */, 0 /* flags */, + true /* processImmediately */); + }); } - }; + } + + private final SurfaceControlViewHostParent mSurfaceControlViewHostParent = + new SurfaceControlViewHostParent(); private final boolean mRtDrivenClipping = Flags.clipSurfaceviews(); @@ -930,13 +986,8 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } if (mSurfacePackage != null) { - try { - mSurfacePackage.getRemoteInterface().attachParentInterface(null); - mEmbeddedWindowParams.clear(); - } catch (RemoteException e) { - Log.d(TAG, "Failed to remove parent interface from SCVH. Likely SCVH is " - + "already dead"); - } + mSurfaceControlViewHostParent.detach(); + mEmbeddedWindowParams.clear(); if (releaseSurfacePackage) { mSurfacePackage.release(); mSurfacePackage = null; @@ -2067,12 +2118,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall applyTransactionOnVriDraw(transaction); } mSurfacePackage = p; - try { - mSurfacePackage.getRemoteInterface().attachParentInterface( - mSurfaceControlViewHostParent); - } catch (RemoteException e) { - Log.d(TAG, "Failed to attach parent interface to SCVH. Likely SCVH is already dead."); - } + mSurfaceControlViewHostParent.attach(this); if (isFocused()) { requestEmbeddedFocus(true); diff --git a/core/java/android/view/contentprotection/OWNERS b/core/java/android/view/contentprotection/OWNERS index b3583a7f6ab1..48052c640560 100644 --- a/core/java/android/view/contentprotection/OWNERS +++ b/core/java/android/view/contentprotection/OWNERS @@ -1,4 +1,6 @@ -# Bug component: 544200 +# Bug component: 1040349 -include /core/java/android/view/contentcapture/OWNERS +njagar@google.com +williamluh@google.com +aaronjosephs@google.com diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig index 0cb96df2878b..61ee13a2693c 100644 --- a/core/java/android/window/flags/windowing_frontend.aconfig +++ b/core/java/android/window/flags/windowing_frontend.aconfig @@ -91,6 +91,14 @@ flag { } flag { + name: "transit_tracker_plumbing" + namespace: "windowing_frontend" + description: "Plumb and collect on transition tracking object instead of singleton" + bug: "325114242" + is_fixed_read_only: true +} + +flag { name: "transit_ready_tracking" namespace: "windowing_frontend" description: "Enable accurate transition readiness tracking" diff --git a/core/java/com/android/internal/app/LocalePickerWithRegion.java b/core/java/com/android/internal/app/LocalePickerWithRegion.java index 27eebbef6245..ce17d788f93b 100644 --- a/core/java/com/android/internal/app/LocalePickerWithRegion.java +++ b/core/java/com/android/internal/app/LocalePickerWithRegion.java @@ -74,6 +74,7 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O * @param locale the locale picked. */ void onLocaleSelected(LocaleStore.LocaleInfo locale); + default void onParentLocaleSelected(LocaleStore.LocaleInfo locale) {} } /** @@ -292,7 +293,7 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O mListener, locale, mTranslatedOnly /* translate only */, mOnActionExpandListener, this.mLocalePickerCollector); } - + mListener.onParentLocaleSelected(locale); if (selector != null) { getFragmentManager().beginTransaction() .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN) diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java index 33610a09b7c8..c7e1fba66d7f 100644 --- a/core/java/com/android/internal/jank/InteractionJankMonitor.java +++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java @@ -396,6 +396,9 @@ public class InteractionJankMonitor { int cujType = conf.mCujType; if (!shouldMonitor()) { return false; + } else if (!conf.hasValidView()) { + Log.w(TAG, "The view has since become invalid, aborting the CUJ."); + return false; } RunningTracker tracker = putTrackerIfNoCurrent(cujType, () -> diff --git a/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java b/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java index 572a599dc8f5..fcc302331dae 100644 --- a/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java +++ b/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java @@ -48,6 +48,7 @@ import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Arrays; import java.util.Map; import java.util.TreeMap; import java.util.stream.Collectors; @@ -423,6 +424,14 @@ public class LegacyProtoLogImpl implements IProtoLog { for (IProtoLogGroup group : protoLogGroups) { mLogGroups.put(group.name(), group); } + + final var hasGroupsLoggingToLogcat = Arrays.stream(protoLogGroups) + .anyMatch(IProtoLogGroup::isLogToLogcat); + + final ILogger logger = (msg) -> Slog.i(TAG, msg); + if (hasGroupsLoggingToLogcat) { + mViewerConfig.loadViewerConfig(logger, mLegacyViewerConfigFilename); + } } } diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java index cb20ceb5484b..cbcbf2db6c51 100644 --- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java +++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java @@ -16,6 +16,7 @@ package com.android.internal.protolog; +import static android.content.Context.PROTOLOG_SERVICE; import static android.internal.perfetto.protos.InternedDataOuterClass.InternedData.PROTOLOG_STACKTRACE; import static android.internal.perfetto.protos.InternedDataOuterClass.InternedData.PROTOLOG_STRING_ARGS; import static android.internal.perfetto.protos.ProfileCommon.InternedString.IID; @@ -46,6 +47,8 @@ import static android.internal.perfetto.protos.TracePacketOuterClass.TracePacket import android.annotation.NonNull; import android.annotation.Nullable; import android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData; +import android.os.RemoteException; +import android.os.ServiceManager; import android.os.ShellCommand; import android.os.SystemClock; import android.text.TextUtils; @@ -76,6 +79,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.TreeMap; import java.util.UUID; @@ -103,36 +107,45 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto private final ProtoLogViewerConfigReader mViewerConfigReader; @Nullable private final ViewerConfigInputStreamProvider mViewerConfigInputStreamProvider; + @NonNull private final TreeMap<String, IProtoLogGroup> mLogGroups = new TreeMap<>(); + @NonNull private final Runnable mCacheUpdater; + @Nullable // null when the flag android.tracing.client_side_proto_logging is not flipped + private final IProtoLogService mProtoLogService; + + @NonNull private final int[] mDefaultLogLevelCounts = new int[LogLevel.values().length]; + @NonNull private final Map<String, int[]> mLogLevelCounts = new ArrayMap<>(); + @NonNull private final Map<String, Integer> mCollectStackTraceGroupCounts = new ArrayMap<>(); private final Lock mBackgroundServiceLock = new ReentrantLock(); private ExecutorService mBackgroundLoggingService = Executors.newSingleThreadExecutor(); - public PerfettoProtoLogImpl(@NonNull String viewerConfigFilePath, Runnable cacheUpdater) { - this(() -> { - try { - return new ProtoInputStream(new FileInputStream(viewerConfigFilePath)); - } catch (FileNotFoundException e) { - throw new RuntimeException("Failed to load viewer config file " + viewerConfigFilePath, e); - } - }, cacheUpdater); + public PerfettoProtoLogImpl() { + this(null, null, null, () -> {}); } - public PerfettoProtoLogImpl() { - this(null, null, () -> {}); + public PerfettoProtoLogImpl(@NonNull Runnable cacheUpdater) { + this(null, null, null, cacheUpdater); } public PerfettoProtoLogImpl( - @NonNull ViewerConfigInputStreamProvider viewerConfigInputStreamProvider, - Runnable cacheUpdater - ) { - this(viewerConfigInputStreamProvider, - new ProtoLogViewerConfigReader(viewerConfigInputStreamProvider), + @NonNull String viewerConfigFilePath, + @NonNull Runnable cacheUpdater) { + this(viewerConfigFilePath, + null, + new ProtoLogViewerConfigReader(() -> { + try { + return new ProtoInputStream(new FileInputStream(viewerConfigFilePath)); + } catch (FileNotFoundException e) { + throw new RuntimeException( + "Failed to load viewer config file " + viewerConfigFilePath, e); + } + }), cacheUpdater); } @@ -140,8 +153,20 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto public PerfettoProtoLogImpl( @Nullable ViewerConfigInputStreamProvider viewerConfigInputStreamProvider, @Nullable ProtoLogViewerConfigReader viewerConfigReader, - Runnable cacheUpdater - ) { + @NonNull Runnable cacheUpdater) { + this(null, viewerConfigInputStreamProvider, viewerConfigReader, cacheUpdater); + } + + private PerfettoProtoLogImpl( + @Nullable String viewerConfigFilePath, + @Nullable ViewerConfigInputStreamProvider viewerConfigInputStreamProvider, + @Nullable ProtoLogViewerConfigReader viewerConfigReader, + @NonNull Runnable cacheUpdater) { + if (viewerConfigFilePath != null && viewerConfigInputStreamProvider != null) { + throw new RuntimeException("Only one of viewerConfigFilePath and " + + "viewerConfigInputStreamProvider can be set"); + } + Producer.init(InitArguments.DEFAULTS); DataSourceParams params = new DataSourceParams.Builder() @@ -153,6 +178,27 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto this.mViewerConfigInputStreamProvider = viewerConfigInputStreamProvider; this.mViewerConfigReader = viewerConfigReader; this.mCacheUpdater = cacheUpdater; + + if (android.tracing.Flags.clientSideProtoLogging()) { + mProtoLogService = + IProtoLogService.Stub.asInterface(ServiceManager.getService(PROTOLOG_SERVICE)); + Objects.requireNonNull(mProtoLogService, + "ServiceManager returned a null ProtoLog service"); + + try { + var args = new ProtoLogService.RegisterClientArgs(); + + if (viewerConfigFilePath != null) { + args.setViewerConfigFile(viewerConfigFilePath); + } + + mProtoLogService.registerClient(this, args); + } catch (RemoteException e) { + throw new RuntimeException("Failed to register ProtoLog client"); + } + } else { + mProtoLogService = null; + } } /** @@ -690,7 +736,7 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto return UUID.nameUUIDFromBytes(fullStringIdentifier.getBytes()).getMostSignificantBits(); } - private static final int STACK_SIZE_TO_PROTO_LOG_ENTRY_CALL = 12; + private static final int STACK_SIZE_TO_PROTO_LOG_ENTRY_CALL = 6; private String collectStackTrace() { StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); diff --git a/core/java/com/android/internal/protolog/ProtoLogImpl.java b/core/java/com/android/internal/protolog/ProtoLogImpl.java index 77ca7ce91b22..8659a8ffeb97 100644 --- a/core/java/com/android/internal/protolog/ProtoLogImpl.java +++ b/core/java/com/android/internal/protolog/ProtoLogImpl.java @@ -111,7 +111,7 @@ public class ProtoLogImpl { // TODO(b/353530422): Remove - temporary fix to unblock b/352290057 // In so tests the viewer config file might not exist in which we don't // want to provide config path to the user - sServiceInstance = new PerfettoProtoLogImpl(null, null, sCacheUpdater); + sServiceInstance = new PerfettoProtoLogImpl(sCacheUpdater); } else { sServiceInstance = new PerfettoProtoLogImpl(sViewerConfigPath, sCacheUpdater); } diff --git a/core/java/com/android/internal/util/RateLimitingCache.java b/core/java/com/android/internal/util/RateLimitingCache.java new file mode 100644 index 000000000000..9916076fd0ef --- /dev/null +++ b/core/java/com/android/internal/util/RateLimitingCache.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.util; + +import android.os.SystemClock; + +/** + * A speed/rate limiting cache that's used to cache a value to be returned as long as period hasn't + * elapsed and then fetches a new value after period has elapsed. Use this when AIDL calls are + * expensive but the value returned by those APIs don't change often enough (or the recency doesn't + * matter as much), to incur the cost every time. This class maintains the last fetch time and + * fetches a new value when period has passed. Do not use this for API calls that have side-effects. + * <p> + * By passing in an optional <code>count</code> during creation, this can be used as a rate + * limiter that allows up to <code>count</code> calls per period to be passed on to the query + * and then the cached value is returned for the remainder of the period. It uses a simple fixed + * window method to track rate. Use a window and count appropriate for bursts of calls and for + * high latency/cost of the AIDL call. + * + * @param <Value> The type of the return value + * @hide + */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass +public class RateLimitingCache<Value> { + + private volatile Value mCurrentValue; + private volatile long mLastTimestamp; // Can be last fetch time or window start of fetch time + private final long mPeriodMillis; // window size + private final int mLimit; // max per window + private int mCount = 0; // current count within window + private long mRandomOffset; // random offset to avoid batching of AIDL calls at window boundary + + /** + * The interface to fetch the actual value, if the cache is null or expired. + * @hide + * @param <V> The return value type + */ + public interface ValueFetcher<V> { + /** Called when the cache needs to be updated. + * @return the latest value fetched from the source + */ + V fetchValue(); + } + + /** + * Create a speed limiting cache that returns the same value until periodMillis has passed + * and then fetches a new value via the {@link ValueFetcher}. + * + * @param periodMillis time to wait before fetching a new value. Use a negative period to + * indicate the value never changes and is fetched only once and + * cached. A value of 0 will mean always fetch a new value. + */ + public RateLimitingCache(long periodMillis) { + this(periodMillis, 1); + } + + /** + * Create a rate-limiting cache that allows up to <code>count</code> number of AIDL calls per + * period before it starts returning a cached value. The count resets when the next period + * begins. + * + * @param periodMillis the window of time in which <code>count</code> calls will fetch the + * newest value from the AIDL call. + * @param count how many times during the period it's ok to forward the request to the fetcher + * in the {@link #get(ValueFetcher)} method. + */ + public RateLimitingCache(long periodMillis, int count) { + mPeriodMillis = periodMillis; + mLimit = count; + if (mLimit > 1 && periodMillis > 1) { + mRandomOffset = (long) (Math.random() * (periodMillis / 2)); + } + } + + /** + * Returns the current time in <code>elapsedRealtime</code>. Can be overridden to use + * a different timebase that is monotonically increasing; for example, uptimeMillis() + * @return a monotonically increasing time in milliseconds + */ + protected long getTime() { + return SystemClock.elapsedRealtime(); + } + + /** + * Returns either the cached value, if called more frequently than the specific rate, or + * a new value is fetched and cached. Warning: if the caller is likely to mutate the returned + * object, override this method and make a clone before returning it. + * @return the cached or latest value + */ + public Value get(ValueFetcher<Value> query) { + // If the value never changes + if (mPeriodMillis < 0 && mLastTimestamp != 0) { + return mCurrentValue; + } + + synchronized (this) { + // Get the current time and add a random offset to avoid colliding with other + // caches with similar harmonic window boundaries + final long now = getTime() + mRandomOffset; + final boolean newWindow = now - mLastTimestamp >= mPeriodMillis; + if (newWindow || mCount < mLimit) { + // Fetch a new value + mCurrentValue = query.fetchValue(); + + // If rate limiting, set timestamp to start of this window + if (mLimit > 1) { + mLastTimestamp = now - (now % mPeriodMillis); + } else { + mLastTimestamp = now; + } + + if (newWindow) { + mCount = 1; + } else { + mCount++; + } + } + return mCurrentValue; + } + } +} diff --git a/core/jni/android_database_SQLiteRawStatement.cpp b/core/jni/android_database_SQLiteRawStatement.cpp index 961486474821..85a6bdf95928 100644 --- a/core/jni/android_database_SQLiteRawStatement.cpp +++ b/core/jni/android_database_SQLiteRawStatement.cpp @@ -72,14 +72,17 @@ static void throwInvalidParameter(JNIEnv *env, jlong stmtPtr, jint index) { // This throws a SQLiteBindOrColumnIndexOutOfRangeException if the column index is out -// of bounds. -static void throwIfInvalidColumn(JNIEnv *env, jlong stmtPtr, jint col) { +// of bounds. It returns true if an exception was thrown. +static bool throwIfInvalidColumn(JNIEnv *env, jlong stmtPtr, jint col) { if (col < 0 || col >= sqlite3_data_count(stmt(stmtPtr))) { int count = sqlite3_data_count(stmt(stmtPtr)); std::string message = android::base::StringPrintf( "column index %d out of bounds [0,%d]", col, count - 1); char const * errmsg = sqlite3_errstr(SQLITE_RANGE); throw_sqlite3_exception(env, SQLITE_RANGE, errmsg, message.c_str()); + return true; + } else { + return false; } } @@ -216,12 +219,16 @@ static void bindText(JNIEnv* env, jclass, jlong stmtPtr, jint index, jstring val static jint columnType(JNIEnv* env, jclass, jlong stmtPtr, jint col) { - throwIfInvalidColumn(env, stmtPtr, col); + if (throwIfInvalidColumn(env, stmtPtr, col)) { + return 0; + } return sqlite3_column_type(stmt(stmtPtr), col); } static jstring columnName(JNIEnv* env, jclass, jlong stmtPtr, jint col) { - throwIfInvalidColumn(env, stmtPtr, col); + if (throwIfInvalidColumn(env, stmtPtr, col)) { + return nullptr; + } const jchar* name = static_cast<const jchar*>(sqlite3_column_name16(stmt(stmtPtr), col)); if (name == nullptr) { throw_sqlite3_exception(env, db(stmtPtr), "error fetching columnName()"); @@ -232,14 +239,18 @@ static jstring columnName(JNIEnv* env, jclass, jlong stmtPtr, jint col) { } static jint columnBytes(JNIEnv* env, jclass, jlong stmtPtr, jint col) { - throwIfInvalidColumn(env, stmtPtr, col); + if (throwIfInvalidColumn(env, stmtPtr, col)) { + return 0; + } int r = sqlite3_column_bytes16(stmt(stmtPtr), col); throwIfError(env, stmtPtr); return r; } static jbyteArray columnBlob(JNIEnv* env, jclass, jlong stmtPtr, jint col) { - throwIfInvalidColumn(env, stmtPtr, col); + if (throwIfInvalidColumn(env, stmtPtr, col)) { + return nullptr; + } const void* blob = sqlite3_column_blob(stmt(stmtPtr), col); if (blob == nullptr) { if (throwIfError(env, stmtPtr)) { @@ -262,7 +273,9 @@ static jbyteArray columnBlob(JNIEnv* env, jclass, jlong stmtPtr, jint col) { static int columnBuffer(JNIEnv* env, jclass, jlong stmtPtr, jint col, jbyteArray buffer, jint offset, jint length, jint srcOffset) { - throwIfInvalidColumn(env, stmtPtr, col); + if (throwIfInvalidColumn(env, stmtPtr, col)) { + return 0; + } const void* blob = sqlite3_column_blob(stmt(stmtPtr), col); if (blob == nullptr) { throwIfError(env, stmtPtr); @@ -281,22 +294,30 @@ static int columnBuffer(JNIEnv* env, jclass, jlong stmtPtr, jint col, } static jdouble columnDouble(JNIEnv* env, jclass, jlong stmtPtr, jint col) { - throwIfInvalidColumn(env, stmtPtr, col); + if (throwIfInvalidColumn(env, stmtPtr, col)) { + return 0; + } return sqlite3_column_double(stmt(stmtPtr), col); } static jint columnInt(JNIEnv* env, jclass, jlong stmtPtr, jint col) { - throwIfInvalidColumn(env, stmtPtr, col); + if (throwIfInvalidColumn(env, stmtPtr, col)) { + return 0; + } return sqlite3_column_int(stmt(stmtPtr), col); } static jlong columnLong(JNIEnv* env, jclass, jlong stmtPtr, jint col) { - throwIfInvalidColumn(env, stmtPtr, col); + if (throwIfInvalidColumn(env, stmtPtr, col)) { + return 0; + } return sqlite3_column_int64(stmt(stmtPtr), col); } static jstring columnText(JNIEnv* env, jclass, jlong stmtPtr, jint col) { - throwIfInvalidColumn(env, stmtPtr, col); + if (throwIfInvalidColumn(env, stmtPtr, col)) { + return nullptr; + } const jchar* text = static_cast<const jchar*>(sqlite3_column_text16(stmt(stmtPtr), col)); if (text == nullptr) { throwIfError(env, stmtPtr); diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 9112d376a32f..284c2997f9a9 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -19,14 +19,6 @@ #include "com_android_internal_os_Zygote.h" -#include <async_safe/log.h> - -// sys/mount.h has to come before linux/fs.h due to redefinition of MS_RDONLY, MS_BIND, etc -#include <sys/mount.h> -#include <linux/fs.h> -#include <sys/types.h> -#include <dirent.h> - #include <algorithm> #include <array> #include <atomic> @@ -41,19 +33,18 @@ #include <android/fdsan.h> #include <arpa/inet.h> +#include <dirent.h> #include <fcntl.h> #include <grp.h> #include <inttypes.h> #include <malloc.h> #include <mntent.h> -#include <paths.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> -#include <sys/auxv.h> #include <sys/capability.h> -#include <sys/cdefs.h> #include <sys/eventfd.h> +#include <sys/mount.h> #include <sys/personality.h> #include <sys/prctl.h> #include <sys/resource.h> @@ -66,6 +57,7 @@ #include <sys/wait.h> #include <unistd.h> +#include <async_safe/log.h> #include <android-base/file.h> #include <android-base/logging.h> #include <android-base/properties.h> diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 7aeabeed2a08..117041ab353e 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -8132,10 +8132,10 @@ <permission android:name="android.permission.MONITOR_STICKY_MODIFIER_STATE" android:protectionLevel="signature" /> - <!-- Allows low-level access to monitor keyboard system shortcuts + <!-- Allows low-level access to manage key gestures. <p>Not for use by third-party applications. @hide --> - <permission android:name="android.permission.MONITOR_KEYBOARD_SYSTEM_SHORTCUTS" + <permission android:name="android.permission.MANAGE_KEY_GESTURES" android:protectionLevel="signature" /> <uses-permission android:name="android.permission.HANDLE_QUERY_PACKAGE_RESTART" /> diff --git a/core/res/res/drawable-car/car_activity_resolver_list_background.xml b/core/res/res/drawable-car/car_activity_resolver_list_background.xml deleted file mode 100644 index dbbadd83d9a8..000000000000 --- a/core/res/res/drawable-car/car_activity_resolver_list_background.xml +++ /dev/null @@ -1,20 +0,0 @@ -<?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. ---> -<shape xmlns:android="http://schemas.android.com/apk/res/android" - android:shape="rectangle"> - <solid android:color="?attr/colorBackgroundFloating" /> - <corners android:radius="@dimen/car_activity_resolver_corner_radius" /> -</shape>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_zen_mode_icon_animal_paw.xml b/core/res/res/drawable/ic_zen_mode_icon_animal_paw.xml new file mode 100644 index 000000000000..31004ecd0e72 --- /dev/null +++ b/core/res/res/drawable/ic_zen_mode_icon_animal_paw.xml @@ -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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?android:attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + <path + android:fillColor="@android:color/white" + android:pathData="M180,485Q138,485 109,456Q80,427 80,385Q80,343 109,314Q138,285 180,285Q222,285 251,314Q280,343 280,385Q280,427 251,456Q222,485 180,485ZM360,325Q318,325 289,296Q260,267 260,225Q260,183 289,154Q318,125 360,125Q402,125 431,154Q460,183 460,225Q460,267 431,296Q402,325 360,325ZM600,325Q558,325 529,296Q500,267 500,225Q500,183 529,154Q558,125 600,125Q642,125 671,154Q700,183 700,225Q700,267 671,296Q642,325 600,325ZM780,485Q738,485 709,456Q680,427 680,385Q680,343 709,314Q738,285 780,285Q822,285 851,314Q880,343 880,385Q880,427 851,456Q822,485 780,485ZM266,885Q221,885 190.5,850.5Q160,816 160,769Q160,717 195.5,678Q231,639 266,601Q295,570 316,533.5Q337,497 366,465Q388,439 417,422Q446,405 480,405Q514,405 543,421Q572,437 594,463Q622,495 643.5,532Q665,569 694,601Q729,639 764.5,678Q800,717 800,769Q800,816 769.5,850.5Q739,885 694,885Q640,885 587,876Q534,867 480,867Q426,867 373,876Q320,885 266,885Z" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_zen_mode_icon_apartment_building.xml b/core/res/res/drawable/ic_zen_mode_icon_apartment_building.xml new file mode 100644 index 000000000000..30f01fa9d3b6 --- /dev/null +++ b/core/res/res/drawable/ic_zen_mode_icon_apartment_building.xml @@ -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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?android:attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + <path + android:fillColor="@android:color/white" + android:pathData="M120,840L120,280L280,280L280,120L680,120L680,440L840,440L840,840L520,840L520,680L440,680L440,840L120,840ZM200,760L280,760L280,680L200,680L200,760ZM200,600L280,600L280,520L200,520L200,600ZM200,440L280,440L280,360L200,360L200,440ZM360,600L440,600L440,520L360,520L360,600ZM360,440L440,440L440,360L360,360L360,440ZM360,280L440,280L440,200L360,200L360,280ZM520,600L600,600L600,520L520,520L520,600ZM520,440L600,440L600,360L520,360L520,440ZM520,280L600,280L600,200L520,200L520,280ZM680,760L760,760L760,680L680,680L680,760ZM680,600L760,600L760,520L680,520L680,600Z" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_zen_mode_icon_book.xml b/core/res/res/drawable/ic_zen_mode_icon_book.xml new file mode 100644 index 000000000000..c30d222da7c1 --- /dev/null +++ b/core/res/res/drawable/ic_zen_mode_icon_book.xml @@ -0,0 +1,26 @@ +<!-- +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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?android:attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960" + android:autoMirrored="true"> + <path + android:fillColor="@android:color/white" + android:pathData="M560,396L560,328Q593,314 627.5,307Q662,300 700,300Q726,300 751,304Q776,308 800,314L800,378Q776,369 751.5,364.5Q727,360 700,360Q662,360 627,369.5Q592,379 560,396ZM560,616L560,548Q593,534 627.5,527Q662,520 700,520Q726,520 751,524Q776,528 800,534L800,598Q776,589 751.5,584.5Q727,580 700,580Q662,580 627,589Q592,598 560,616ZM560,506L560,438Q593,424 627.5,417Q662,410 700,410Q726,410 751,414Q776,418 800,424L800,488Q776,479 751.5,474.5Q727,470 700,470Q662,470 627,479.5Q592,489 560,506ZM260,640Q307,640 351.5,650.5Q396,661 440,682L440,288Q399,264 353,252Q307,240 260,240Q224,240 188.5,247Q153,254 120,268Q120,268 120,268Q120,268 120,268L120,664Q120,664 120,664Q120,664 120,664Q155,652 189.5,646Q224,640 260,640ZM520,682Q564,661 608.5,650.5Q653,640 700,640Q736,640 770.5,646Q805,652 840,664Q840,664 840,664Q840,664 840,664L840,268Q840,268 840,268Q840,268 840,268Q807,254 771.5,247Q736,240 700,240Q653,240 607,252Q561,264 520,288L520,682ZM480,800Q432,762 376,741Q320,720 260,720Q218,720 177.5,731Q137,742 100,762Q79,773 59.5,761Q40,749 40,726L40,244Q40,233 45.5,223Q51,213 62,208Q108,184 158,172Q208,160 260,160Q318,160 373.5,175Q429,190 480,220Q531,190 586.5,175Q642,160 700,160Q752,160 802,172Q852,184 898,208Q909,213 914.5,223Q920,233 920,244L920,726Q920,749 900.5,761Q881,773 860,762Q823,742 782.5,731Q742,720 700,720Q640,720 584,741Q528,762 480,800ZM280,466Q280,466 280,466Q280,466 280,466L280,466Q280,466 280,466Q280,466 280,466Q280,466 280,466Q280,466 280,466Q280,466 280,466Q280,466 280,466L280,466Q280,466 280,466Q280,466 280,466Q280,466 280,466Q280,466 280,466Z" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_zen_mode_icon_child.xml b/core/res/res/drawable/ic_zen_mode_icon_child.xml new file mode 100644 index 000000000000..d11772ac1db6 --- /dev/null +++ b/core/res/res/drawable/ic_zen_mode_icon_child.xml @@ -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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?android:attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + <path + android:fillColor="@android:color/white" + android:pathData="M580,470Q559,470 544.5,455.5Q530,441 530,420Q530,399 544.5,384.5Q559,370 580,370Q601,370 615.5,384.5Q630,399 630,420Q630,441 615.5,455.5Q601,470 580,470ZM380,470Q359,470 344.5,455.5Q330,441 330,420Q330,399 344.5,384.5Q359,370 380,370Q401,370 415.5,384.5Q430,399 430,420Q430,441 415.5,455.5Q401,470 380,470ZM480,680Q420,680 371.5,647Q323,614 300,560L660,560Q637,614 588.5,647Q540,680 480,680ZM480,840Q405,840 339.5,811.5Q274,783 225.5,734.5Q177,686 148.5,620.5Q120,555 120,480Q120,405 148.5,339.5Q177,274 225.5,225.5Q274,177 339.5,148.5Q405,120 480,120Q555,120 620.5,148.5Q686,177 734.5,225.5Q783,274 811.5,339.5Q840,405 840,480Q840,555 811.5,620.5Q783,686 734.5,734.5Q686,783 620.5,811.5Q555,840 480,840ZM480,760Q596,760 678,678Q760,596 760,480Q760,364 678,282Q596,200 480,200Q474,200 468,200Q462,200 456,202Q450,208 448,215Q446,222 446,230Q446,251 460.5,265.5Q475,280 496,280Q505,280 512.5,277Q520,274 528,274Q540,274 548,283Q556,292 556,304Q556,327 534.5,333.5Q513,340 496,340Q451,340 418.5,307.5Q386,275 386,230Q386,227 386,224Q386,221 387,216Q304,246 252,317Q200,388 200,480Q200,596 282,678Q364,760 480,760ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Z" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_zen_mode_icon_classical_building.xml b/core/res/res/drawable/ic_zen_mode_icon_classical_building.xml new file mode 100644 index 000000000000..26751262c673 --- /dev/null +++ b/core/res/res/drawable/ic_zen_mode_icon_classical_building.xml @@ -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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?android:attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + <path + android:fillColor="@android:color/white" + android:pathData="M200,680L200,400L280,400L280,680L200,680ZM440,680L440,400L520,400L520,680L440,680ZM80,840L80,760L880,760L880,840L80,840ZM680,680L680,400L760,400L760,680L680,680ZM80,320L80,240L480,40L880,240L880,320L80,320ZM258,240L480,240L702,240L258,240ZM258,240L702,240L480,130L258,240Z" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_zen_mode_icon_croissant.xml b/core/res/res/drawable/ic_zen_mode_icon_croissant.xml new file mode 100644 index 000000000000..199343d0e502 --- /dev/null +++ b/core/res/res/drawable/ic_zen_mode_icon_croissant.xml @@ -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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?android:attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + <path + android:fillColor="@android:color/white" + android:pathData="M804,678Q821,687 834,674Q847,661 838,644L780,536L738,644L804,678ZM604,640L652,640L748,402Q751,394 746.5,388.5Q742,383 736,380L656,348Q647,345 638.5,350Q630,355 628,364L604,640ZM308,640L356,640L332,364Q330,353 321.5,349Q313,345 304,348L224,380Q216,383 212.5,388.5Q209,394 212,402L308,640ZM156,678L222,644L180,536L122,644Q113,661 126,674Q139,687 156,678ZM436,640L524,640L554,302Q556,293 549.5,286.5Q543,280 534,280L426,280Q418,280 411.5,286.5Q405,293 406,302L436,640ZM138,760Q96,760 68,728.5Q40,697 40,654Q40,642 43.5,630.5Q47,619 52,608L140,440Q126,400 141,361Q156,322 194,306L274,274Q288,269 302,267Q316,265 330,268Q344,239 369,219.5Q394,200 426,200L534,200Q566,200 591,219.5Q616,239 630,268Q644,266 658,267.5Q672,269 686,274L766,306Q806,322 822,361Q838,400 820,438L908,606Q914,617 917,629Q920,641 920,654Q920,699 889.5,729.5Q859,760 814,760Q803,760 792,757.5Q781,755 770,750L708,720L250,720L194,750Q181,757 166.5,758.5Q152,760 138,760ZM480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Z" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_zen_mode_icon_fork_and_knife.xml b/core/res/res/drawable/ic_zen_mode_icon_fork_and_knife.xml new file mode 100644 index 000000000000..1fa73794eb32 --- /dev/null +++ b/core/res/res/drawable/ic_zen_mode_icon_fork_and_knife.xml @@ -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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?android:attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + <path + android:fillColor="@android:color/white" + android:pathData="M280,880L280,514Q229,500 194.5,458Q160,416 160,360L160,80L240,80L240,360L280,360L280,80L360,80L360,360L400,360L400,80L480,80L480,360Q480,416 445.5,458Q411,500 360,514L360,880L280,880ZM680,880L680,560L560,560L560,280Q560,197 618.5,138.5Q677,80 760,80L760,880L680,880Z" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_zen_mode_icon_group_of_people.xml b/core/res/res/drawable/ic_zen_mode_icon_group_of_people.xml new file mode 100644 index 000000000000..c6194d529117 --- /dev/null +++ b/core/res/res/drawable/ic_zen_mode_icon_group_of_people.xml @@ -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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?android:attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + <path + android:fillColor="@android:color/white" + android:pathData="M0,720L0,657Q0,614 44,587Q88,560 160,560Q173,560 185,560.5Q197,561 208,563Q194,584 187,607Q180,630 180,655L180,720L0,720ZM240,720L240,655Q240,623 257.5,596.5Q275,570 307,550Q339,530 383.5,520Q428,510 480,510Q533,510 577.5,520Q622,530 654,550Q686,570 703,596.5Q720,623 720,655L720,720L240,720ZM780,720L780,655Q780,629 773.5,606Q767,583 754,563Q765,561 776.5,560.5Q788,560 800,560Q872,560 916,586.5Q960,613 960,657L960,720L780,720ZM325,640L636,640L636,640Q626,620 580.5,605Q535,590 480,590Q425,590 379.5,605Q334,620 325,640ZM160,520Q127,520 103.5,496.5Q80,473 80,440Q80,406 103.5,383Q127,360 160,360Q194,360 217,383Q240,406 240,440Q240,473 217,496.5Q194,520 160,520ZM800,520Q767,520 743.5,496.5Q720,473 720,440Q720,406 743.5,383Q767,360 800,360Q834,360 857,383Q880,406 880,440Q880,473 857,496.5Q834,520 800,520ZM480,480Q430,480 395,445Q360,410 360,360Q360,309 395,274.5Q430,240 480,240Q531,240 565.5,274.5Q600,309 600,360Q600,410 565.5,445Q531,480 480,480ZM480,400Q497,400 508.5,388.5Q520,377 520,360Q520,343 508.5,331.5Q497,320 480,320Q463,320 451.5,331.5Q440,343 440,360Q440,377 451.5,388.5Q463,400 480,400ZM481,640L481,640Q481,640 481,640Q481,640 481,640Q481,640 481,640Q481,640 481,640L481,640ZM480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Z" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_zen_mode_icon_headphones.xml b/core/res/res/drawable/ic_zen_mode_icon_headphones.xml new file mode 100644 index 000000000000..f0bc7a211ca5 --- /dev/null +++ b/core/res/res/drawable/ic_zen_mode_icon_headphones.xml @@ -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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?android:attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + <path + android:fillColor="@android:color/white" + android:pathData="M360,840L200,840Q167,840 143.5,816.5Q120,793 120,760L120,480Q120,405 148.5,339.5Q177,274 225.5,225.5Q274,177 339.5,148.5Q405,120 480,120Q555,120 620.5,148.5Q686,177 734.5,225.5Q783,274 811.5,339.5Q840,405 840,480L840,760Q840,793 816.5,816.5Q793,840 760,840L600,840L600,520L760,520L760,480Q760,363 678.5,281.5Q597,200 480,200Q363,200 281.5,281.5Q200,363 200,480L200,520L360,520L360,840ZM280,600L200,600L200,760Q200,760 200,760Q200,760 200,760L280,760L280,600ZM680,600L680,760L760,760Q760,760 760,760Q760,760 760,760L760,600L680,600ZM280,600L280,600L200,600Q200,600 200,600Q200,600 200,600L200,600L280,600ZM680,600L760,600L760,600Q760,600 760,600Q760,600 760,600L680,600Z" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_zen_mode_icon_heart.xml b/core/res/res/drawable/ic_zen_mode_icon_heart.xml new file mode 100644 index 000000000000..c9b1577d57f4 --- /dev/null +++ b/core/res/res/drawable/ic_zen_mode_icon_heart.xml @@ -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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?android:attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + <path + android:fillColor="@android:color/white" + android:pathData="M480,840L422,788Q321,697 255,631Q189,565 150,512.5Q111,460 95.5,416Q80,372 80,326Q80,232 143,169Q206,106 300,106Q352,106 399,128Q446,150 480,190Q514,150 561,128Q608,106 660,106Q754,106 817,169Q880,232 880,326Q880,372 864.5,416Q849,460 810,512.5Q771,565 705,631Q639,697 538,788L480,840ZM480,732Q576,646 638,584.5Q700,523 736,477.5Q772,432 786,396.5Q800,361 800,326Q800,266 760,226Q720,186 660,186Q613,186 573,212.5Q533,239 518,280L518,280L442,280L442,280Q427,239 387,212.5Q347,186 300,186Q240,186 200,226Q160,266 160,326Q160,361 174,396.5Q188,432 224,477.5Q260,523 322,584.5Q384,646 480,732ZM480,459Q480,459 480,459Q480,459 480,459Q480,459 480,459Q480,459 480,459Q480,459 480,459Q480,459 480,459Q480,459 480,459Q480,459 480,459L480,459L480,459L480,459Q480,459 480,459Q480,459 480,459Q480,459 480,459Q480,459 480,459Q480,459 480,459Q480,459 480,459Q480,459 480,459Q480,459 480,459Z" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_zen_mode_icon_house.xml b/core/res/res/drawable/ic_zen_mode_icon_house.xml new file mode 100644 index 000000000000..e25d194eb8d0 --- /dev/null +++ b/core/res/res/drawable/ic_zen_mode_icon_house.xml @@ -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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?android:attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + <path + android:fillColor="@android:color/white" + android:pathData="M160,840L160,465L88,520L40,456L160,364L160,240L240,240L240,303L480,120L920,456L872,519L800,465L800,840L160,840ZM240,760L440,760L440,600L520,600L520,760L720,760L720,404L480,221L240,404L240,760ZM160,200Q160,150 195,115Q230,80 280,80Q297,80 308.5,68.5Q320,57 320,40L400,40Q400,90 365,125Q330,160 280,160Q263,160 251.5,171.5Q240,183 240,200L160,200ZM240,760L440,760L440,760L520,760L520,760L720,760L720,760L480,760L240,760Z" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_zen_mode_icon_lightbulb.xml b/core/res/res/drawable/ic_zen_mode_icon_lightbulb.xml new file mode 100644 index 000000000000..602e60d0824a --- /dev/null +++ b/core/res/res/drawable/ic_zen_mode_icon_lightbulb.xml @@ -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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?android:attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + <path + android:fillColor="@android:color/white" + android:pathData="M480,880Q454,880 433,867.5Q412,855 400,834L400,834Q367,834 343.5,810.5Q320,787 320,754L320,612Q261,573 225.5,509Q190,445 190,370Q190,249 274.5,164.5Q359,80 480,80Q601,80 685.5,164.5Q770,249 770,370Q770,447 734.5,510Q699,573 640,612L640,754Q640,787 616.5,810.5Q593,834 560,834L560,834Q548,855 527,867.5Q506,880 480,880ZM400,754L560,754L560,718L400,718L400,754ZM400,678L560,678L560,640L400,640L400,678ZM392,560L450,560L450,452L362,364L404,322L480,398L556,322L598,364L510,452L510,560L568,560Q622,534 656,483.5Q690,433 690,370Q690,282 629,221Q568,160 480,160Q392,160 331,221Q270,282 270,370Q270,433 304,483.5Q338,534 392,560ZM480,398L480,398L480,398L480,398L480,398L480,398L480,398L480,398L480,398ZM480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360L480,360L480,360L480,360L480,360L480,360L480,360L480,360L480,360L480,360Z" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_zen_mode_icon_palette.xml b/core/res/res/drawable/ic_zen_mode_icon_palette.xml new file mode 100644 index 000000000000..f31704de1498 --- /dev/null +++ b/core/res/res/drawable/ic_zen_mode_icon_palette.xml @@ -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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?android:attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + <path + android:fillColor="@android:color/white" + android:pathData="M480,880Q398,880 325,848.5Q252,817 197.5,762.5Q143,708 111.5,635Q80,562 80,480Q80,397 112.5,324Q145,251 200.5,197Q256,143 330,111.5Q404,80 488,80Q568,80 639,107.5Q710,135 763.5,183.5Q817,232 848.5,298.5Q880,365 880,442Q880,557 810,618.5Q740,680 640,680L566,680Q557,680 553.5,685Q550,690 550,696Q550,708 565,730.5Q580,753 580,782Q580,832 552.5,856Q525,880 480,880ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480ZM260,520Q286,520 303,503Q320,486 320,460Q320,434 303,417Q286,400 260,400Q234,400 217,417Q200,434 200,460Q200,486 217,503Q234,520 260,520ZM380,360Q406,360 423,343Q440,326 440,300Q440,274 423,257Q406,240 380,240Q354,240 337,257Q320,274 320,300Q320,326 337,343Q354,360 380,360ZM580,360Q606,360 623,343Q640,326 640,300Q640,274 623,257Q606,240 580,240Q554,240 537,257Q520,274 520,300Q520,326 537,343Q554,360 580,360ZM700,520Q726,520 743,503Q760,486 760,460Q760,434 743,417Q726,400 700,400Q674,400 657,417Q640,434 640,460Q640,486 657,503Q674,520 700,520ZM480,800Q489,800 494.5,795Q500,790 500,782Q500,768 485,749Q470,730 470,692Q470,650 499,625Q528,600 570,600L640,600Q706,600 753,561.5Q800,523 800,442Q800,321 707.5,240.5Q615,160 488,160Q352,160 256,253Q160,346 160,480Q160,613 253.5,706.5Q347,800 480,800Z" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_zen_mode_icon_rabbit.xml b/core/res/res/drawable/ic_zen_mode_icon_rabbit.xml new file mode 100644 index 000000000000..190d0cb319ec --- /dev/null +++ b/core/res/res/drawable/ic_zen_mode_icon_rabbit.xml @@ -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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?android:attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + <path + android:fillColor="@android:color/white" + android:pathData="M380,880Q305,880 252.5,827.5Q200,775 200,700Q200,665 217,635.5Q234,606 280,560Q286,554 291.5,547.5Q297,541 306,530Q255,452 227.5,366.5Q200,281 200,200Q200,142 221,111Q242,80 280,80Q337,80 382,135Q427,190 450,236Q459,256 466.5,276.5Q474,297 480,319Q486,297 493.5,276.5Q501,256 511,236Q533,190 578,135Q623,80 680,80Q718,80 739,111Q760,142 760,200Q760,281 732.5,366.5Q705,452 654,530Q663,541 668.5,547.5Q674,554 680,560Q726,606 743,635.5Q760,665 760,700Q760,775 707.5,827.5Q655,880 580,880Q535,880 507.5,870Q480,860 480,860Q480,860 452.5,870Q425,880 380,880ZM380,800Q403,800 426,794.5Q449,789 469,778Q458,773 449,761Q440,749 440,740Q440,732 451.5,726Q463,720 480,720Q497,720 508.5,726Q520,732 520,740Q520,749 511,761Q502,773 491,778Q511,789 534,794.5Q557,800 580,800Q622,800 651,771Q680,742 680,700Q680,682 670,665Q660,648 640,631Q626,619 617,610Q608,601 588,576Q559,541 540,530.5Q521,520 480,520Q439,520 419.5,530.5Q400,541 372,576Q352,601 343,610Q334,619 320,631Q300,648 290,665Q280,682 280,700Q280,742 309,771Q338,800 380,800ZM420,670Q412,670 406,661Q400,652 400,640Q400,628 406,619Q412,610 420,610Q428,610 434,619Q440,628 440,640Q440,652 434,661Q428,670 420,670ZM540,670Q532,670 526,661Q520,652 520,640Q520,628 526,619Q532,610 540,610Q548,610 554,619Q560,628 560,640Q560,652 554,661Q548,670 540,670ZM363,471Q374,463 388,457Q402,451 419,446Q417,398 404.5,350.5Q392,303 373,264Q354,224 331,196.5Q308,169 285,161Q283,167 281.5,176.5Q280,186 280,200Q280,268 301.5,338Q323,408 363,471ZM597,471Q637,408 658.5,338Q680,268 680,200Q680,186 678.5,176.5Q677,167 675,161Q652,169 629,196.5Q606,224 587,264Q569,303 556.5,350.5Q544,398 541,446Q556,450 570,456.5Q584,463 597,471Z" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_zen_mode_icon_running.xml b/core/res/res/drawable/ic_zen_mode_icon_running.xml new file mode 100644 index 000000000000..472b04e24bc0 --- /dev/null +++ b/core/res/res/drawable/ic_zen_mode_icon_running.xml @@ -0,0 +1,26 @@ +<!-- +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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?android:attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960" + android:autoMirrored="true"> + <path + android:fillColor="@android:color/white" + android:pathData="M520,920L520,680L436,600L396,776L120,720L136,640L328,680L392,356L320,384L320,520L240,520L240,332L398,264Q433,249 449.5,244.5Q466,240 480,240Q501,240 519,251Q537,262 548,280L588,344Q614,386 658.5,413Q703,440 760,440L760,520Q694,520 636.5,492.5Q579,465 540,420L516,540L600,620L600,920L520,920ZM540,220Q507,220 483.5,196.5Q460,173 460,140Q460,107 483.5,83.5Q507,60 540,60Q573,60 596.5,83.5Q620,107 620,140Q620,173 596.5,196.5Q573,220 540,220Z" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_zen_mode_icon_shopping_cart.xml b/core/res/res/drawable/ic_zen_mode_icon_shopping_cart.xml new file mode 100644 index 000000000000..92cec4dde7b7 --- /dev/null +++ b/core/res/res/drawable/ic_zen_mode_icon_shopping_cart.xml @@ -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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?android:attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + <path + android:fillColor="@android:color/white" + android:pathData="M280,880Q247,880 223.5,856.5Q200,833 200,800Q200,767 223.5,743.5Q247,720 280,720Q313,720 336.5,743.5Q360,767 360,800Q360,833 336.5,856.5Q313,880 280,880ZM680,880Q647,880 623.5,856.5Q600,833 600,800Q600,767 623.5,743.5Q647,720 680,720Q713,720 736.5,743.5Q760,767 760,800Q760,833 736.5,856.5Q713,880 680,880ZM246,240L342,440L622,440Q622,440 622,440Q622,440 622,440L732,240Q732,240 732,240Q732,240 732,240L246,240ZM208,160L798,160Q821,160 833,180.5Q845,201 834,222L692,478Q681,498 662.5,509Q644,520 622,520L324,520L280,600Q280,600 280,600Q280,600 280,600L760,600L760,680L280,680Q235,680 212,640.5Q189,601 210,562L264,464L120,160L40,160L40,80L170,80L208,160ZM342,440L342,440L622,440Q622,440 622,440Q622,440 622,440L622,440L342,440Z" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_zen_mode_icon_snowflake.xml b/core/res/res/drawable/ic_zen_mode_icon_snowflake.xml new file mode 100644 index 000000000000..1746e20a6aae --- /dev/null +++ b/core/res/res/drawable/ic_zen_mode_icon_snowflake.xml @@ -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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?android:attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + <path + android:fillColor="@android:color/white" + android:pathData="M440,880L440,714L310,842L254,786L440,600L440,520L360,520L174,706L118,650L246,520L80,520L80,440L246,440L118,310L174,254L360,440L440,440L440,360L254,174L310,118L440,246L440,80L520,80L520,246L650,118L706,174L520,360L520,440L600,440L786,254L842,310L714,440L880,440L880,520L714,520L842,650L786,706L600,520L520,520L520,600L706,786L650,842L520,714L520,880L440,880Z" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_zen_mode_icon_speech_bubble.xml b/core/res/res/drawable/ic_zen_mode_icon_speech_bubble.xml new file mode 100644 index 000000000000..4be98abd9369 --- /dev/null +++ b/core/res/res/drawable/ic_zen_mode_icon_speech_bubble.xml @@ -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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?android:attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + <path + android:fillColor="@android:color/white" + android:pathData="M80,880L80,160Q80,127 103.5,103.5Q127,80 160,80L800,80Q833,80 856.5,103.5Q880,127 880,160L880,640Q880,673 856.5,696.5Q833,720 800,720L240,720L80,880ZM206,640L800,640Q800,640 800,640Q800,640 800,640L800,160Q800,160 800,160Q800,160 800,160L160,160Q160,160 160,160Q160,160 160,160L160,685L206,640ZM160,640L160,640L160,160Q160,160 160,160Q160,160 160,160L160,160Q160,160 160,160Q160,160 160,160L160,640Q160,640 160,640Q160,640 160,640Z" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_zen_mode_icon_train.xml b/core/res/res/drawable/ic_zen_mode_icon_train.xml new file mode 100644 index 000000000000..b6f3445468a5 --- /dev/null +++ b/core/res/res/drawable/ic_zen_mode_icon_train.xml @@ -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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?android:attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + <path + android:fillColor="@android:color/white" + android:pathData="M160,620L160,240Q160,187 187.5,155.5Q215,124 260,107.5Q305,91 362.5,85.5Q420,80 480,80Q546,80 604.5,85.5Q663,91 706.5,107.5Q750,124 775,155.5Q800,187 800,240L800,620Q800,679 759.5,719.5Q719,760 660,760L720,820L720,840L640,840L560,760L400,760L320,840L240,840L240,820L300,760Q241,760 200.5,719.5Q160,679 160,620ZM480,160Q374,160 325,172.5Q276,185 258,200L706,200Q691,183 641.5,171.5Q592,160 480,160ZM240,400L440,400L440,280L240,280L240,400ZM660,480L300,480Q274,480 257,480Q240,480 240,480L240,480L720,480L720,480Q720,480 703,480Q686,480 660,480ZM520,400L720,400L720,280L520,280L520,400ZM340,640Q366,640 383,623Q400,606 400,580Q400,554 383,537Q366,520 340,520Q314,520 297,537Q280,554 280,580Q280,606 297,623Q314,640 340,640ZM620,640Q646,640 663,623Q680,606 680,580Q680,554 663,537Q646,520 620,520Q594,520 577,537Q560,554 560,580Q560,606 577,623Q594,640 620,640ZM300,680L660,680Q686,680 703,663Q720,646 720,620L720,480L240,480L240,620Q240,646 257,663Q274,680 300,680ZM480,200Q592,200 641.5,200Q691,200 706,200L258,200Q276,200 325,200Q374,200 480,200Z" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_zen_mode_icon_tv.xml b/core/res/res/drawable/ic_zen_mode_icon_tv.xml new file mode 100644 index 000000000000..eaa920aa37cc --- /dev/null +++ b/core/res/res/drawable/ic_zen_mode_icon_tv.xml @@ -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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?android:attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + <path + android:fillColor="@android:color/white" + android:pathData="M320,840L320,760L160,760Q127,760 103.5,736.5Q80,713 80,680L80,200Q80,167 103.5,143.5Q127,120 160,120L800,120Q833,120 856.5,143.5Q880,167 880,200L880,680Q880,713 856.5,736.5Q833,760 800,760L640,760L640,840L320,840ZM160,680L800,680Q800,680 800,680Q800,680 800,680L800,200Q800,200 800,200Q800,200 800,200L160,200Q160,200 160,200Q160,200 160,200L160,680Q160,680 160,680Q160,680 160,680ZM160,680Q160,680 160,680Q160,680 160,680L160,200Q160,200 160,200Q160,200 160,200L160,200Q160,200 160,200Q160,200 160,200L160,680Q160,680 160,680Q160,680 160,680Z" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/layout-car/car_resolver_list.xml b/core/res/res/layout-car/car_resolver_list.xml deleted file mode 100644 index 08c98615087b..000000000000 --- a/core/res/res/layout-car/car_resolver_list.xml +++ /dev/null @@ -1,127 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* -* Copyright 2019, The Android Open Source Project -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ ---> -<com.android.internal.widget.ResolverDrawerLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="@dimen/car_activity_resolver_width" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:id="@id/contentPanel"> - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical" - android:background="@drawable/car_activity_resolver_list_background"> - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:background="@drawable/car_activity_resolver_list_background" - android:orientation="horizontal" - android:paddingVertical="@dimen/car_padding_4" - android:paddingHorizontal="@dimen/car_padding_4" > - <TextView - android:id="@+id/profile_button" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:visibility="gone" /> - - <TextView - android:id="@+id/title" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="start" - android:textAppearance="@android:style/TextAppearance.DeviceDefault.DialogWindowTitle" /> - </LinearLayout> - - <FrameLayout - android:id="@+id/stub" - android:visibility="gone" - android:layout_width="match_parent" - android:layout_height="wrap_content"/> - - <TabHost - android:id="@+id/profile_tabhost" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_alignParentTop="true" - android:layout_centerHorizontal="true" - android:background="?android:attr/colorBackgroundFloating"> - <LinearLayout - android:orientation="vertical" - android:layout_width="match_parent" - android:layout_height="wrap_content"> - <TabWidget - android:id="@android:id/tabs" - android:visibility="gone" - android:layout_width="match_parent" - android:layout_height="wrap_content"> - </TabWidget> - <View - android:id="@+id/resolver_tab_divider" - android:visibility="gone" - android:layout_width="match_parent" - android:layout_height="wrap_content" /> - <FrameLayout - android:id="@android:id/tabcontent" - android:layout_width="match_parent" - android:layout_height="wrap_content"> - <com.android.internal.app.ResolverViewPager - android:id="@+id/profile_pager" - android:layout_width="match_parent" - android:layout_height="wrap_content"/> - </FrameLayout> - </LinearLayout> - </TabHost> - - <LinearLayout - android:id="@+id/button_bar" - android:visibility="gone" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginVertical="@dimen/car_padding_4" - android:layout_marginHorizontal="@dimen/car_padding_4" - android:padding="0dp" - android:gravity="center" - android:background="@drawable/car_activity_resolver_list_background" - android:orientation="vertical"> - - <Button - android:id="@+id/button_once" - android:layout_width="match_parent" - android:layout_height="@dimen/car_button_height" - android:enabled="false" - android:layout_gravity="center" - android:layout_marginBottom="@dimen/car_padding_2" - android:text="@string/activity_resolver_use_once" - android:onClick="onButtonClick"/> - - <Button - android:id="@+id/button_always" - android:layout_width="match_parent" - android:layout_height="@dimen/car_button_height" - android:enabled="false" - android:layout_gravity="center" - android:text="@string/activity_resolver_use_always" - android:onClick="onButtonClick"/> - </LinearLayout> - - </LinearLayout> - -</com.android.internal.widget.ResolverDrawerLayout>
\ No newline at end of file diff --git a/core/res/res/layout-car/car_resolver_list_with_default.xml b/core/res/res/layout-car/car_resolver_list_with_default.xml deleted file mode 100644 index 08cc7ff3021f..000000000000 --- a/core/res/res/layout-car/car_resolver_list_with_default.xml +++ /dev/null @@ -1,159 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* -* Copyright 2019, The Android Open Source Project -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ ---> -<com.android.internal.widget.ResolverDrawerLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="@dimen/car_activity_resolver_width" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:id="@id/contentPanel"> - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical" - android:layout_gravity="center" - android:background="@drawable/car_activity_resolver_list_background"> - - <FrameLayout - android:id="@+id/stub" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:background="@drawable/car_activity_resolver_list_background"/> - - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:minHeight="@dimen/car_activity_resolver_list_item_height" - android:orientation="horizontal"> - - <RadioButton - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:focusable="false" - android:clickable="false" - android:layout_marginStart="?attr/listPreferredItemPaddingStart" - android:layout_gravity="start|center_vertical" - android:checked="true"/> - - <ImageView - android:id="@+id/icon" - android:layout_width="@dimen/car_icon_size" - android:layout_height="@dimen/car_icon_size" - android:layout_gravity="start|center_vertical" - android:layout_marginStart="@dimen/car_padding_4" - android:src="@drawable/resolver_icon_placeholder" - android:scaleType="fitCenter"/> - - <TextView - android:id="@+id/title" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginHorizontal="?attr/listPreferredItemPaddingStart" - style="?android:attr/textAppearanceListItem" - android:layout_gravity="start|center_vertical" /> - - <LinearLayout - android:id="@+id/profile_button" - android:visibility="gone" - android:layout_width="wrap_content" - android:layout_height="wrap_content"> - - <ImageView - android:id="@+id/icon" - android:visibility="gone" - android:layout_width="wrap_content" - android:layout_height="wrap_content" /> - - <TextView - android:id="@id/text1" - android:visibility="gone" - android:layout_width="wrap_content" - android:layout_height="wrap_content" /> - </LinearLayout> - </LinearLayout> - - <TabHost - android:id="@+id/profile_tabhost" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_alignParentTop="true" - android:layout_centerHorizontal="true" - android:background="?attr/colorBackgroundFloating"> - <LinearLayout - android:orientation="vertical" - android:layout_width="match_parent" - android:layout_height="wrap_content"> - <TabWidget - android:id="@android:id/tabs" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:visibility="gone"> - </TabWidget> - <View - android:id="@+id/resolver_tab_divider" - android:visibility="gone" - android:layout_width="match_parent" - android:layout_height="wrap_content" /> - <FrameLayout - android:id="@android:id/tabcontent" - android:layout_width="match_parent" - android:layout_height="wrap_content"> - <com.android.internal.app.ResolverViewPager - android:id="@+id/profile_pager" - android:layout_width="match_parent" - android:layout_height="wrap_content"> - </com.android.internal.app.ResolverViewPager> - </FrameLayout> - </LinearLayout> - </TabHost> - - <LinearLayout - android:id="@+id/button_bar" - android:visibility="gone" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginVertical="@dimen/car_padding_4" - android:layout_marginHorizontal="@dimen/car_padding_4" - android:gravity="center" - android:background="@drawable/car_activity_resolver_list_background" - android:orientation="vertical"> - - <Button - android:id="@+id/button_once" - android:layout_width="match_parent" - android:layout_height="@dimen/car_button_height" - android:enabled="false" - android:layout_gravity="center" - android:layout_marginBottom="@dimen/car_padding_2" - android:text="@string/activity_resolver_use_once" - android:onClick="onButtonClick"/> - - <Button - android:id="@+id/button_always" - android:layout_width="match_parent" - android:layout_height="@dimen/car_button_height" - android:enabled="false" - android:layout_gravity="center" - android:text="@string/activity_resolver_use_always" - android:onClick="onButtonClick"/> - </LinearLayout> - </LinearLayout> - -</com.android.internal.widget.ResolverDrawerLayout> diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml index 61c7a8cde7d5..383033d9ae5d 100644 --- a/core/res/res/values/config_telephony.xml +++ b/core/res/res/values/config_telephony.xml @@ -237,6 +237,13 @@ <integer name="config_emergency_call_wait_for_connection_timeout_millis">20000</integer> <java-symbol type="integer" name="config_emergency_call_wait_for_connection_timeout_millis" /> + <!-- The time duration in millis after which Telephony will stop waiting for the OFF state + from cellular modem, consider the request to power off cellular modem as failed, and thus + treat the cellular modem state as ON. + --> + <integer name="config_satellite_wait_for_cellular_modem_off_timeout_millis">120000</integer> + <java-symbol type="integer" name="config_satellite_wait_for_cellular_modem_off_timeout_millis" /> + <!-- Indicates the data limit in bytes that can be used for bootstrap sim until factory reset. -1 means unlimited. --> <integer name="config_esim_bootstrap_data_limit_bytes">-1</integer> diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java index 832ebe534c67..6dad3b7b2ac4 100644 --- a/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java +++ b/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java @@ -999,4 +999,31 @@ public class SQLiteRawStatementTest { mDatabase.endTransaction(); } } + + /** + * This test verifies that the JNI exception thrown because of a bad column is actually thrown + * and does not crash the VM. + */ + @Test + public void testJniExceptions() { + // Create the t1 table. + mDatabase.beginTransaction(); + try { + mDatabase.execSQL("CREATE TABLE t1 (i int, j int);"); + mDatabase.setTransactionSuccessful(); + } finally { + mDatabase.endTransaction(); + } + + mDatabase.beginTransactionReadOnly(); + try (SQLiteRawStatement s = mDatabase.createRawStatement("SELECT * from t1")) { + s.step(); + s.getColumnText(5); // out-of-range column + fail("JNI exception not thrown"); + } catch (SQLiteBindOrColumnIndexOutOfRangeException e) { + // Passing case. + } finally { + mDatabase.endTransaction(); + } + } } diff --git a/core/tests/coretests/src/android/graphics/PaintFontVariationTest.java b/core/tests/coretests/src/android/graphics/PaintFontVariationTest.java new file mode 100644 index 000000000000..8a54e5b998e7 --- /dev/null +++ b/core/tests/coretests/src/android/graphics/PaintFontVariationTest.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.graphics; + +import static com.google.common.truth.Truth.assertThat; + +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.test.InstrumentationTestCase; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.text.flags.Flags; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * PaintTest tests {@link Paint}. + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class PaintFontVariationTest extends InstrumentationTestCase { + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + + @RequiresFlagsEnabled(Flags.FLAG_TYPEFACE_CACHE_FOR_VAR_SETTINGS) + @Test + public void testDerivedFromSameTypeface() { + final Paint p = new Paint(); + + p.setTypeface(Typeface.SANS_SERIF); + assertThat(p.setFontVariationSettings("'wght' 450")).isTrue(); + Typeface first = p.getTypeface(); + + p.setTypeface(Typeface.SANS_SERIF); + assertThat(p.setFontVariationSettings("'wght' 480")).isTrue(); + Typeface second = p.getTypeface(); + + assertThat(first.getDerivedFrom()).isSameInstanceAs(second.getDerivedFrom()); + } + + @RequiresFlagsEnabled(Flags.FLAG_TYPEFACE_CACHE_FOR_VAR_SETTINGS) + @Test + public void testDerivedFromChained() { + final Paint p = new Paint(); + + p.setTypeface(Typeface.SANS_SERIF); + assertThat(p.setFontVariationSettings("'wght' 450")).isTrue(); + Typeface first = p.getTypeface(); + + assertThat(p.setFontVariationSettings("'wght' 480")).isTrue(); + Typeface second = p.getTypeface(); + + assertThat(first.getDerivedFrom()).isSameInstanceAs(second.getDerivedFrom()); + } +} diff --git a/core/tests/coretests/src/android/graphics/PaintTest.java b/core/tests/coretests/src/android/graphics/PaintTest.java index 0dec756d7611..878ba703c8fe 100644 --- a/core/tests/coretests/src/android/graphics/PaintTest.java +++ b/core/tests/coretests/src/android/graphics/PaintTest.java @@ -16,13 +16,22 @@ package android.graphics; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertNotEquals; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.test.InstrumentationTestCase; import android.text.TextUtils; import androidx.test.filters.SmallTest; +import com.android.text.flags.Flags; + +import org.junit.Rule; + import java.util.Arrays; import java.util.HashSet; @@ -30,6 +39,9 @@ import java.util.HashSet; * PaintTest tests {@link Paint}. */ public class PaintTest extends InstrumentationTestCase { + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + private static final String FONT_PATH = "fonts/HintedAdvanceWidthTest-Regular.ttf"; static void assertEquals(String message, float[] expected, float[] actual) { @@ -403,4 +415,33 @@ public class PaintTest extends InstrumentationTestCase { assertEquals(6, getClusterCount(p, rtlStr + ltrStr)); assertEquals(9, getClusterCount(p, ltrStr + rtlStr + ltrStr)); } + + @RequiresFlagsEnabled(Flags.FLAG_TYPEFACE_CACHE_FOR_VAR_SETTINGS) + public void testDerivedFromSameTypeface() { + final Paint p = new Paint(); + + p.setTypeface(Typeface.SANS_SERIF); + assertThat(p.setFontVariationSettings("'wght' 450")).isTrue(); + Typeface first = p.getTypeface(); + + p.setTypeface(Typeface.SANS_SERIF); + assertThat(p.setFontVariationSettings("'wght' 480")).isTrue(); + Typeface second = p.getTypeface(); + + assertThat(first.getDerivedFrom()).isSameInstanceAs(second.getDerivedFrom()); + } + + @RequiresFlagsEnabled(Flags.FLAG_TYPEFACE_CACHE_FOR_VAR_SETTINGS) + public void testDerivedFromChained() { + final Paint p = new Paint(); + + p.setTypeface(Typeface.SANS_SERIF); + assertThat(p.setFontVariationSettings("'wght' 450")).isTrue(); + Typeface first = p.getTypeface(); + + assertThat(p.setFontVariationSettings("'wght' 480")).isTrue(); + Typeface second = p.getTypeface(); + + assertThat(first.getDerivedFrom()).isSameInstanceAs(second.getDerivedFrom()); + } } diff --git a/core/tests/coretests/src/android/os/VibrationAttributesTest.java b/core/tests/coretests/src/android/os/VibrationAttributesTest.java index d8142c8d91bf..5bdae0e6455b 100644 --- a/core/tests/coretests/src/android/os/VibrationAttributesTest.java +++ b/core/tests/coretests/src/android/os/VibrationAttributesTest.java @@ -28,11 +28,9 @@ public class VibrationAttributesTest { @Test public void testSimple() throws Exception { final VibrationAttributes attr = new VibrationAttributes.Builder() - .setCategory(VibrationAttributes.CATEGORY_KEYBOARD) .setUsage(VibrationAttributes.USAGE_ALARM) .build(); - assertEquals(VibrationAttributes.CATEGORY_KEYBOARD, attr.getCategory()); assertEquals(VibrationAttributes.USAGE_ALARM, attr.getUsage()); } } diff --git a/core/tests/coretests/src/android/view/contentprotection/OWNERS b/core/tests/coretests/src/android/view/contentprotection/OWNERS index b3583a7f6ab1..3d09da303b0f 100644 --- a/core/tests/coretests/src/android/view/contentprotection/OWNERS +++ b/core/tests/coretests/src/android/view/contentprotection/OWNERS @@ -1,4 +1,4 @@ -# Bug component: 544200 +# Bug component: 1040349 -include /core/java/android/view/contentcapture/OWNERS +include /core/java/android/view/contentprotection/OWNERS diff --git a/core/tests/coretests/src/com/android/internal/util/RateLimitingCacheTest.java b/core/tests/coretests/src/com/android/internal/util/RateLimitingCacheTest.java new file mode 100644 index 000000000000..7541a844c1da --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/util/RateLimitingCacheTest.java @@ -0,0 +1,158 @@ +/* + * 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.util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import android.os.SystemClock; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Test the RateLimitingCache class. + */ +@RunWith(AndroidJUnit4.class) +public class RateLimitingCacheTest { + + private int mCounter = 0; + + @Before + public void before() { + mCounter = -1; + } + + RateLimitingCache.ValueFetcher<Integer> mFetcher = () -> { + return ++mCounter; + }; + + /** + * Test zero period passed into RateLimitingCache. A new value should be returned for each + * time the cache's get() is invoked. + */ + @Test + public void testTtl_Zero() { + RateLimitingCache<Integer> s = new RateLimitingCache<>(0); + + int first = s.get(mFetcher); + assertEquals(first, 0); + int second = s.get(mFetcher); + assertEquals(second, 1); + SystemClock.sleep(20); + int third = s.get(mFetcher); + assertEquals(third, 2); + } + + /** + * Test a period of 100ms passed into RateLimitingCache. A new value should not be fetched + * any more frequently than every 100ms. + */ + @Test + public void testTtl_100() { + RateLimitingCache<Integer> s = new RateLimitingCache<>(100); + + int first = s.get(mFetcher); + assertEquals(first, 0); + int second = s.get(mFetcher); + // Too early to change + assertEquals(second, 0); + SystemClock.sleep(150); + int third = s.get(mFetcher); + // Changed by now + assertEquals(third, 1); + int fourth = s.get(mFetcher); + // Too early to change again + assertEquals(fourth, 1); + } + + /** + * Test a negative period passed into RateLimitingCache. A new value should only be fetched the + * first call to get(). + */ + @Test + public void testTtl_Negative() { + RateLimitingCache<Integer> s = new RateLimitingCache<>(-1); + + int first = s.get(mFetcher); + assertEquals(first, 0); + SystemClock.sleep(200); + // Should return the original value every time + int second = s.get(mFetcher); + assertEquals(second, 0); + } + + /** + * Test making tons of calls to the speed-limiter and make sure number of fetches does not + * exceed expected number of fetches. + */ + @Test + public void testTtl_Spam() { + RateLimitingCache<Integer> s = new RateLimitingCache<>(100); + assertCount(s, 1000, 7, 15); + } + + /** + * Test rate-limiting across multiple periods and make sure the expected number of fetches is + * within the specified rate. + */ + @Test + public void testRate_10hz() { + RateLimitingCache<Integer> s = new RateLimitingCache<>(1000, 10); + // At 10 per second, 2 seconds should not exceed about 30, assuming overlap into left and + // right windows that allow 10 each + assertCount(s, 2000, 20, 33); + } + + /** + * Test that using a different timebase works correctly. + */ + @Test + public void testTimebase() { + RateLimitingCache<Integer> s = new RateLimitingCache<>(1000, 10) { + @Override + protected long getTime() { + return SystemClock.elapsedRealtime() / 2; + } + }; + // Timebase is moving at half the speed, so only allows for 1 second worth in 2 seconds. + assertCount(s, 2000, 10, 22); + } + + /** + * Helper to make repeated calls every 5 millis to verify the number of expected fetches for + * the given parameters. + * @param cache the cache object + * @param period the period for which to make get() calls + * @param minCount the lower end of the expected number of fetches, with a margin for error + * @param maxCount the higher end of the expected number of fetches, with a margin for error + */ + private void assertCount(RateLimitingCache<Integer> cache, long period, + int minCount, int maxCount) { + long startTime = SystemClock.elapsedRealtime(); + while (SystemClock.elapsedRealtime() < startTime + period) { + int value = cache.get(mFetcher); + SystemClock.sleep(5); + } + int latest = cache.get(mFetcher); + assertTrue("Latest should be between " + minCount + " and " + maxCount + + " but is " + latest, latest <= maxCount && latest >= minCount); + } +} diff --git a/core/tests/resourceflaggingtests/src/com/android/resourceflaggingtests/ResourceFlaggingTest.java b/core/tests/resourceflaggingtests/src/com/android/resourceflaggingtests/ResourceFlaggingTest.java index c1e357864fff..471b4021545a 100644 --- a/core/tests/resourceflaggingtests/src/com/android/resourceflaggingtests/ResourceFlaggingTest.java +++ b/core/tests/resourceflaggingtests/src/com/android/resourceflaggingtests/ResourceFlaggingTest.java @@ -68,6 +68,16 @@ public class ResourceFlaggingTest { assertThat(getBoolean("res3")).isTrue(); } + @Test + public void testFlagDisabledStringArrayElement() { + assertThat(getStringArray("strarr1")).isEqualTo(new String[]{"one", "two", "three"}); + } + + @Test + public void testFlagDisabledIntArrayElement() { + assertThat(getIntArray("intarr1")).isEqualTo(new int[]{1, 2, 3}); + } + private boolean getBoolean(String name) { int resId = mResources.getIdentifier( name, @@ -77,13 +87,22 @@ public class ResourceFlaggingTest { return mResources.getBoolean(resId); } - private String getString(String name) { + private String[] getStringArray(String name) { + int resId = mResources.getIdentifier( + name, + "array", + "com.android.intenal.flaggedresources"); + assertThat(resId).isNotEqualTo(0); + return mResources.getStringArray(resId); + } + + private int[] getIntArray(String name) { int resId = mResources.getIdentifier( name, - "string", + "array", "com.android.intenal.flaggedresources"); assertThat(resId).isNotEqualTo(0); - return mResources.getString(resId); + return mResources.getIntArray(resId); } private String extractApkAndGetPath(int id) throws Exception { diff --git a/core/tests/vibrator/src/android/os/VibrationEffectTest.java b/core/tests/vibrator/src/android/os/VibrationEffectTest.java index bd3d94467ead..4f76dd636c30 100644 --- a/core/tests/vibrator/src/android/os/VibrationEffectTest.java +++ b/core/tests/vibrator/src/android/os/VibrationEffectTest.java @@ -60,7 +60,7 @@ import java.util.Arrays; @RunWith(MockitoJUnitRunner.class) public class VibrationEffectTest { - + private static final float TOLERANCE = 1e-2f; private static final String RINGTONE_URI_1 = "content://test/system/ringtone_1"; private static final String RINGTONE_URI_2 = "content://test/system/ringtone_2"; private static final String RINGTONE_URI_3 = "content://test/system/ringtone_3"; @@ -709,7 +709,7 @@ public class VibrationEffectTest { @Test public void testScaleWaveform() { VibrationEffect scaledUp = TEST_WAVEFORM.scale(1.5f); - assertEquals(1f, getStepSegment(scaledUp, 0).getAmplitude(), 1e-5f); + assertEquals(1f, getStepSegment(scaledUp, 0).getAmplitude(), TOLERANCE); VibrationEffect scaledDown = TEST_WAVEFORM.scale(0.5f); assertTrue(1f > getStepSegment(scaledDown, 0).getAmplitude()); @@ -731,11 +731,11 @@ public class VibrationEffectTest { public void testScaleVendorEffect() { VibrationEffect effect = VibrationEffect.createVendorEffect(createNonEmptyBundle()); - VibrationEffect scaledUp = effect.scale(1.5f); - assertEquals(effect, scaledUp); + VibrationEffect.VendorEffect scaledUp = (VibrationEffect.VendorEffect) effect.scale(1.5f); + assertEquals(1.5f, scaledUp.getScale()); - VibrationEffect scaledDown = effect.scale(0.5f); - assertEquals(effect, scaledDown); + VibrationEffect.VendorEffect scaledDown = (VibrationEffect.VendorEffect) effect.scale(0.5f); + assertEquals(0.5f, scaledDown.getScale()); } @Test @@ -755,6 +755,70 @@ public class VibrationEffectTest { } @Test + public void testApplyAdaptiveScaleOneShot() { + VibrationEffect oneShot = VibrationEffect.createOneShot(TEST_TIMING, /* amplitude= */ 100); + + VibrationEffect scaledUp = oneShot.applyAdaptiveScale(1.5f); + assertThat(getStepSegment(scaledUp, 0).getAmplitude()).isWithin(TOLERANCE).of(150 / 255f); + + VibrationEffect scaledDown = oneShot.applyAdaptiveScale(0.5f); + assertThat(getStepSegment(scaledDown, 0).getAmplitude()).isWithin(TOLERANCE).of(50 / 255f); + } + + @Test + public void testApplyAdaptiveScaleWaveform() { + VibrationEffect waveform = VibrationEffect.createWaveform( + new long[] { 100, 100 }, new int[] { 10, 0 }, -1); + + VibrationEffect scaledUp = waveform.applyAdaptiveScale(1.5f); + assertThat(getStepSegment(scaledUp, 0).getAmplitude()).isWithin(TOLERANCE).of(15 / 255f); + + VibrationEffect scaledDown = waveform.applyAdaptiveScale(0.5f); + assertThat(getStepSegment(scaledDown, 0).getAmplitude()).isWithin(TOLERANCE).of(5 / 255f); + } + + @Test + public void testApplyAdaptiveScalePrebaked() { + VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK); + + VibrationEffect scaledUp = effect.applyAdaptiveScale(1.5f); + assertEquals(effect, scaledUp); + + VibrationEffect scaledDown = effect.applyAdaptiveScale(0.5f); + assertEquals(effect, scaledDown); + } + + @Test + @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS) + public void testApplyAdaptiveScaleVendorEffect() { + VibrationEffect effect = VibrationEffect.createVendorEffect(createNonEmptyBundle()); + + VibrationEffect.VendorEffect scaledUp = + (VibrationEffect.VendorEffect) effect.applyAdaptiveScale(1.5f); + assertEquals(1.5f, scaledUp.getAdaptiveScale()); + + VibrationEffect.VendorEffect scaledDown = + (VibrationEffect.VendorEffect) effect.applyAdaptiveScale(0.5f); + assertEquals(0.5f, scaledDown.getAdaptiveScale()); + } + + @Test + public void testApplyAdaptiveScaleComposed() { + VibrationEffect effect = VibrationEffect.startComposition() + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 1) + .addEffect(VibrationEffect.createOneShot(TEST_TIMING, /* amplitude= */ 100)) + .compose(); + + VibrationEffect scaledUp = effect.applyAdaptiveScale(1.5f); + assertThat(getPrimitiveSegment(scaledUp, 0).getScale()).isWithin(TOLERANCE).of(0.75f); + assertThat(getStepSegment(scaledUp, 1).getAmplitude()).isWithin(TOLERANCE).of(150 / 255f); + + VibrationEffect scaledDown = effect.applyAdaptiveScale(0.5f); + assertThat(getPrimitiveSegment(scaledDown, 0).getScale()).isWithin(TOLERANCE).of(0.25f); + assertThat(getStepSegment(scaledDown, 1).getAmplitude()).isWithin(TOLERANCE).of(50 / 255f); + } + + @Test public void testApplyEffectStrengthToOneShotWaveformAndPrimitives() { VibrationEffect oneShot = VibrationEffect.createOneShot(100, 100); VibrationEffect waveform = VibrationEffect.createWaveform(new long[] { 10, 20 }, 0); diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java index fd788167a0d8..889a778556b7 100644 --- a/graphics/java/android/graphics/Typeface.java +++ b/graphics/java/android/graphics/Typeface.java @@ -56,6 +56,7 @@ import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; +import com.android.text.flags.Flags; import dalvik.annotation.optimization.CriticalNative; import dalvik.annotation.optimization.FastNative; @@ -74,6 +75,7 @@ import java.nio.ByteOrder; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Objects; @@ -143,6 +145,23 @@ public class Typeface { private static final LruCache<String, Typeface> sDynamicTypefaceCache = new LruCache<>(16); private static final Object sDynamicCacheLock = new Object(); + private static final LruCache<Long, LruCache<String, Typeface>> sVariableCache = + new LruCache<>(16); + private static final Object sVariableCacheLock = new Object(); + + /** @hide */ + @VisibleForTesting + public static void clearTypefaceCachesForTestingPurpose() { + synchronized (sWeightCacheLock) { + sWeightTypefaceCache.clear(); + } + synchronized (sDynamicCacheLock) { + sDynamicTypefaceCache.evictAll(); + } + synchronized (sVariableCacheLock) { + sVariableCache.evictAll(); + } + } @GuardedBy("SYSTEM_FONT_MAP_LOCK") static Typeface sDefaultTypeface; @@ -195,6 +214,8 @@ public class Typeface { @UnsupportedAppUsage public final long native_instance; + private final Typeface mDerivedFrom; + private final String mSystemFontFamilyName; private final Runnable mCleaner; @@ -274,6 +295,18 @@ public class Typeface { } /** + * Returns the Typeface used for creating this Typeface. + * + * Maybe null if this is not derived from other Typeface. + * TODO(b/357707916): Make this public API. + * @hide + */ + @VisibleForTesting + public final @Nullable Typeface getDerivedFrom() { + return mDerivedFrom; + } + + /** * Returns the system font family name if the typeface was created from a system font family, * otherwise returns null. */ @@ -1021,9 +1054,51 @@ public class Typeface { return typeface; } - /** @hide */ + private static String axesToVarKey(@NonNull List<FontVariationAxis> axes) { + // The given list can be mutated because it is allocated in Paint#setFontVariationSettings. + // Currently, Paint#setFontVariationSettings is the only code path reaches this method. + axes.sort(Comparator.comparingInt(FontVariationAxis::getOpenTypeTagValue)); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < axes.size(); ++i) { + final FontVariationAxis fva = axes.get(i); + sb.append(fva.getTag()); + sb.append(fva.getStyleValue()); + } + return sb.toString(); + } + + /** + * TODO(b/357707916): Make this public API. + * @hide + */ public static Typeface createFromTypefaceWithVariation(@Nullable Typeface family, @NonNull List<FontVariationAxis> axes) { + if (Flags.typefaceCacheForVarSettings()) { + final Typeface target = (family == null) ? Typeface.DEFAULT : family; + final Typeface base = (target.mDerivedFrom == null) ? target : target.mDerivedFrom; + + final String key = axesToVarKey(axes); + + synchronized (sVariableCacheLock) { + LruCache<String, Typeface> innerCache = sVariableCache.get(base.native_instance); + if (innerCache == null) { + // Cache up to 16 var instance per root Typeface + innerCache = new LruCache<>(16); + sVariableCache.put(base.native_instance, innerCache); + } else { + Typeface cached = innerCache.get(key); + if (cached != null) { + return cached; + } + } + Typeface typeface = new Typeface( + nativeCreateFromTypefaceWithVariation(base.native_instance, axes), + base.getSystemFontFamilyName(), base); + innerCache.put(key, typeface); + return typeface; + } + } + final Typeface base = family == null ? Typeface.DEFAULT : family; Typeface typeface = new Typeface( nativeCreateFromTypefaceWithVariation(base.native_instance, axes), @@ -1184,11 +1259,19 @@ public class Typeface { // don't allow clients to call this directly @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private Typeface(long ni) { - this(ni, null); + this(ni, null, null); } + // don't allow clients to call this directly + // This is kept for robolectric. private Typeface(long ni, @Nullable String systemFontFamilyName) { + this(ni, systemFontFamilyName, null); + } + + // don't allow clients to call this directly + private Typeface(long ni, @Nullable String systemFontFamilyName, + @Nullable Typeface derivedFrom) { if (ni == 0) { throw new RuntimeException("native typeface cannot be made"); } @@ -1198,6 +1281,7 @@ public class Typeface { mStyle = nativeGetStyle(ni); mWeight = nativeGetWeight(ni); mSystemFontFamilyName = systemFontFamilyName; + mDerivedFrom = derivedFrom; } /** diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt index ae60d8bc2596..4b97451a0c41 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt @@ -268,7 +268,8 @@ class BubblePositionerTest { ) positioner.update(deviceConfig) val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName) - val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor()) + val bubble = + Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor(), directExecutor()) assertThat(positioner.getExpandedViewHeight(bubble)).isEqualTo(MAX_HEIGHT) } @@ -294,6 +295,7 @@ class BubblePositionerTest { 0 /* taskId */, null /* locus */, true /* isDismissable */, + directExecutor(), directExecutor() ) {} @@ -322,6 +324,7 @@ class BubblePositionerTest { 0 /* taskId */, null /* locus */, true /* isDismissable */, + directExecutor(), directExecutor() ) {} @@ -416,7 +419,8 @@ class BubblePositionerTest { positioner.update(deviceConfig) val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName) - val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor()) + val bubble = + Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor(), directExecutor()) // This bubble will have max height so it'll always be top aligned assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */)) @@ -433,7 +437,8 @@ class BubblePositionerTest { positioner.update(deviceConfig) val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName) - val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor()) + val bubble = + Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor(), directExecutor()) // Always top aligned in phone portrait assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */)) @@ -452,7 +457,8 @@ class BubblePositionerTest { positioner.update(deviceConfig) val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName) - val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor()) + val bubble = + Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor(), directExecutor()) // This bubble will have max height which is always top aligned on small tablets assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */)) @@ -470,7 +476,8 @@ class BubblePositionerTest { positioner.update(deviceConfig) val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName) - val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor()) + val bubble = + Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor(), directExecutor()) // This bubble will have max height which is always top aligned on small tablets assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */)) @@ -489,7 +496,8 @@ class BubblePositionerTest { positioner.update(deviceConfig) val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName) - val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor()) + val bubble = + Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor(), directExecutor()) // This bubble will have max height which is always top aligned on landscape, large tablet assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */)) @@ -507,7 +515,8 @@ class BubblePositionerTest { positioner.update(deviceConfig) val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName) - val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor()) + val bubble = + Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor(), directExecutor()) val manageButtonHeight = context.resources.getDimensionPixelSize(R.dimen.bubble_manage_button_height) diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt index 84f7bb27ca82..faadf1d623c9 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt @@ -102,6 +102,7 @@ class BubbleStackViewTest { BubbleLogger(UiEventLoggerFake()), positioner, BubbleEducationController(context), + shellExecutor, shellExecutor ) bubbleStackViewManager = FakeBubbleStackViewManager() @@ -364,6 +365,7 @@ class BubbleStackViewTest { /* taskId= */ 0, "locus", /* isDismissable= */ true, + directExecutor(), directExecutor() ) {} inflateBubble(bubble) @@ -373,7 +375,8 @@ class BubbleStackViewTest { private fun createAndInflateBubble(): Bubble { val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName) val icon = Icon.createWithResource(context.resources, R.drawable.bubble_ic_overflow_button) - val bubble = Bubble.createAppBubble(intent, UserHandle(1), icon, directExecutor()) + val bubble = + Bubble.createAppBubble(intent, UserHandle(1), icon, directExecutor(), directExecutor()) inflateBubble(bubble) return bubble } diff --git a/libs/WindowManager/Shell/res/drawable/decor_maximize_button_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_maximize_button_dark.xml index ab4e29ac97e5..7b3353462bd6 100644 --- a/libs/WindowManager/Shell/res/drawable/decor_maximize_button_dark.xml +++ b/libs/WindowManager/Shell/res/drawable/decor_maximize_button_dark.xml @@ -17,19 +17,16 @@ <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="32.0dp" android:height="32.0dp" - android:viewportWidth="32.0" - android:viewportHeight="32.0" - android:tint="@color/decor_button_dark_color"> + android:viewportWidth="24.0" + android:viewportHeight="24.0"> <group android:scaleX="0.5" android:scaleY="0.5" - android:translateX="8.0" - android:translateY="8.0" > + android:translateX="6.0" + android:translateY="6.0" > <path - android:fillColor="@android:color/white" - android:pathData="M2.0,4.0l0.0,16.0l28.0,0.0L30.0,4.0L2.0,4.0zM26.0,16.0L6.0,16.0L6.0,8.0l20.0,0.0L26.0,16.0z"/> - <path - android:fillColor="@android:color/white" - android:pathData="M2.0,24.0l28.0,0.0l0.0,4.0l-28.0,0.0z"/> + android:fillColor="@android:color/black" + android:fillType="evenOdd" + android:pathData="M23.0,1.0v22.0H1V1h22zm-3,19H4V4h16v16z"/> </group> </vector> diff --git a/libs/WindowManager/Shell/res/drawable/decor_restore_button_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_restore_button_dark.xml new file mode 100644 index 000000000000..91c8f544c08d --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/decor_restore_button_dark.xml @@ -0,0 +1,31 @@ +<?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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="32.0dp" + android:height="32.0dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <group android:scaleX="0.5" + android:scaleY="0.5" + android:translateX="6.0" + android:translateY="6.0" > + <path + android:fillColor="@android:color/black" + android:fillType="evenOdd" + android:pathData="M23,16H8V1h15v15zm-12,-3V4h9v9h-9zM4,8H1v15h15v-3H4V8z"/> + </group> +</vector> diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index 8627c0b920e5..53ab2d56f6e6 100644 --- a/libs/WindowManager/Shell/res/values/dimen.xml +++ b/libs/WindowManager/Shell/res/values/dimen.xml @@ -539,20 +539,23 @@ <!-- The size of the icon shown in the resize veil. --> <dimen name="desktop_mode_resize_veil_icon_size">96dp</dimen> - <!-- The with of the border around the app task for edge resizing, when + <!-- The width of the border outside the app task eligible for edge resizing, when enable_windowing_edge_drag_resize is enabled. --> - <dimen name="desktop_mode_edge_handle">12dp</dimen> + <dimen name="freeform_edge_handle_outset">10dp</dimen> + + <!-- The size of the border inside the app task eligible for edge resizing, when + enable_windowing_edge_drag_resize is enabled. --> + <dimen name="freeform_edge_handle_inset">2dp</dimen> <!-- The original width of the border around the app task for edge resizing, when enable_windowing_edge_drag_resize is disabled. --> <dimen name="freeform_resize_handle">15dp</dimen> <!-- The size of the corner region for drag resizing with touch, when a larger touch region is - appropriate. Applied when enable_windowing_edge_drag_resize is enabled. --> + appropriate. --> <dimen name="desktop_mode_corner_resize_large">48dp</dimen> - <!-- The original size of the corner region for darg resizing, when - enable_windowing_edge_drag_resize is disabled. --> + <!-- The size of the corner region for drag resizing with a cursor or a stylus. --> <dimen name="freeform_resize_corner">44dp</dimen> <!-- The thickness in dp for all desktop drag transition regions. --> diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java index 282385a16b5f..341ca0eb6bed 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java @@ -24,6 +24,8 @@ import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.window.flags.Flags; +import java.io.PrintWriter; + /** * Constants for desktop mode feature */ @@ -203,4 +205,19 @@ public class DesktopModeStatus { private static boolean isDeviceEligibleForDesktopMode(@NonNull Context context) { return !enforceDeviceRestrictions() || isDesktopModeSupported(context); } + + /** Dumps DesktopModeStatus flags and configs. */ + public static void dump(PrintWriter pw, String prefix, Context context) { + String innerPrefix = prefix + " "; + pw.print(prefix); pw.println(TAG); + pw.print(innerPrefix); pw.print("maxTaskLimit="); pw.println(getMaxTaskLimit(context)); + + pw.print(innerPrefix); pw.print("maxTaskLimit config override="); + pw.println(context.getResources().getInteger( + R.integer.config_maxDesktopWindowingActiveTasks)); + + SystemProperties.Handle maxTaskLimitHandle = SystemProperties.find(MAX_TASK_LIMIT_SYS_PROP); + pw.print(innerPrefix); pw.print("maxTaskLimit sysprop="); + pw.println(maxTaskLimitHandle == null ? "null" : maxTaskLimitHandle.getInt(/* def= */ -1)); + } } 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 5cd2cb7d51d5..021d3c32fd63 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 @@ -54,6 +54,8 @@ import com.android.wm.shell.Flags; import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView; import com.android.wm.shell.bubbles.bar.BubbleBarLayerView; import com.android.wm.shell.common.bubbles.BubbleInfo; +import com.android.wm.shell.shared.annotations.ShellBackgroundThread; +import com.android.wm.shell.shared.annotations.ShellMainThread; import java.io.PrintWriter; import java.util.List; @@ -79,6 +81,7 @@ public class Bubble implements BubbleViewProvider { private final LocusId mLocusId; private final Executor mMainExecutor; + private final Executor mBgExecutor; private long mLastUpdated; private long mLastAccessed; @@ -111,7 +114,10 @@ public class Bubble implements BubbleViewProvider { @Nullable private BubbleTaskView mBubbleTaskView; + @Nullable private BubbleViewInfoTask mInflationTask; + @Nullable + private BubbleViewInfoTaskLegacy mInflationTaskLegacy; private boolean mInflateSynchronously; private boolean mPendingIntentCanceled; private boolean mIsImportantConversation; @@ -203,7 +209,9 @@ public class Bubble implements BubbleViewProvider { @VisibleForTesting(visibility = PRIVATE) public Bubble(@NonNull final String key, @NonNull final ShortcutInfo shortcutInfo, final int desiredHeight, final int desiredHeightResId, @Nullable final String title, - int taskId, @Nullable final String locus, boolean isDismissable, Executor mainExecutor, + int taskId, @Nullable final String locus, boolean isDismissable, + @ShellMainThread Executor mainExecutor, + @ShellBackgroundThread Executor bgExecutor, final Bubbles.BubbleMetadataFlagListener listener) { Objects.requireNonNull(key); Objects.requireNonNull(shortcutInfo); @@ -222,6 +230,7 @@ public class Bubble implements BubbleViewProvider { mTitle = title; mShowBubbleUpdateDot = false; mMainExecutor = mainExecutor; + mBgExecutor = bgExecutor; mTaskId = taskId; mBubbleMetadataFlagListener = listener; mIsAppBubble = false; @@ -233,7 +242,8 @@ public class Bubble implements BubbleViewProvider { @Nullable Icon icon, boolean isAppBubble, String key, - Executor mainExecutor) { + @ShellMainThread Executor mainExecutor, + @ShellBackgroundThread Executor bgExecutor) { mGroupKey = null; mLocusId = null; mFlags = 0; @@ -243,13 +253,15 @@ public class Bubble implements BubbleViewProvider { mKey = key; mShowBubbleUpdateDot = false; mMainExecutor = mainExecutor; + mBgExecutor = bgExecutor; mTaskId = INVALID_TASK_ID; mAppIntent = intent; mDesiredHeight = Integer.MAX_VALUE; mPackageName = intent.getPackage(); } - private Bubble(ShortcutInfo info, Executor mainExecutor) { + private Bubble(ShortcutInfo info, @ShellMainThread Executor mainExecutor, + @ShellBackgroundThread Executor bgExecutor) { mGroupKey = null; mLocusId = null; mFlags = 0; @@ -259,6 +271,7 @@ public class Bubble implements BubbleViewProvider { mKey = getBubbleKeyForShortcut(info); mShowBubbleUpdateDot = false; mMainExecutor = mainExecutor; + mBgExecutor = bgExecutor; mTaskId = INVALID_TASK_ID; mAppIntent = null; mDesiredHeight = Integer.MAX_VALUE; @@ -267,24 +280,21 @@ public class Bubble implements BubbleViewProvider { } /** Creates an app bubble. */ - public static Bubble createAppBubble( - Intent intent, - UserHandle user, - @Nullable Icon icon, - Executor mainExecutor) { + public static Bubble createAppBubble(Intent intent, UserHandle user, @Nullable Icon icon, + @ShellMainThread Executor mainExecutor, @ShellBackgroundThread Executor bgExecutor) { return new Bubble(intent, user, icon, /* isAppBubble= */ true, /* key= */ getAppBubbleKeyForApp(intent.getPackage(), user), - mainExecutor); + mainExecutor, bgExecutor); } /** Creates a shortcut bubble. */ public static Bubble createShortcutBubble( ShortcutInfo info, - Executor mainExecutor) { - return new Bubble(info, mainExecutor); + @ShellMainThread Executor mainExecutor, @ShellBackgroundThread Executor bgExecutor) { + return new Bubble(info, mainExecutor, bgExecutor); } /** @@ -309,7 +319,7 @@ public class Bubble implements BubbleViewProvider { public Bubble(@NonNull final BubbleEntry entry, final Bubbles.BubbleMetadataFlagListener listener, final Bubbles.PendingIntentCanceledListener intentCancelListener, - Executor mainExecutor) { + @ShellMainThread Executor mainExecutor, @ShellBackgroundThread Executor bgExecutor) { mIsAppBubble = false; mKey = entry.getKey(); mGroupKey = entry.getGroupKey(); @@ -324,6 +334,7 @@ public class Bubble implements BubbleViewProvider { }); }; mMainExecutor = mainExecutor; + mBgExecutor = bgExecutor; mTaskId = INVALID_TASK_ID; setEntry(entry); } @@ -557,40 +568,70 @@ public class Bubble implements BubbleViewProvider { @Nullable BubbleBarLayerView layerView, BubbleIconFactory iconFactory, boolean skipInflation) { - if (isBubbleLoading()) { - mInflationTask.cancel(true /* mayInterruptIfRunning */); - } - mInflationTask = new BubbleViewInfoTask(this, - context, - expandedViewManager, - taskViewFactory, - positioner, - stackView, - layerView, - iconFactory, - skipInflation, - callback, - mMainExecutor); - if (mInflateSynchronously) { - mInflationTask.onPostExecute(mInflationTask.doInBackground()); + if (Flags.bubbleViewInfoExecutors()) { + if (mInflationTask != null && mInflationTask.getStatus() != FINISHED) { + mInflationTask.cancel(true /* mayInterruptIfRunning */); + } + // TODO(b/353894869): switch to executors + mInflationTask = new BubbleViewInfoTask(this, + context, + expandedViewManager, + taskViewFactory, + positioner, + stackView, + layerView, + iconFactory, + skipInflation, + callback, + mMainExecutor); + if (mInflateSynchronously) { + mInflationTask.onPostExecute(mInflationTask.doInBackground()); + } else { + mInflationTask.execute(); + } } else { - mInflationTask.execute(); + if (mInflationTaskLegacy != null && mInflationTaskLegacy.getStatus() != FINISHED) { + mInflationTaskLegacy.cancel(true /* mayInterruptIfRunning */); + } + mInflationTaskLegacy = new BubbleViewInfoTaskLegacy(this, + context, + expandedViewManager, + taskViewFactory, + positioner, + stackView, + layerView, + iconFactory, + skipInflation, + bubble -> { + if (callback != null) { + callback.onBubbleViewsReady(bubble); + } + }, + mMainExecutor); + if (mInflateSynchronously) { + mInflationTaskLegacy.onPostExecute(mInflationTaskLegacy.doInBackground()); + } else { + mInflationTaskLegacy.execute(); + } } } - private boolean isBubbleLoading() { - return mInflationTask != null && mInflationTask.getStatus() != FINISHED; - } - boolean isInflated() { return (mIconView != null && mExpandedView != null) || mBubbleBarExpandedView != null; } void stopInflation() { - if (mInflationTask == null) { - return; + if (Flags.bubbleViewInfoExecutors()) { + if (mInflationTask == null) { + return; + } + mInflationTask.cancel(true /* mayInterruptIfRunning */); + } else { + if (mInflationTaskLegacy == null) { + return; + } + mInflationTaskLegacy.cancel(true /* mayInterruptIfRunning */); } - mInflationTask.cancel(true /* mayInterruptIfRunning */); } void setViewInfo(BubbleViewInfoTask.BubbleViewInfo info) { @@ -626,6 +667,42 @@ public class Bubble implements BubbleViewProvider { } /** + * @deprecated {@link BubbleViewInfoTaskLegacy} is deprecated. + */ + @Deprecated + void setViewInfoLegacy(BubbleViewInfoTaskLegacy.BubbleViewInfo info) { + if (!isInflated()) { + mIconView = info.imageView; + mExpandedView = info.expandedView; + mBubbleBarExpandedView = info.bubbleBarExpandedView; + } + + mShortcutInfo = info.shortcutInfo; + mAppName = info.appName; + if (mTitle == null) { + mTitle = mAppName; + } + mFlyoutMessage = info.flyoutMessage; + + mBadgeBitmap = info.badgeBitmap; + mRawBadgeBitmap = info.rawBadgeBitmap; + mBubbleBitmap = info.bubbleBitmap; + + mDotColor = info.dotColor; + mDotPath = info.dotPath; + + if (mExpandedView != null) { + mExpandedView.update(this /* bubble */); + } + if (mBubbleBarExpandedView != null) { + mBubbleBarExpandedView.update(this /* bubble */); + } + if (mIconView != null) { + mIconView.setRenderedBubble(this /* bubble */); + } + } + + /** * Set visibility of bubble in the expanded state. * * <p>Note that this contents visibility doesn't affect visibility at {@link android.view.View}, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index 29520efd70b0..cfe3cfad123f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -1495,7 +1495,7 @@ public class BubbleController implements ConfigurationChangeListener, b.setAppBubbleIntent(intent); } else { // App bubble does not exist, lets add and expand it - b = Bubble.createAppBubble(intent, user, icon, mMainExecutor); + b = Bubble.createAppBubble(intent, user, icon, mMainExecutor, mBackgroundExecutor); } ProtoLog.d(WM_SHELL_BUBBLES, "inflateAndAdd %s", appBubbleKey); b.setShouldAutoExpand(true); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java index 3c6c6fa0d8d5..4ad1802cba7f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java @@ -43,6 +43,8 @@ import com.android.wm.shell.R; import com.android.wm.shell.bubbles.Bubbles.DismissReason; import com.android.wm.shell.common.bubbles.BubbleBarUpdate; import com.android.wm.shell.common.bubbles.RemovedBubble; +import com.android.wm.shell.shared.annotations.ShellBackgroundThread; +import com.android.wm.shell.shared.annotations.ShellMainThread; import java.io.PrintWriter; import java.util.ArrayList; @@ -201,6 +203,7 @@ public class BubbleData { private final BubblePositioner mPositioner; private final BubbleEducationController mEducationController; private final Executor mMainExecutor; + private final Executor mBgExecutor; /** Bubbles that are actively in the stack. */ private final List<Bubble> mBubbles; /** Bubbles that aged out to overflow. */ @@ -246,12 +249,14 @@ public class BubbleData { private HashMap<String, String> mSuppressedGroupKeys = new HashMap<>(); public BubbleData(Context context, BubbleLogger bubbleLogger, BubblePositioner positioner, - BubbleEducationController educationController, Executor mainExecutor) { + BubbleEducationController educationController, @ShellMainThread Executor mainExecutor, + @ShellBackgroundThread Executor bgExecutor) { mContext = context; mLogger = bubbleLogger; mPositioner = positioner; mEducationController = educationController; mMainExecutor = mainExecutor; + mBgExecutor = bgExecutor; mOverflow = new BubbleOverflow(context, positioner); mBubbles = new ArrayList<>(); mOverflowBubbles = new ArrayList<>(); @@ -431,7 +436,8 @@ public class BubbleData { bubbleToReturn = new Bubble(entry, mBubbleMetadataFlagListener, mCancelledListener, - mMainExecutor); + mMainExecutor, + mBgExecutor); } else { // If there's no entry it must be a persisted bubble bubbleToReturn = persistedBubble; @@ -450,7 +456,7 @@ public class BubbleData { String bubbleKey = Bubble.getBubbleKeyForShortcut(info); Bubble bubbleToReturn = findAndRemoveBubbleFromOverflow(bubbleKey); if (bubbleToReturn == null) { - bubbleToReturn = Bubble.createShortcutBubble(info, mMainExecutor); + bubbleToReturn = Bubble.createShortcutBubble(info, mMainExecutor, mBgExecutor); } return bubbleToReturn; } @@ -461,7 +467,7 @@ public class BubbleData { user); Bubble bubbleToReturn = findAndRemoveBubbleFromOverflow(bubbleKey); if (bubbleToReturn == null) { - bubbleToReturn = Bubble.createAppBubble(intent, user, null, mMainExecutor); + bubbleToReturn = Bubble.createAppBubble(intent, user, null, mMainExecutor, mBgExecutor); } return bubbleToReturn; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt index df12999afc9d..818ba45bec42 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt @@ -31,6 +31,9 @@ import com.android.wm.shell.bubbles.storage.BubbleEntity import com.android.wm.shell.bubbles.storage.BubblePersistentRepository import com.android.wm.shell.bubbles.storage.BubbleVolatileRepository import com.android.wm.shell.common.ShellExecutor +import com.android.wm.shell.shared.annotations.ShellBackgroundThread +import com.android.wm.shell.shared.annotations.ShellMainThread +import java.util.concurrent.Executor import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -41,7 +44,8 @@ import kotlinx.coroutines.yield class BubbleDataRepository( private val launcherApps: LauncherApps, - private val mainExecutor: ShellExecutor, + @ShellMainThread private val mainExecutor: ShellExecutor, + @ShellBackgroundThread private val bgExecutor: Executor, private val persistentRepository: BubblePersistentRepository, ) { private val volatileRepository = BubbleVolatileRepository(launcherApps) @@ -259,8 +263,8 @@ class BubbleDataRepository( entity.locus, entity.isDismissable, mainExecutor, - bubbleMetadataFlagListener - ) + bgExecutor, + bubbleMetadataFlagListener) } } mainExecutor.execute { cb(bubbles) } 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 69119cf4338e..03a2efd902f9 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 @@ -54,6 +54,7 @@ import java.util.concurrent.Executor; /** * Simple task to inflate views & load necessary info to display a bubble. */ +// TODO(b/353894869): switch to executors public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask.BubbleViewInfo> { private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleViewInfoTask" : TAG_BUBBLES; 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 new file mode 100644 index 000000000000..5cfebf8f1647 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskLegacy.java @@ -0,0 +1,346 @@ +/* + * 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.bubbles; + +import static com.android.wm.shell.bubbles.BadgedImageView.DEFAULT_PATH_SIZE; +import static com.android.wm.shell.bubbles.BadgedImageView.WHITE_SCRIM_ALPHA; +import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES; +import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ShortcutInfo; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.graphics.Matrix; +import android.graphics.Path; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.Icon; +import android.os.AsyncTask; +import android.util.Log; +import android.util.PathParser; +import android.view.LayoutInflater; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.graphics.ColorUtils; +import com.android.launcher3.icons.BitmapInfo; +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 java.lang.ref.WeakReference; +import java.util.Objects; +import java.util.concurrent.Executor; + +/** + * Simple task to inflate views & load necessary info to display a bubble. + * + * @deprecated Deprecated since this is using an AsyncTask. Use {@link BubbleViewInfoTask} instead. + */ +@Deprecated +// TODO(b/353894869): remove once flag for loading view info with executors rolls out +public class BubbleViewInfoTaskLegacy extends + AsyncTask<Void, Void, BubbleViewInfoTaskLegacy.BubbleViewInfo> { + private static final String TAG = + TAG_WITH_CLASS_NAME ? "BubbleViewInfoTaskLegacy" : TAG_BUBBLES; + + + /** + * Callback to find out when the bubble has been inflated & necessary data loaded. + */ + public interface Callback { + /** + * Called when data has been loaded for the bubble. + */ + void onBubbleViewsReady(Bubble bubble); + } + + private Bubble mBubble; + private WeakReference<Context> mContext; + private WeakReference<BubbleExpandedViewManager> mExpandedViewManager; + private WeakReference<BubbleTaskViewFactory> mTaskViewFactory; + private WeakReference<BubblePositioner> mPositioner; + private WeakReference<BubbleStackView> mStackView; + private WeakReference<BubbleBarLayerView> mLayerView; + private BubbleIconFactory mIconFactory; + private boolean mSkipInflation; + private Callback mCallback; + private Executor mMainExecutor; + + /** + * Creates a task to load information for the provided {@link Bubble}. Once all info + * is loaded, {@link Callback} is notified. + */ + BubbleViewInfoTaskLegacy(Bubble b, + Context context, + BubbleExpandedViewManager expandedViewManager, + BubbleTaskViewFactory taskViewFactory, + BubblePositioner positioner, + @Nullable BubbleStackView stackView, + @Nullable BubbleBarLayerView layerView, + BubbleIconFactory factory, + boolean skipInflation, + Callback c, + Executor mainExecutor) { + mBubble = b; + mContext = new WeakReference<>(context); + mExpandedViewManager = new WeakReference<>(expandedViewManager); + mTaskViewFactory = new WeakReference<>(taskViewFactory); + mPositioner = new WeakReference<>(positioner); + mStackView = new WeakReference<>(stackView); + mLayerView = new WeakReference<>(layerView); + mIconFactory = factory; + mSkipInflation = skipInflation; + mCallback = c; + mMainExecutor = mainExecutor; + } + + @Override + protected BubbleViewInfo doInBackground(Void... voids) { + if (!verifyState()) { + // If we're in an inconsistent state, then switched modes and should just bail now. + return null; + } + if (mLayerView.get() != null) { + return BubbleViewInfo.populateForBubbleBar(mContext.get(), mExpandedViewManager.get(), + mTaskViewFactory.get(), mPositioner.get(), mLayerView.get(), mIconFactory, + mBubble, mSkipInflation); + } else { + return BubbleViewInfo.populate(mContext.get(), mExpandedViewManager.get(), + mTaskViewFactory.get(), mPositioner.get(), mStackView.get(), mIconFactory, + mBubble, mSkipInflation); + } + } + + @Override + protected void onPostExecute(BubbleViewInfo viewInfo) { + if (isCancelled() || viewInfo == null) { + return; + } + + mMainExecutor.execute(() -> { + if (!verifyState()) { + return; + } + mBubble.setViewInfoLegacy(viewInfo); + if (mCallback != null) { + mCallback.onBubbleViewsReady(mBubble); + } + }); + } + + private boolean verifyState() { + if (mExpandedViewManager.get().isShowingAsBubbleBar()) { + return mLayerView.get() != null; + } else { + return mStackView.get() != null; + } + } + + /** + * Info necessary to render a bubble. + */ + @VisibleForTesting + public static class BubbleViewInfo { + // TODO(b/273312602): for foldables it might make sense to populate all of the views + + // Always populated + ShortcutInfo shortcutInfo; + String appName; + Bitmap rawBadgeBitmap; + + // Only populated when showing in taskbar + @Nullable BubbleBarExpandedView bubbleBarExpandedView; + + // These are only populated when not showing in taskbar + @Nullable BadgedImageView imageView; + @Nullable BubbleExpandedView expandedView; + int dotColor; + Path dotPath; + @Nullable Bubble.FlyoutMessage flyoutMessage; + Bitmap bubbleBitmap; + Bitmap badgeBitmap; + + @Nullable + public static BubbleViewInfo populateForBubbleBar(Context c, + BubbleExpandedViewManager expandedViewManager, + BubbleTaskViewFactory taskViewFactory, + BubblePositioner positioner, + BubbleBarLayerView layerView, + BubbleIconFactory iconFactory, + Bubble b, + boolean skipInflation) { + BubbleViewInfo info = new BubbleViewInfo(); + + if (!skipInflation && !b.isInflated()) { + BubbleTaskView bubbleTaskView = b.getOrCreateBubbleTaskView(taskViewFactory); + LayoutInflater inflater = LayoutInflater.from(c); + info.bubbleBarExpandedView = (BubbleBarExpandedView) inflater.inflate( + R.layout.bubble_bar_expanded_view, layerView, false /* attachToRoot */); + info.bubbleBarExpandedView.initialize( + expandedViewManager, positioner, false /* isOverflow */, bubbleTaskView); + } + + if (!populateCommonInfo(info, c, b, iconFactory)) { + // if we failed to update common fields return null + return null; + } + + return info; + } + + @VisibleForTesting + @Nullable + public static BubbleViewInfo populate(Context c, + BubbleExpandedViewManager expandedViewManager, + BubbleTaskViewFactory taskViewFactory, + BubblePositioner positioner, + BubbleStackView stackView, + BubbleIconFactory iconFactory, + Bubble b, + boolean skipInflation) { + BubbleViewInfo info = new BubbleViewInfo(); + + // View inflation: only should do this once per bubble + if (!skipInflation && !b.isInflated()) { + LayoutInflater inflater = LayoutInflater.from(c); + info.imageView = (BadgedImageView) inflater.inflate( + R.layout.bubble_view, stackView, false /* attachToRoot */); + info.imageView.initialize(positioner); + + BubbleTaskView bubbleTaskView = b.getOrCreateBubbleTaskView(taskViewFactory); + info.expandedView = (BubbleExpandedView) inflater.inflate( + R.layout.bubble_expanded_view, stackView, false /* attachToRoot */); + info.expandedView.initialize( + expandedViewManager, stackView, positioner, false /* isOverflow */, + bubbleTaskView); + } + + if (!populateCommonInfo(info, c, b, iconFactory)) { + // if we failed to update common fields return null + return null; + } + + // Flyout + info.flyoutMessage = b.getFlyoutMessage(); + if (info.flyoutMessage != null) { + info.flyoutMessage.senderAvatar = + loadSenderAvatar(c, info.flyoutMessage.senderIcon); + } + return info; + } + } + + /** + * Modifies the given {@code info} object and populates common fields in it. + * + * <p>This method returns {@code true} if the update was successful and {@code false} otherwise. + * Callers should assume that the info object is unusable if the update was unsuccessful. + */ + private static boolean populateCommonInfo( + BubbleViewInfo info, Context c, Bubble b, BubbleIconFactory iconFactory) { + if (b.getShortcutInfo() != null) { + info.shortcutInfo = b.getShortcutInfo(); + } + + // App name & app icon + PackageManager pm = BubbleController.getPackageManagerForUser(c, + b.getUser().getIdentifier()); + ApplicationInfo appInfo; + Drawable badgedIcon; + Drawable appIcon; + try { + appInfo = pm.getApplicationInfo( + b.getPackageName(), + PackageManager.MATCH_UNINSTALLED_PACKAGES + | PackageManager.MATCH_DISABLED_COMPONENTS + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE + | PackageManager.MATCH_DIRECT_BOOT_AWARE); + if (appInfo != null) { + info.appName = String.valueOf(pm.getApplicationLabel(appInfo)); + } + appIcon = pm.getApplicationIcon(b.getPackageName()); + badgedIcon = pm.getUserBadgedIcon(appIcon, b.getUser()); + } catch (PackageManager.NameNotFoundException exception) { + // If we can't find package... don't think we should show the bubble. + Log.w(TAG, "Unable to find package: " + b.getPackageName()); + return false; + } + + Drawable bubbleDrawable = null; + try { + // Badged bubble image + bubbleDrawable = iconFactory.getBubbleDrawable(c, info.shortcutInfo, + b.getIcon()); + } catch (Exception e) { + // If we can't create the icon we'll default to the app icon + Log.w(TAG, "Exception creating icon for the bubble: " + b.getKey()); + } + + if (bubbleDrawable == null) { + // Default to app icon + bubbleDrawable = appIcon; + } + + BitmapInfo badgeBitmapInfo = iconFactory.getBadgeBitmap(badgedIcon, + b.isImportantConversation()); + info.badgeBitmap = badgeBitmapInfo.icon; + // Raw badge bitmap never includes the important conversation ring + info.rawBadgeBitmap = b.isImportantConversation() + ? iconFactory.getBadgeBitmap(badgedIcon, false).icon + : badgeBitmapInfo.icon; + + float[] bubbleBitmapScale = new float[1]; + info.bubbleBitmap = iconFactory.getBubbleBitmap(bubbleDrawable, bubbleBitmapScale); + + // Dot color & placement + Path iconPath = PathParser.createPathFromPathData( + c.getResources().getString(com.android.internal.R.string.config_icon_mask)); + Matrix matrix = new Matrix(); + float scale = bubbleBitmapScale[0]; + float radius = DEFAULT_PATH_SIZE / 2f; + matrix.setScale(scale /* x scale */, scale /* y scale */, radius /* pivot x */, + radius /* pivot y */); + iconPath.transform(matrix); + info.dotPath = iconPath; + info.dotColor = ColorUtils.blendARGB(badgeBitmapInfo.color, + Color.WHITE, WHITE_SCRIM_ALPHA); + return true; + } + + @Nullable + static Drawable loadSenderAvatar(@NonNull final Context context, @Nullable final Icon icon) { + Objects.requireNonNull(context); + if (icon == null) return null; + try { + if (icon.getType() == Icon.TYPE_URI + || icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP) { + context.grantUriPermission(context.getPackageName(), + icon.getUri(), Intent.FLAG_GRANT_READ_URI_PERMISSION); + } + return icon.loadDrawable(context); + } catch (Exception e) { + Log.w(TAG, "loadSenderAvatar failed: " + e.getMessage()); + return null; + } + } +} 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 955361ffac1b..63a25730f1aa 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 @@ -164,8 +164,10 @@ public abstract class WMShellModule { BubbleLogger logger, BubblePositioner positioner, BubbleEducationController educationController, - @ShellMainThread ShellExecutor mainExecutor) { - return new BubbleData(context, logger, positioner, educationController, mainExecutor); + @ShellMainThread ShellExecutor mainExecutor, + @ShellBackgroundThread ShellExecutor bgExecutor) { + return new BubbleData(context, logger, positioner, educationController, mainExecutor, + bgExecutor); } // Note: Handler needed for LauncherApps.register @@ -198,7 +200,7 @@ public abstract class WMShellModule { IWindowManager wmService) { return new BubbleController(context, shellInit, shellCommandHandler, shellController, data, null /* synchronizer */, floatingContentCoordinator, - new BubbleDataRepository(launcherApps, mainExecutor, + new BubbleDataRepository(launcherApps, mainExecutor, bgExecutor, new BubblePersistentRepository(context)), statusBarService, windowManager, windowManagerShellWrapper, userManager, launcherApps, logger, taskStackListener, organizer, positioner, displayController, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java index 06c1e68753e1..51ce2c6707ac 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java @@ -45,6 +45,7 @@ import com.android.wm.shell.pip2.phone.PipScheduler; import com.android.wm.shell.pip2.phone.PipTouchHandler; import com.android.wm.shell.pip2.phone.PipTransition; import com.android.wm.shell.pip2.phone.PipTransitionState; +import com.android.wm.shell.pip2.phone.PipUiStateChangeController; import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; @@ -73,10 +74,11 @@ public abstract class Pip2Module { Optional<PipController> pipController, PipTouchHandler pipTouchHandler, @NonNull PipScheduler pipScheduler, - @NonNull PipTransitionState pipStackListenerController) { + @NonNull PipTransitionState pipStackListenerController, + @NonNull PipUiStateChangeController pipUiStateChangeController) { return new PipTransition(context, shellInit, shellTaskOrganizer, transitions, pipBoundsState, null, pipBoundsAlgorithm, pipScheduler, - pipStackListenerController); + pipStackListenerController, pipUiStateChangeController); } @WMSingleton @@ -181,4 +183,11 @@ public abstract class Pip2Module { static PipTransitionState providePipTransitionState(@ShellMainThread Handler handler) { return new PipTransitionState(handler); } + + @WMSingleton + @Provides + static PipUiStateChangeController providePipUiStateChangeController( + PipTransitionState pipTransitionState) { + return new PipUiStateChangeController(pipTransitionState); + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt index 05c9d02a0de7..b6f2a25ff1c0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt @@ -16,6 +16,7 @@ package com.android.wm.shell.desktopmode +import com.android.internal.annotations.VisibleForTesting import com.android.internal.protolog.ProtoLog import com.android.internal.util.FrameworkStatsLog import com.android.wm.shell.protolog.ShellProtoLogGroup @@ -128,7 +129,10 @@ class DesktopModeEventLogger { /* task_y */ taskUpdate.taskY, /* session_id */ - sessionId + sessionId, + taskUpdate.minimizeReason?.reason ?: UNSET_MINIMIZE_REASON, + taskUpdate.unminimizeReason?.reason ?: UNSET_UNMINIMIZE_REASON, + ) } @@ -142,6 +146,8 @@ class DesktopModeEventLogger { * @property taskWidth width of the task in px * @property taskX x-coordinate of the top-left corner * @property taskY y-coordinate of the top-left corner + * @property minimizeReason the reason the task was minimized + * @property unminimizeEvent the reason the task was unminimized * */ data class TaskUpdate( @@ -151,8 +157,52 @@ class DesktopModeEventLogger { val taskWidth: Int, val taskX: Int, val taskY: Int, + val minimizeReason: MinimizeReason? = null, + val unminimizeReason: UnminimizeReason? = null, ) + // Default value used when the task was not minimized. + @VisibleForTesting + const val UNSET_MINIMIZE_REASON = + FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__MINIMIZE_REASON__UNSET_MINIMIZE + + /** The reason a task was minimized. */ + enum class MinimizeReason (val reason: Int) { + TASK_LIMIT( + FrameworkStatsLog + .DESKTOP_MODE_SESSION_TASK_UPDATE__MINIMIZE_REASON__MINIMIZE_TASK_LIMIT + ), + MINIMIZE_BUTTON( // TODO(b/356843241): use this enum value + FrameworkStatsLog + .DESKTOP_MODE_SESSION_TASK_UPDATE__MINIMIZE_REASON__MINIMIZE_BUTTON + ), + } + + // Default value used when the task was not unminimized. + @VisibleForTesting + const val UNSET_UNMINIMIZE_REASON = + FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__UNMINIMIZE_REASON__UNSET_UNMINIMIZE + + /** The reason a task was unminimized. */ + enum class UnminimizeReason (val reason: Int) { + UNKNOWN( + FrameworkStatsLog + .DESKTOP_MODE_SESSION_TASK_UPDATE__UNMINIMIZE_REASON__UNMINIMIZE_UNKNOWN + ), + TASKBAR_TAP( + FrameworkStatsLog + .DESKTOP_MODE_SESSION_TASK_UPDATE__UNMINIMIZE_REASON__UNMINIMIZE_TASKBAR_TAP + ), + ALT_TAB( + FrameworkStatsLog + .DESKTOP_MODE_SESSION_TASK_UPDATE__UNMINIMIZE_REASON__UNMINIMIZE_ALT_TAB + ), + TASK_LAUNCH( + FrameworkStatsLog + .DESKTOP_MODE_SESSION_TASK_UPDATE__UNMINIMIZE_REASON__UNMINIMIZE_TASK_LAUNCH + ), + } + /** * Enum EnterReason mapped to the EnterReason definition in * stats/atoms/desktopmode/desktopmode_extensions_atoms.proto diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index f54b44b29683..544c2dd5c484 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -1045,6 +1045,17 @@ class DesktopTasksController( wct.reorder(task.token, true) return wct } + // If task is already visible, it must have been handled already and added to desktop mode. + // Cascade task only if it's not visible yet. + if (DesktopModeFlags.CASCADING_WINDOWS.isEnabled(context) + && !taskRepository.isVisibleTask(task.taskId)) { + val displayLayout = displayController.getDisplayLayout(task.displayId) + if (displayLayout != null) { + val initialBounds = Rect(task.configuration.windowConfiguration.bounds) + cascadeWindow(task, initialBounds, displayLayout) + wct.setBounds(task.token, initialBounds) + } + } if (useDesktopOverrideDensity()) { wct.setDensityDpi(task.token, DESKTOP_DENSITY_OVERRIDE) } @@ -1097,16 +1108,21 @@ class DesktopTasksController( /** Handle task closing by removing wallpaper activity if it's the last active task */ private fun handleTaskClosing(task: RunningTaskInfo): WindowContainerTransaction? { logV("handleTaskClosing") + if (!isDesktopModeShowing(task.displayId)) + return null + val wct = WindowContainerTransaction() if (taskRepository.isOnlyVisibleNonClosingTask(task.taskId) - && taskRepository.wallpaperActivityToken != null) { + && taskRepository.wallpaperActivityToken != null + ) { // Remove wallpaper activity when the last active task is removed removeWallpaperActivity(wct) } taskRepository.addClosingTask(task.displayId, task.taskId) // If a CLOSE or TO_BACK is triggered on a desktop task, remove the task. if (DesktopModeFlags.BACK_NAVIGATION.isEnabled(context) && - taskRepository.isVisibleTask(task.taskId)) { + taskRepository.isVisibleTask(task.taskId) + ) { wct.removeTask(task.token) } return if (wct.isEmpty) null else wct @@ -1134,18 +1150,9 @@ class DesktopTasksController( } if (DesktopModeFlags.CASCADING_WINDOWS.isEnabled(context)) { - val stableBounds = Rect() - displayLayout.getStableBoundsForDesktopMode(stableBounds) - - val activeTasks = taskRepository - .getActiveNonMinimizedOrderedTasks(taskInfo.displayId) - activeTasks.firstOrNull()?.let { activeTask -> - shellTaskOrganizer.getRunningTaskInfo(activeTask)?.let { - cascadeWindow(context.resources, stableBounds, - it.configuration.windowConfiguration.bounds, initialBounds) - } - } + cascadeWindow(taskInfo, initialBounds, displayLayout) } + if (canChangeTaskPosition(taskInfo)) { wct.setBounds(taskInfo.token, initialBounds) } @@ -1180,6 +1187,19 @@ class DesktopTasksController( } } + private fun cascadeWindow(task: TaskInfo, bounds: Rect, displayLayout: DisplayLayout) { + val stableBounds = Rect() + displayLayout.getStableBoundsForDesktopMode(stableBounds) + + val activeTasks = taskRepository.getActiveNonMinimizedOrderedTasks(task.displayId) + activeTasks.firstOrNull()?.let { activeTask -> + shellTaskOrganizer.getRunningTaskInfo(activeTask)?.let { + cascadeWindow(context.resources, stableBounds, + it.configuration.windowConfiguration.bounds, bounds) + } + } + } + /** * Adds split screen changes to a transaction. Note that bounds are not reset here due to * animation; see {@link onDesktopSplitSelectAnimComplete} @@ -1513,6 +1533,7 @@ class DesktopTasksController( private fun dump(pw: PrintWriter, prefix: String) { val innerPrefix = "$prefix " pw.println("${prefix}DesktopTasksController") + DesktopModeStatus.dump(pw, innerPrefix, context) taskRepository.dump(pw, innerPrefix) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java index 846fa6268fc3..ed18712b283d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java @@ -119,7 +119,8 @@ public class PipTransition extends PipTransitionController implements PipMenuController pipMenuController, PipBoundsAlgorithm pipBoundsAlgorithm, PipScheduler pipScheduler, - PipTransitionState pipTransitionState) { + PipTransitionState pipTransitionState, + PipUiStateChangeController pipUiStateChangeController) { super(shellInit, shellTaskOrganizer, transitions, pipBoundsState, pipMenuController, pipBoundsAlgorithm); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipUiStateChangeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipUiStateChangeController.java new file mode 100644 index 000000000000..224016e6c9d4 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipUiStateChangeController.java @@ -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.wm.shell.pip2.phone; + +import android.app.ActivityTaskManager; +import android.app.Flags; +import android.app.PictureInPictureUiState; +import android.os.Bundle; +import android.os.RemoteException; + +import androidx.annotation.Nullable; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.protolog.ProtoLog; +import com.android.wm.shell.protolog.ShellProtoLogGroup; + +import java.util.function.Consumer; + +/** + * Controller class manages the {@link android.app.PictureInPictureUiState} callbacks sent to app. + */ +public class PipUiStateChangeController implements + PipTransitionState.PipTransitionStateChangedListener { + + private final PipTransitionState mPipTransitionState; + + private Consumer<PictureInPictureUiState> mPictureInPictureUiStateConsumer; + + public PipUiStateChangeController(PipTransitionState pipTransitionState) { + mPipTransitionState = pipTransitionState; + mPipTransitionState.addPipTransitionStateChangedListener(this); + mPictureInPictureUiStateConsumer = pictureInPictureUiState -> { + try { + ActivityTaskManager.getService().onPictureInPictureUiStateChanged( + pictureInPictureUiState); + } catch (RemoteException | IllegalStateException e) { + ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "Failed to send PictureInPictureUiState."); + } + }; + } + + @Override + public void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState, + @PipTransitionState.TransitionState int newState, @Nullable Bundle extra) { + if (newState == PipTransitionState.SWIPING_TO_PIP) { + onIsTransitioningToPipUiStateChange(true /* isTransitioningToPip */); + } else if (newState == PipTransitionState.ENTERING_PIP + && !mPipTransitionState.isInSwipePipToHomeTransition()) { + onIsTransitioningToPipUiStateChange(true /* isTransitioningToPip */); + } else if (newState == PipTransitionState.ENTERED_PIP) { + onIsTransitioningToPipUiStateChange(false /* isTransitioningToPip */); + } + } + + @VisibleForTesting + void setPictureInPictureUiStateConsumer(Consumer<PictureInPictureUiState> consumer) { + mPictureInPictureUiStateConsumer = consumer; + } + + private void onIsTransitioningToPipUiStateChange(boolean isTransitioningToPip) { + if (Flags.enablePipUiStateCallbackOnEntering() + && mPictureInPictureUiStateConsumer != null) { + mPictureInPictureUiStateConsumer.accept(new PictureInPictureUiState.Builder() + .setTransitioningToPip(isTransitioningToPip) + .build()); + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index c850ff87f5ff..001bf26ddd0f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -717,7 +717,11 @@ public class Transitions implements RemoteCallable<Transitions>, Log.e(TAG, "Got duplicate transitionReady for " + transitionToken); // The transition is already somewhere else in the pipeline, so just return here. t.apply(); - existing.mFinishT.merge(finishT); + if (existing.mFinishT != null) { + existing.mFinishT.merge(finishT); + } else { + existing.mFinishT = finishT; + } return; } // This usually means the system is in a bad state and may not recover; however, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java index 401b78df026a..231570f5d90e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java @@ -19,6 +19,7 @@ package com.android.wm.shell.windowdecor; import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getFineResizeCornerSize; import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getLargeResizeCornerSize; import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getResizeEdgeHandleSize; +import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getResizeHandleEdgeInset; import android.annotation.NonNull; import android.annotation.SuppressLint; @@ -260,6 +261,8 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL setupRootView(); } + bindData(mResult.mRootView, taskInfo); + if (!isDragResizeable) { closeDragResizeListener(); return; @@ -286,7 +289,8 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL final Resources res = mResult.mRootView.getResources(); mDragResizeListener.setGeometry(new DragResizeWindowGeometry(0 /* taskCornerRadius */, new Size(mResult.mWidth, mResult.mHeight), getResizeEdgeHandleSize(mContext, res), - getFineResizeCornerSize(res), getLargeResizeCornerSize(res)), touchSlop); + getResizeHandleEdgeInset(res), getFineResizeCornerSize(res), + getLargeResizeCornerSize(res)), touchSlop); } /** @@ -305,6 +309,14 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL maximize.setOnClickListener(mOnCaptionButtonClickListener); } + private void bindData(View rootView, RunningTaskInfo taskInfo) { + final boolean isFullscreen = + taskInfo.getWindowingMode() == WindowConfiguration.WINDOWING_MODE_FULLSCREEN; + rootView.findViewById(R.id.maximize_window) + .setBackgroundResource(isFullscreen ? R.drawable.decor_restore_button_dark + : R.drawable.decor_maximize_button_dark); + } + void setCaptionColor(int captionColor) { if (mResult.mRootView == null) { return; 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 538d0fb9cbf6..b5f5bb931c02 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 @@ -31,6 +31,7 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSIT import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getFineResizeCornerSize; import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getLargeResizeCornerSize; import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getResizeEdgeHandleSize; +import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getResizeHandleEdgeInset; import android.annotation.NonNull; import android.annotation.Nullable; @@ -515,8 +516,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin if (mDragResizeListener.setGeometry( new DragResizeWindowGeometry(mRelayoutParams.mCornerRadius, new Size(mResult.mWidth, mResult.mHeight), - getResizeEdgeHandleSize(mContext, res), getFineResizeCornerSize(res), - getLargeResizeCornerSize(res)), touchSlop) + getResizeEdgeHandleSize(mContext, res), getResizeHandleEdgeInset(res), + getFineResizeCornerSize(res), getLargeResizeCornerSize(res)), touchSlop) || !mTaskInfo.positionInParent.equals(mPositionInParent)) { updateExclusionRegion(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java index 014d61d00b9e..fd7bed7c60de 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java @@ -44,27 +44,32 @@ import java.util.Objects; final class DragResizeWindowGeometry { private final int mTaskCornerRadius; private final Size mTaskSize; - // The size of the handle applied to the edges of the window, for the user to drag resize. - private final int mResizeHandleThickness; + // The size of the handle outside the task window applied to the edges of the window, for the + // user to drag resize. + private final int mResizeHandleEdgeOutset; + // The size of the handle inside the task window applied to the edges of the window, for the + // user to drag resize. + private final int mResizeHandleEdgeInset; // The task corners to permit drag resizing with a course input, such as touch. - private final @NonNull TaskCorners mLargeTaskCorners; // The task corners to permit drag resizing with a fine input, such as stylus or cursor. private final @NonNull TaskCorners mFineTaskCorners; // The bounds for each edge drag region, which can resize the task in one direction. - private final @NonNull TaskEdges mTaskEdges; + final @NonNull TaskEdges mTaskEdges; DragResizeWindowGeometry(int taskCornerRadius, @NonNull Size taskSize, - int resizeHandleThickness, int fineCornerSize, int largeCornerSize) { + int resizeHandleEdgeOutset, int resizeHandleEdgeInset, int fineCornerSize, + int largeCornerSize) { mTaskCornerRadius = taskCornerRadius; mTaskSize = taskSize; - mResizeHandleThickness = resizeHandleThickness; + mResizeHandleEdgeOutset = resizeHandleEdgeOutset; + mResizeHandleEdgeInset = resizeHandleEdgeInset; mLargeTaskCorners = new TaskCorners(mTaskSize, largeCornerSize); mFineTaskCorners = new TaskCorners(mTaskSize, fineCornerSize); // Save touch areas for each edge. - mTaskEdges = new TaskEdges(mTaskSize, mResizeHandleThickness); + mTaskEdges = new TaskEdges(mTaskSize, mResizeHandleEdgeOutset, mResizeHandleEdgeInset); } /** @@ -72,11 +77,18 @@ final class DragResizeWindowGeometry { */ static int getResizeEdgeHandleSize(@NonNull Context context, @NonNull Resources res) { return EDGE_DRAG_RESIZE.isEnabled(context) - ? res.getDimensionPixelSize(R.dimen.desktop_mode_edge_handle) + ? res.getDimensionPixelSize(R.dimen.freeform_edge_handle_outset) : res.getDimensionPixelSize(R.dimen.freeform_resize_handle); } /** + * Returns the resource value to use for the edge resize handle inside the task bounds. + */ + static int getResizeHandleEdgeInset(@NonNull Resources res) { + return res.getDimensionPixelSize(R.dimen.freeform_edge_handle_inset); + } + + /** * Returns the resource value to use for course input, such as touch, that benefits from a large * square on each of the window's corners. */ @@ -95,7 +107,8 @@ final class DragResizeWindowGeometry { /** * Returns the size of the task this geometry is calculated for. */ - @NonNull Size getTaskSize() { + @NonNull + Size getTaskSize() { // Safe to return directly since size is immutable. return mTaskSize; } @@ -217,13 +230,15 @@ final class DragResizeWindowGeometry { ctrlType |= CTRL_TYPE_BOTTOM; } // If the touch is within one of the four corners, check if it is within the bounds of the - // // handle. + // handle. if ((ctrlType & (CTRL_TYPE_LEFT | CTRL_TYPE_RIGHT)) != 0 && (ctrlType & (CTRL_TYPE_TOP | CTRL_TYPE_BOTTOM)) != 0) { return checkDistanceFromCenter(ctrlType, x, y); } - // Otherwise, we should make sure we don't resize tasks inside task bounds. - return (x < 0 || y < 0 || x >= mTaskSize.getWidth() || y >= mTaskSize.getHeight()) + // Allow a small resize handle inside the task bounds defined by the edge inset. + return (x <= mResizeHandleEdgeInset || y <= mResizeHandleEdgeInset + || x >= mTaskSize.getWidth() - mResizeHandleEdgeInset + || y >= mTaskSize.getHeight() - mResizeHandleEdgeInset) ? ctrlType : CTRL_TYPE_UNDEFINED; } @@ -237,7 +252,7 @@ final class DragResizeWindowGeometry { final Point cornerRadiusCenter = calculateCenterForCornerRadius(ctrlType); double distanceFromCenter = Math.hypot(x - cornerRadiusCenter.x, y - cornerRadiusCenter.y); - if (distanceFromCenter < mTaskCornerRadius + mResizeHandleThickness + if (distanceFromCenter < mTaskCornerRadius + mResizeHandleEdgeOutset && distanceFromCenter >= mTaskCornerRadius) { return ctrlType; } @@ -288,7 +303,8 @@ final class DragResizeWindowGeometry { return this.mTaskCornerRadius == other.mTaskCornerRadius && this.mTaskSize.equals(other.mTaskSize) - && this.mResizeHandleThickness == other.mResizeHandleThickness + && this.mResizeHandleEdgeOutset == other.mResizeHandleEdgeOutset + && this.mResizeHandleEdgeInset == other.mResizeHandleEdgeInset && this.mFineTaskCorners.equals(other.mFineTaskCorners) && this.mLargeTaskCorners.equals(other.mLargeTaskCorners) && this.mTaskEdges.equals(other.mTaskEdges); @@ -299,7 +315,8 @@ final class DragResizeWindowGeometry { return Objects.hash( mTaskCornerRadius, mTaskSize, - mResizeHandleThickness, + mResizeHandleEdgeOutset, + mResizeHandleEdgeInset, mFineTaskCorners, mLargeTaskCorners, mTaskEdges); @@ -421,26 +438,27 @@ final class DragResizeWindowGeometry { private final @NonNull Rect mBottomEdgeBounds; private final @NonNull Region mRegion; - private TaskEdges(@NonNull Size taskSize, int resizeHandleThickness) { + private TaskEdges(@NonNull Size taskSize, int resizeHandleThickness, + int resizeHandleEdgeInset) { // Save touch areas for each edge. mTopEdgeBounds = new Rect( -resizeHandleThickness, -resizeHandleThickness, taskSize.getWidth() + resizeHandleThickness, - 0); + resizeHandleThickness); mLeftEdgeBounds = new Rect( -resizeHandleThickness, 0, - 0, + resizeHandleEdgeInset, taskSize.getHeight()); mRightEdgeBounds = new Rect( - taskSize.getWidth(), + taskSize.getWidth() - resizeHandleEdgeInset, 0, taskSize.getWidth() + resizeHandleThickness, taskSize.getHeight()); mBottomEdgeBounds = new Rect( -resizeHandleThickness, - taskSize.getHeight(), + taskSize.getHeight() - resizeHandleEdgeInset, taskSize.getWidth() + resizeHandleThickness, taskSize.getHeight() + resizeHandleThickness); diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/Android.bp b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/Android.bp index 35b2f56bca92..a231e381beda 100644 --- a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/Android.bp +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/Android.bp @@ -15,6 +15,7 @@ // package { + default_team: "trendy_team_multitasking_windowing", // 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" @@ -30,6 +31,46 @@ filegroup { ], } +java_library { + name: "WMShellFlickerTestsSplitScreenBase", + srcs: [ + ":WMShellFlickerTestsSplitScreenBase-src", + ], + static_libs: [ + "WMShellFlickerTestsBase", + "wm-shell-flicker-utils", + "androidx.test.ext.junit", + "flickertestapplib", + "flickerlib", + "flickerlib-helpers", + "flickerlib-trace_processor_shell", + "platform-test-annotations", + "wm-flicker-common-app-helpers", + "wm-flicker-common-assertions", + "launcher-helper-lib", + "launcher-aosp-tapl", + ], +} + +android_test { + name: "WMShellFlickerTestsSplitScreen", + defaults: ["WMShellFlickerTestsDefault"], + manifest: "AndroidManifest.xml", + package_name: "com.android.wm.shell.flicker.splitscreen", + instrumentation_target_package: "com.android.wm.shell.flicker.splitscreen", + test_config_template: "AndroidTestTemplate.xml", + srcs: ["src/**/*.kt"], + exclude_srcs: ["src/**/benchmark/*.kt"], + static_libs: [ + "WMShellFlickerTestsBase", + "WMShellFlickerTestsSplitScreenBase", + ], + data: ["trace_config/*"], +} + +//////////////////////////////////////////////////////////////////////////////// +// Begin cleanup after gcl merges + filegroup { name: "WMShellFlickerTestsSplitScreenGroup1-src", srcs: [ @@ -61,27 +102,6 @@ filegroup { ], } -java_library { - name: "WMShellFlickerTestsSplitScreenBase", - srcs: [ - ":WMShellFlickerTestsSplitScreenBase-src", - ], - static_libs: [ - "WMShellFlickerTestsBase", - "wm-shell-flicker-utils", - "androidx.test.ext.junit", - "flickertestapplib", - "flickerlib", - "flickerlib-helpers", - "flickerlib-trace_processor_shell", - "platform-test-annotations", - "wm-flicker-common-app-helpers", - "wm-flicker-common-assertions", - "launcher-helper-lib", - "launcher-aosp-tapl", - ], -} - android_test { name: "WMShellFlickerTestsSplitScreenGroup1", defaults: ["WMShellFlickerTestsDefault"], @@ -154,3 +174,156 @@ android_test { ], data: ["trace_config/*"], } + +//////////////////////////////////////////////////////////////////////////////// +// End cleanup after gcl merges + +//////////////////////////////////////////////////////////////////////////////// +// Begin breakdowns for FlickerTestsRotation module + +test_module_config { + name: "WMShellFlickerTestsSplitScreen-CatchAll", + base: "WMShellFlickerTestsSplitScreen", + exclude_filters: [ + "com.android.wm.shell.flicker.splitscreen.CopyContentInSplit", + "com.android.wm.shell.flicker.splitscreen.DismissSplitScreenByDivider", + "com.android.wm.shell.flicker.splitscreen.DismissSplitScreenByGoHome", + "com.android.wm.shell.flicker.splitscreen.DragDividerToResize", + "com.android.wm.shell.flicker.splitscreen.EnterSplitScreenByDragFromAllApps", + "com.android.wm.shell.flicker.splitscreen.EnterSplitScreenByDragFromNotification", + "com.android.wm.shell.flicker.splitscreen.EnterSplitScreenByDragFromShortcut", + "com.android.wm.shell.flicker.splitscreen.EnterSplitScreenByDragFromTaskbar", + "com.android.wm.shell.flicker.splitscreen.EnterSplitScreenFromOverview", + "com.android.wm.shell.flicker.splitscreen.MultipleShowImeRequestsInSplitScreen", + "com.android.wm.shell.flicker.splitscreen.SwitchAppByDoubleTapDivider", + "com.android.wm.shell.flicker.splitscreen.SwitchBackToSplitFromAnotherApp", + "com.android.wm.shell.flicker.splitscreen.SwitchBackToSplitFromHome", + "com.android.wm.shell.flicker.splitscreen.SwitchBackToSplitFromRecent", + "com.android.wm.shell.flicker.splitscreen.SwitchBetweenSplitPairs", + "com.android.wm.shell.flicker.splitscreen.SwitchBetweenSplitPairsNoPip", + "com.android.wm.shell.flicker.splitscreen.", + ], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsSplitScreen-CopyContentInSplit", + base: "WMShellFlickerTestsSplitScreen", + include_filters: ["com.android.wm.shell.flicker.splitscreen.CopyContentInSplit"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsSplitScreen-DismissSplitScreenByDivider", + base: "WMShellFlickerTestsSplitScreen", + include_filters: ["com.android.wm.shell.flicker.splitscreen.DismissSplitScreenByDivider"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsSplitScreen-DismissSplitScreenByGoHome", + base: "WMShellFlickerTestsSplitScreen", + include_filters: ["com.android.wm.shell.flicker.splitscreen.DismissSplitScreenByGoHome"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsSplitScreen-DragDividerToResize", + base: "WMShellFlickerTestsSplitScreen", + include_filters: ["com.android.wm.shell.flicker.splitscreen.DragDividerToResize"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsSplitScreen-EnterSplitScreenByDragFromAllApps", + base: "WMShellFlickerTestsSplitScreen", + include_filters: ["com.android.wm.shell.flicker.splitscreen.EnterSplitScreenByDragFromAllApps"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsSplitScreen-EnterSplitScreenByDragFromNotification", + base: "WMShellFlickerTestsSplitScreen", + include_filters: ["com.android.wm.shell.flicker.splitscreen.EnterSplitScreenByDragFromNotification"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsSplitScreen-EnterSplitScreenByDragFromShortcut", + base: "WMShellFlickerTestsSplitScreen", + include_filters: ["com.android.wm.shell.flicker.splitscreen.EnterSplitScreenByDragFromShortcut"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsSplitScreen-EnterSplitScreenByDragFromTaskbar", + base: "WMShellFlickerTestsSplitScreen", + include_filters: ["com.android.wm.shell.flicker.splitscreen.EnterSplitScreenByDragFromTaskbar"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsSplitScreen-EnterSplitScreenFromOverview", + base: "WMShellFlickerTestsSplitScreen", + include_filters: ["com.android.wm.shell.flicker.splitscreen.EnterSplitScreenFromOverview"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsSplitScreen-MultipleShowImeRequestsInSplitScreen", + base: "WMShellFlickerTestsSplitScreen", + include_filters: ["com.android.wm.shell.flicker.splitscreen.MultipleShowImeRequestsInSplitScreen"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsSplitScreen-SwitchAppByDoubleTapDivider", + base: "WMShellFlickerTestsSplitScreen", + include_filters: ["com.android.wm.shell.flicker.splitscreen.SwitchAppByDoubleTapDivider"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsSplitScreen-SwitchBackToSplitFromAnotherApp", + base: "WMShellFlickerTestsSplitScreen", + include_filters: ["com.android.wm.shell.flicker.splitscreen.SwitchBackToSplitFromAnotherApp"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsSplitScreen-SwitchBackToSplitFromHome", + base: "WMShellFlickerTestsSplitScreen", + include_filters: ["com.android.wm.shell.flicker.splitscreen.SwitchBackToSplitFromHome"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsSplitScreen-SwitchBackToSplitFromRecent", + base: "WMShellFlickerTestsSplitScreen", + include_filters: ["com.android.wm.shell.flicker.splitscreen.SwitchBackToSplitFromRecent"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsSplitScreen-SwitchBetweenSplitPairs", + base: "WMShellFlickerTestsSplitScreen", + include_filters: ["com.android.wm.shell.flicker.splitscreen.SwitchBetweenSplitPairs"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsSplitScreen-SwitchBetweenSplitPairsNoPip", + base: "WMShellFlickerTestsSplitScreen", + include_filters: ["com.android.wm.shell.flicker.splitscreen.SwitchBetweenSplitPairsNoPip"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsSplitScreen-UnlockKeyguardToSplitScreen", + base: "WMShellFlickerTestsSplitScreen", + include_filters: ["com.android.wm.shell.flicker.splitscreen.UnlockKeyguardToSplitScreen"], + test_suites: ["device-tests"], +} + +// End breakdowns for FlickerTestsRotation module +//////////////////////////////////////////////////////////////////////////////// diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/Android.bp b/libs/WindowManager/Shell/tests/flicker/appcompat/Android.bp index e151ab2c5878..29a9f1050b25 100644 --- a/libs/WindowManager/Shell/tests/flicker/appcompat/Android.bp +++ b/libs/WindowManager/Shell/tests/flicker/appcompat/Android.bp @@ -15,6 +15,7 @@ // package { + default_team: "trendy_team_app_compat", // 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" @@ -23,6 +24,9 @@ package { default_applicable_licenses: ["frameworks_base_license"], } +//////////////////////////////////////////////////////////////////////////////// +// Begin cleanup after gcl merge + filegroup { name: "WMShellFlickerTestsAppCompat-src", srcs: [ @@ -41,3 +45,80 @@ android_test { static_libs: ["WMShellFlickerTestsBase"], data: ["trace_config/*"], } + +//////////////////////////////////////////////////////////////////////////////// +// End cleanup after gcl merge + +android_test { + name: "WMShellFlickerTestsAppCompat", + defaults: ["WMShellFlickerTestsDefault"], + manifest: "AndroidManifest.xml", + package_name: "com.android.wm.shell.flicker", + instrumentation_target_package: "com.android.wm.shell.flicker", + test_config_template: "AndroidTestTemplate.xml", + srcs: ["src/**/*.kt"], + static_libs: ["WMShellFlickerTestsBase"], + data: ["trace_config/*"], +} + +//////////////////////////////////////////////////////////////////////////////// +// Begin breakdowns for WMShellFlickerTestsAppCompat module + +test_module_config { + name: "WMShellFlickerTestsAppCompat-CatchAll", + base: "WMShellFlickerTestsAppCompat", + exclude_filters: [ + "com.android.wm.shell.flicker.appcompat.OpenAppInSizeCompatModeTest", + "com.android.wm.shell.flicker.appcompat.OpenTransparentActivityTest", + "com.android.wm.shell.flicker.appcompat.QuickSwitchLauncherToLetterboxAppTest", + "com.android.wm.shell.flicker.appcompat.RepositionFixedPortraitAppTest", + "com.android.wm.shell.flicker.appcompat.RestartAppInSizeCompatModeTest", + "com.android.wm.shell.flicker.appcompat.RotateImmersiveAppInFullscreenTest", + ], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsAppCompat-OpenAppInSizeCompatModeTest", + base: "WMShellFlickerTestsAppCompat", + include_filters: ["com.android.wm.shell.flicker.appcompat.OpenAppInSizeCompatModeTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsAppCompat-OpenTransparentActivityTest", + base: "WMShellFlickerTestsAppCompat", + include_filters: ["com.android.wm.shell.flicker.appcompat.OpenTransparentActivityTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsAppCompat-QuickSwitchLauncherToLetterboxAppTest", + base: "WMShellFlickerTestsAppCompat", + include_filters: ["com.android.wm.shell.flicker.appcompat.QuickSwitchLauncherToLetterboxAppTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsAppCompat-RepositionFixedPortraitAppTest", + base: "WMShellFlickerTestsAppCompat", + include_filters: ["com.android.wm.shell.flicker.appcompat.RepositionFixedPortraitAppTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsAppCompat-RestartAppInSizeCompatModeTest", + base: "WMShellFlickerTestsAppCompat", + include_filters: ["com.android.wm.shell.flicker.appcompat.RestartAppInSizeCompatModeTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsAppCompat-RotateImmersiveAppInFullscreenTest", + base: "WMShellFlickerTestsAppCompat", + include_filters: ["com.android.wm.shell.flicker.appcompat.RotateImmersiveAppInFullscreenTest"], + test_suites: ["device-tests"], +} + +// End breakdowns for FlickerTestsRotation module +//////////////////////////////////////////////////////////////////////////////// diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/Android.bp b/libs/WindowManager/Shell/tests/flicker/bubble/Android.bp index f0b4f1faad46..2ff7ab231c28 100644 --- a/libs/WindowManager/Shell/tests/flicker/bubble/Android.bp +++ b/libs/WindowManager/Shell/tests/flicker/bubble/Android.bp @@ -15,6 +15,7 @@ // package { + default_team: "trendy_team_multitasking_windowing", // 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" @@ -34,3 +35,57 @@ android_test { static_libs: ["WMShellFlickerTestsBase"], data: ["trace_config/*"], } + +//////////////////////////////////////////////////////////////////////////////// +// Begin breakdowns for WMShellFlickerTestsBubbles module + +test_module_config { + name: "WMShellFlickerTestsBubbles-CatchAll", + base: "WMShellFlickerTestsBubbles", + exclude_filters: [ + "com.android.wm.shell.flicker.bubble.ChangeActiveActivityFromBubbleTest", + "com.android.wm.shell.flicker.bubble.DragToDismissBubbleScreenTest", + "com.android.wm.shell.flicker.bubble.OpenActivityFromBubbleOnLocksreenTest", + "com.android.wm.shell.flicker.bubble.OpenActivityFromBubbleTest", + "com.android.wm.shell.flicker.bubble.SendBubbleNotificationTest", + ], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsBubbles-ChangeActiveActivityFromBubbleTest", + base: "WMShellFlickerTestsBubbles", + include_filters: ["com.android.wm.shell.flicker.bubble.ChangeActiveActivityFromBubbleTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsBubbles-DragToDismissBubbleScreenTest", + base: "WMShellFlickerTestsBubbles", + include_filters: ["com.android.wm.shell.flicker.bubble.DragToDismissBubbleScreenTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsBubbles-OpenActivityFromBubbleOnLocksreenTest", + base: "WMShellFlickerTestsBubbles", + include_filters: ["com.android.wm.shell.flicker.bubble.OpenActivityFromBubbleOnLocksreenTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsBubbles-OpenActivityFromBubbleTest", + base: "WMShellFlickerTestsBubbles", + include_filters: ["com.android.wm.shell.flicker.bubble.OpenActivityFromBubbleTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsBubbles-SendBubbleNotificationTest", + base: "WMShellFlickerTestsBubbles", + include_filters: ["com.android.wm.shell.flicker.bubble.SendBubbleNotificationTest"], + test_suites: ["device-tests"], +} + +// End breakdowns for WMShellFlickerTestsBubbles module +//////////////////////////////////////////////////////////////////////////////// diff --git a/libs/WindowManager/Shell/tests/flicker/pip/Android.bp b/libs/WindowManager/Shell/tests/flicker/pip/Android.bp index faeb342a44be..4165ed093929 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/Android.bp +++ b/libs/WindowManager/Shell/tests/flicker/pip/Android.bp @@ -15,6 +15,7 @@ // package { + default_team: "trendy_team_multitasking_windowing", // 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" @@ -24,6 +25,14 @@ package { } filegroup { + name: "WMShellFlickerTestsPipApps-src", + srcs: ["src/**/apps/*.kt"], +} + +//////////////////////////////////////////////////////////////////////////////// +// Begin cleanup after gcl merges + +filegroup { name: "WMShellFlickerTestsPip1-src", srcs: [ "src/**/A*.kt", @@ -52,11 +61,6 @@ filegroup { srcs: ["src/**/common/*.kt"], } -filegroup { - name: "WMShellFlickerTestsPipApps-src", - srcs: ["src/**/apps/*.kt"], -} - android_test { name: "WMShellFlickerTestsPip1", defaults: ["WMShellFlickerTestsDefault"], @@ -107,6 +111,21 @@ android_test { data: ["trace_config/*"], } +//////////////////////////////////////////////////////////////////////////////// +// End cleanup after gcl merges + +android_test { + name: "WMShellFlickerTestsPip", + defaults: ["WMShellFlickerTestsDefault"], + manifest: "AndroidManifest.xml", + package_name: "com.android.wm.shell.flicker.pip", + instrumentation_target_package: "com.android.wm.shell.flicker.pip", + test_config_template: "AndroidTestTemplate.xml", + srcs: ["src/**/*.kt"], + static_libs: ["WMShellFlickerTestsBase"], + data: ["trace_config/*"], +} + android_test { name: "WMShellFlickerTestsPipApps", defaults: ["WMShellFlickerTestsDefault"], @@ -146,3 +165,185 @@ csuite_test { test_plan_include: "csuitePlan.xml", test_config_template: "csuiteDefaultTemplate.xml", } + +//////////////////////////////////////////////////////////////////////////////// +// Begin breakdowns for WMShellFlickerTestsPip module + +test_module_config { + name: "WMShellFlickerTestsPip-CatchAll", + base: "WMShellFlickerTestsPip", + exclude_filters: [ + "com.android.wm.shell.flicker.pip.AutoEnterPipOnGoToHomeTest", + "com.android.wm.shell.flicker.pip.AutoEnterPipWithSourceRectHintTest", + "com.android.wm.shell.flicker.pip.ClosePipBySwipingDownTest", + "com.android.wm.shell.flicker.pip.ClosePipWithDismissButtonTest", + "com.android.wm.shell.flicker.pip.EnterPipOnUserLeaveHintTest", + "com.android.wm.shell.flicker.pip.EnterPipViaAppUiButtonTest", + "com.android.wm.shell.flicker.pip.ExitPipToAppViaExpandButtonTest", + "com.android.wm.shell.flicker.pip.ExitPipToAppViaIntentTest", + "com.android.wm.shell.flicker.pip.ExpandPipOnDoubleClickTest", + "com.android.wm.shell.flicker.pip.ExpandPipOnPinchOpenTest", + "com.android.wm.shell.flicker.pip.FromSplitScreenAutoEnterPipOnGoToHomeTest", + "com.android.wm.shell.flicker.pip.FromSplitScreenEnterPipOnUserLeaveHintTest", + "com.android.wm.shell.flicker.pip.MovePipDownOnShelfHeightChange", + "com.android.wm.shell.flicker.pip.MovePipOnImeVisibilityChangeTest", + "com.android.wm.shell.flicker.pip.MovePipUpOnShelfHeightChangeTest", + "com.android.wm.shell.flicker.pip.PipAspectRatioChangeTest", + "com.android.wm.shell.flicker.pip.PipDragTest", + "com.android.wm.shell.flicker.pip.PipDragThenSnapTest", + "com.android.wm.shell.flicker.pip.PipPinchInTest", + "com.android.wm.shell.flicker.pip.SetRequestedOrientationWhilePinned", + "com.android.wm.shell.flicker.pip.ShowPipAndRotateDisplay", + ], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsPip-AutoEnterPipOnGoToHomeTest", + base: "WMShellFlickerTestsPip", + include_filters: ["com.android.wm.shell.flicker.pip.AutoEnterPipOnGoToHomeTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsPip-AutoEnterPipWithSourceRectHintTest", + base: "WMShellFlickerTestsPip", + include_filters: ["com.android.wm.shell.flicker.pip.AutoEnterPipWithSourceRectHintTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsPip-ClosePipBySwipingDownTest", + base: "WMShellFlickerTestsPip", + include_filters: ["com.android.wm.shell.flicker.pip.ClosePipBySwipingDownTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsPip-ClosePipWithDismissButtonTest", + base: "WMShellFlickerTestsPip", + include_filters: ["com.android.wm.shell.flicker.pip.ClosePipWithDismissButtonTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsPip-EnterPipOnUserLeaveHintTest", + base: "WMShellFlickerTestsPip", + include_filters: ["com.android.wm.shell.flicker.pip.EnterPipOnUserLeaveHintTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsPip-EnterPipViaAppUiButtonTest", + base: "WMShellFlickerTestsPip", + include_filters: ["com.android.wm.shell.flicker.pip.EnterPipViaAppUiButtonTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsPip-ExitPipToAppViaExpandButtonTest", + base: "WMShellFlickerTestsPip", + include_filters: ["com.android.wm.shell.flicker.pip.ExitPipToAppViaExpandButtonTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsPip-ExitPipToAppViaIntentTest", + base: "WMShellFlickerTestsPip", + include_filters: ["com.android.wm.shell.flicker.pip.ExitPipToAppViaIntentTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsPip-ExpandPipOnDoubleClickTest", + base: "WMShellFlickerTestsPip", + include_filters: ["com.android.wm.shell.flicker.pip.ExpandPipOnDoubleClickTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsPip-ExpandPipOnPinchOpenTest", + base: "WMShellFlickerTestsPip", + include_filters: ["com.android.wm.shell.flicker.pip.ExpandPipOnPinchOpenTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsPip-FromSplitScreenAutoEnterPipOnGoToHomeTest", + base: "WMShellFlickerTestsPip", + include_filters: ["com.android.wm.shell.flicker.pip.FromSplitScreenAutoEnterPipOnGoToHomeTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsPip-FromSplitScreenEnterPipOnUserLeaveHintTest", + base: "WMShellFlickerTestsPip", + include_filters: ["com.android.wm.shell.flicker.pip.FromSplitScreenEnterPipOnUserLeaveHintTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsPip-MovePipDownOnShelfHeightChange", + base: "WMShellFlickerTestsPip", + include_filters: ["com.android.wm.shell.flicker.pip.MovePipDownOnShelfHeightChange"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsPip-MovePipOnImeVisibilityChangeTest", + base: "WMShellFlickerTestsPip", + include_filters: ["com.android.wm.shell.flicker.pip.MovePipOnImeVisibilityChangeTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsPip-MovePipUpOnShelfHeightChangeTest", + base: "WMShellFlickerTestsPip", + include_filters: ["com.android.wm.shell.flicker.pip.MovePipUpOnShelfHeightChangeTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsPip-PipAspectRatioChangeTest", + base: "WMShellFlickerTestsPip", + include_filters: ["com.android.wm.shell.flicker.pip.PipAspectRatioChangeTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsPip-PipDragTest", + base: "WMShellFlickerTestsPip", + include_filters: ["com.android.wm.shell.flicker.pip.PipDragTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsPip-PipDragThenSnapTest", + base: "WMShellFlickerTestsPip", + include_filters: ["com.android.wm.shell.flicker.pip.PipDragThenSnapTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsPip-PipPinchInTest", + base: "WMShellFlickerTestsPip", + include_filters: ["com.android.wm.shell.flicker.pip.PipPinchInTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsPip-SetRequestedOrientationWhilePinned", + base: "WMShellFlickerTestsPip", + include_filters: ["com.android.wm.shell.flicker.pip.SetRequestedOrientationWhilePinned"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsPip-ShowPipAndRotateDisplay", + base: "WMShellFlickerTestsPip", + include_filters: ["com.android.wm.shell.flicker.pip.ShowPipAndRotateDisplay"], + test_suites: ["device-tests"], +} + +// End breakdowns for WMShellFlickerTestsPip module +//////////////////////////////////////////////////////////////////////////////// diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataRepositoryTest.kt index e35995775f76..9ec62c965a14 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataRepositoryTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataRepositoryTest.kt @@ -124,6 +124,7 @@ class BubbleDataRepositoryTest : ShellTestCase() { private val testHandler = Handler(Looper.getMainLooper()) private val mainExecutor = HandlerExecutor(testHandler) + private val bgExecutor = HandlerExecutor(testHandler) private val launcherApps = mock<LauncherApps>() private val persistedBubbles = SparseArray<List<BubbleEntity>>() @@ -134,7 +135,8 @@ class BubbleDataRepositoryTest : ShellTestCase() { @Before fun setup() { persistentRepository = BubblePersistentRepository(mContext) - dataRepository = spy(BubbleDataRepository(launcherApps, mainExecutor, persistentRepository)) + dataRepository = + spy(BubbleDataRepository(launcherApps, mainExecutor, bgExecutor, persistentRepository)) persistedBubbles.put(0, user0BubbleEntities) persistedBubbles.put(1, user1BubbleEntities) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java index c138a2498d35..859602ec709f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java @@ -117,6 +117,8 @@ public class BubbleDataTest extends ShellTestCase { private BubbleEducationController mEducationController; @Mock private ShellExecutor mMainExecutor; + @Mock + private ShellExecutor mBgExecutor; @Captor private ArgumentCaptor<BubbleData.Update> mUpdateCaptor; @@ -144,47 +146,47 @@ public class BubbleDataTest extends ShellTestCase { when(ranking.isTextChanged()).thenReturn(true); mEntryInterruptive = createBubbleEntry(1, "interruptive", "package.d", ranking); mBubbleInterruptive = new Bubble(mEntryInterruptive, mBubbleMetadataFlagListener, null, - mMainExecutor); + mMainExecutor, mBgExecutor); mEntryDismissed = createBubbleEntry(1, "dismissed", "package.d", null); mBubbleDismissed = new Bubble(mEntryDismissed, mBubbleMetadataFlagListener, null, - mMainExecutor); + mMainExecutor, mBgExecutor); mEntryLocusId = createBubbleEntry(1, "keyLocus", "package.e", null, new LocusId("locusId1")); mBubbleLocusId = new Bubble(mEntryLocusId, mBubbleMetadataFlagListener, null /* pendingIntentCanceledListener */, - mMainExecutor); + mMainExecutor, mBgExecutor); mBubbleA1 = new Bubble(mEntryA1, mBubbleMetadataFlagListener, mPendingIntentCanceledListener, - mMainExecutor); + mMainExecutor, mBgExecutor); mBubbleA2 = new Bubble(mEntryA2, mBubbleMetadataFlagListener, mPendingIntentCanceledListener, - mMainExecutor); + mMainExecutor, mBgExecutor); mBubbleA3 = new Bubble(mEntryA3, mBubbleMetadataFlagListener, mPendingIntentCanceledListener, - mMainExecutor); + mMainExecutor, mBgExecutor); mBubbleB1 = new Bubble(mEntryB1, mBubbleMetadataFlagListener, mPendingIntentCanceledListener, - mMainExecutor); + mMainExecutor, mBgExecutor); mBubbleB2 = new Bubble(mEntryB2, mBubbleMetadataFlagListener, mPendingIntentCanceledListener, - mMainExecutor); + mMainExecutor, mBgExecutor); mBubbleB3 = new Bubble(mEntryB3, mBubbleMetadataFlagListener, mPendingIntentCanceledListener, - mMainExecutor); + mMainExecutor, mBgExecutor); mBubbleC1 = new Bubble(mEntryC1, mBubbleMetadataFlagListener, mPendingIntentCanceledListener, - mMainExecutor); + mMainExecutor, mBgExecutor); Intent appBubbleIntent = new Intent(mContext, BubblesTestActivity.class); appBubbleIntent.setPackage(mContext.getPackageName()); @@ -192,12 +194,12 @@ public class BubbleDataTest extends ShellTestCase { appBubbleIntent, new UserHandle(1), mock(Icon.class), - mMainExecutor); + mMainExecutor, mBgExecutor); mPositioner = new TestableBubblePositioner(mContext, mContext.getSystemService(WindowManager.class)); mBubbleData = new BubbleData(getContext(), mBubbleLogger, mPositioner, mEducationController, - mMainExecutor); + mMainExecutor, mBgExecutor); // Used by BubbleData to set lastAccessedTime when(mTimeSource.currentTimeMillis()).thenReturn(1000L); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java index afec1ee12341..50c4a1828026 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java @@ -61,6 +61,8 @@ public class BubbleTest extends ShellTestCase { private StatusBarNotification mSbn; @Mock private ShellExecutor mMainExecutor; + @Mock + private ShellExecutor mBgExecutor; private BubbleEntry mBubbleEntry; private Bundle mExtras; @@ -85,7 +87,8 @@ public class BubbleTest extends ShellTestCase { when(mNotif.getBubbleMetadata()).thenReturn(metadata); when(mSbn.getKey()).thenReturn("mock"); mBubbleEntry = new BubbleEntry(mSbn, null, true, false, false, false); - mBubble = new Bubble(mBubbleEntry, mBubbleMetadataFlagListener, null, mMainExecutor); + mBubble = new Bubble(mBubbleEntry, mBubbleMetadataFlagListener, null, mMainExecutor, + mBgExecutor); } @Test @@ -176,7 +179,8 @@ public class BubbleTest extends ShellTestCase { @Test public void testBubbleIsConversation_hasNoShortcut() { - Bubble bubble = new Bubble(mBubbleEntry, mBubbleMetadataFlagListener, null, mMainExecutor); + Bubble bubble = new Bubble(mBubbleEntry, mBubbleMetadataFlagListener, null, mMainExecutor, + mBgExecutor); assertThat(bubble.getShortcutInfo()).isNull(); assertThat(bubble.isConversation()).isFalse(); } @@ -199,7 +203,7 @@ public class BubbleTest extends ShellTestCase { Intent intent = new Intent(mContext, BubblesTestActivity.class); intent.setPackage(mContext.getPackageName()); Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1 /* userId */), - null /* icon */, mMainExecutor); + null /* icon */, mMainExecutor, mBgExecutor); BubbleInfo bubbleInfo = bubble.asBubbleBarBubble(); assertThat(bubble.getShortcutInfo()).isNull(); @@ -215,6 +219,6 @@ public class BubbleTest extends ShellTestCase { .build(); return new Bubble("mockKey", shortcutInfo, 10, Resources.ID_NULL, "mockTitle", 0 /* taskId */, "mockLocus", true /* isDismissible */, - mMainExecutor, mBubbleMetadataFlagListener); + mMainExecutor, mBgExecutor, mBubbleMetadataFlagListener); } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt index 4a4c5e860bb2..8035e917d5b4 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt @@ -70,6 +70,7 @@ class BubbleViewInfoTest : ShellTestCase() { private lateinit var bubble: Bubble private lateinit var bubbleController: BubbleController private lateinit var mainExecutor: ShellExecutor + private lateinit var bgExecutor: ShellExecutor private lateinit var bubbleStackView: BubbleStackView private lateinit var bubbleBarLayerView: BubbleBarLayerView private lateinit var bubblePositioner: BubblePositioner @@ -92,6 +93,7 @@ class BubbleViewInfoTest : ShellTestCase() { ) mainExecutor = TestShellExecutor() + bgExecutor = TestShellExecutor() val windowManager = context.getSystemService(WindowManager::class.java) val shellInit = ShellInit(mainExecutor) val shellCommandHandler = ShellCommandHandler() @@ -104,7 +106,8 @@ class BubbleViewInfoTest : ShellTestCase() { mock<BubbleLogger>(), bubblePositioner, BubbleEducationController(context), - mainExecutor + mainExecutor, + bgExecutor ) val surfaceSynchronizer = { obj: Runnable -> obj.run() } @@ -132,7 +135,7 @@ class BubbleViewInfoTest : ShellTestCase() { null, mainExecutor, mock<Handler>(), - mock<ShellExecutor>(), + bgExecutor, mock<TaskViewTransitions>(), mock<Transitions>(), mock<SyncTransactionQueue>(), @@ -256,7 +259,7 @@ class BubbleViewInfoTest : ShellTestCase() { "mockLocus", true /* isDismissible */, mainExecutor, - metadataFlagListener - ) + bgExecutor, + metadataFlagListener) } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt index 4548fcb06c55..70b366110692 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt @@ -16,14 +16,17 @@ package com.android.wm.shell.desktopmode -import com.android.dx.mockito.inline.extended.ExtendedMockito +import com.android.dx.mockito.inline.extended.ExtendedMockito.verify import com.android.internal.util.FrameworkStatsLog import com.android.modules.utils.testing.ExtendedMockitoRule import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.TaskUpdate +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UnminimizeReason +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UNSET_MINIMIZE_REASON +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UNSET_UNMINIMIZE_REASON import kotlinx.coroutines.runBlocking -import org.junit.Before import org.junit.Rule import org.junit.Test import org.mockito.kotlin.eq @@ -33,7 +36,7 @@ import org.mockito.kotlin.eq */ class DesktopModeEventLoggerTest { - private val desktopModeEventLogger = DesktopModeEventLogger() + private val desktopModeEventLogger = DesktopModeEventLogger() @JvmField @Rule @@ -44,7 +47,7 @@ class DesktopModeEventLoggerTest { fun logSessionEnter_enterReason() = runBlocking { desktopModeEventLogger.logSessionEnter(sessionId = SESSION_ID, EnterReason.UNKNOWN_ENTER) - ExtendedMockito.verify { + verify { FrameworkStatsLog.write( eq(FrameworkStatsLog.DESKTOP_MODE_UI_CHANGED), /* event */ @@ -63,7 +66,7 @@ class DesktopModeEventLoggerTest { fun logSessionExit_exitReason() = runBlocking { desktopModeEventLogger.logSessionExit(sessionId = SESSION_ID, ExitReason.UNKNOWN_EXIT) - ExtendedMockito.verify { + verify { FrameworkStatsLog.write( eq(FrameworkStatsLog.DESKTOP_MODE_UI_CHANGED), /* event */ @@ -82,7 +85,7 @@ class DesktopModeEventLoggerTest { fun logTaskAdded_taskUpdate() = runBlocking { desktopModeEventLogger.logTaskAdded(sessionId = SESSION_ID, TASK_UPDATE) - ExtendedMockito.verify { + verify { FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE), /* task_event */ eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_ADDED), @@ -99,7 +102,9 @@ class DesktopModeEventLoggerTest { /* task_y */ eq(TASK_UPDATE.taskY), /* session_id */ - eq(SESSION_ID)) + eq(SESSION_ID), + eq(UNSET_MINIMIZE_REASON), + eq(UNSET_UNMINIMIZE_REASON)) } } @@ -107,7 +112,7 @@ class DesktopModeEventLoggerTest { fun logTaskRemoved_taskUpdate() = runBlocking { desktopModeEventLogger.logTaskRemoved(sessionId = SESSION_ID, TASK_UPDATE) - ExtendedMockito.verify { + verify { FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE), /* task_event */ eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_REMOVED), @@ -124,7 +129,9 @@ class DesktopModeEventLoggerTest { /* task_y */ eq(TASK_UPDATE.taskY), /* session_id */ - eq(SESSION_ID)) + eq(SESSION_ID), + eq(UNSET_MINIMIZE_REASON), + eq(UNSET_UNMINIMIZE_REASON)) } } @@ -132,10 +139,11 @@ class DesktopModeEventLoggerTest { fun logTaskInfoChanged_taskUpdate() = runBlocking { desktopModeEventLogger.logTaskInfoChanged(sessionId = SESSION_ID, TASK_UPDATE) - ExtendedMockito.verify { + verify { FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE), /* task_event */ - eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED), + eq(FrameworkStatsLog + .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED), /* instance_id */ eq(TASK_UPDATE.instanceId), /* uid */ @@ -149,7 +157,71 @@ class DesktopModeEventLoggerTest { /* task_y */ eq(TASK_UPDATE.taskY), /* session_id */ - eq(SESSION_ID)) + eq(SESSION_ID), + eq(UNSET_MINIMIZE_REASON), + eq(UNSET_UNMINIMIZE_REASON)) + } + } + + @Test + fun logTaskInfoChanged_logsTaskUpdateWithMinimizeReason() = runBlocking { + desktopModeEventLogger.logTaskInfoChanged(sessionId = SESSION_ID, + createTaskUpdate(minimizeReason = MinimizeReason.TASK_LIMIT)) + + verify { + FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE), + /* task_event */ + eq(FrameworkStatsLog + .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED), + /* instance_id */ + eq(TASK_UPDATE.instanceId), + /* uid */ + eq(TASK_UPDATE.uid), + /* task_height */ + eq(TASK_UPDATE.taskHeight), + /* task_width */ + eq(TASK_UPDATE.taskWidth), + /* task_x */ + eq(TASK_UPDATE.taskX), + /* task_y */ + eq(TASK_UPDATE.taskY), + /* session_id */ + eq(SESSION_ID), + /* minimize_reason */ + eq(MinimizeReason.TASK_LIMIT.reason), + /* unminimize_reason */ + eq(UNSET_UNMINIMIZE_REASON)) + } + } + + @Test + fun logTaskInfoChanged_logsTaskUpdateWithUnminimizeReason() = runBlocking { + desktopModeEventLogger.logTaskInfoChanged(sessionId = SESSION_ID, + createTaskUpdate(unminimizeReason = UnminimizeReason.TASKBAR_TAP)) + + verify { + FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE), + /* task_event */ + eq(FrameworkStatsLog + .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED), + /* instance_id */ + eq(TASK_UPDATE.instanceId), + /* uid */ + eq(TASK_UPDATE.uid), + /* task_height */ + eq(TASK_UPDATE.taskHeight), + /* task_width */ + eq(TASK_UPDATE.taskWidth), + /* task_x */ + eq(TASK_UPDATE.taskX), + /* task_y */ + eq(TASK_UPDATE.taskY), + /* session_id */ + eq(SESSION_ID), + /* minimize_reason */ + eq(UNSET_MINIMIZE_REASON), + /* unminimize_reason */ + eq(UnminimizeReason.TASKBAR_TAP.reason)) } } @@ -165,5 +237,11 @@ class DesktopModeEventLoggerTest { private val TASK_UPDATE = TaskUpdate( TASK_ID, TASK_UID, TASK_HEIGHT, TASK_WIDTH, TASK_X, TASK_Y ) + + private fun createTaskUpdate( + minimizeReason: MinimizeReason? = null, + unminimizeReason: UnminimizeReason? = null, + ) = TaskUpdate(TASK_ID, TASK_UID, TASK_HEIGHT, TASK_WIDTH, TASK_X, TASK_Y, minimizeReason, + unminimizeReason) } }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt index 7bb54498b877..a630cef99474 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -641,6 +641,41 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS) + fun handleRequest_newFreeformTaskLaunch_cascadeApplied() { + assumeTrue(ENABLE_SHELL_TRANSITIONS) + setUpLandscapeDisplay() + val stableBounds = Rect() + displayLayout.getStableBoundsForDesktopMode(stableBounds) + + setUpFreeformTask(bounds = DEFAULT_LANDSCAPE_BOUNDS) + val freeformTask = setUpFreeformTask(bounds = DEFAULT_LANDSCAPE_BOUNDS, active = false) + + val wct = controller.handleRequest(Binder(), createTransition(freeformTask)) + + assertNotNull(wct, "should handle request") + val finalBounds = findBoundsChange(wct, freeformTask) + assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!)) + .isEqualTo(DesktopTaskPosition.BottomRight) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS) + fun handleRequest_freeformTaskAlreadyExistsInDesktopMode_cascadeNotApplied() { + assumeTrue(ENABLE_SHELL_TRANSITIONS) + setUpLandscapeDisplay() + val stableBounds = Rect() + displayLayout.getStableBoundsForDesktopMode(stableBounds) + + setUpFreeformTask(bounds = DEFAULT_LANDSCAPE_BOUNDS) + val freeformTask = setUpFreeformTask(bounds = DEFAULT_LANDSCAPE_BOUNDS) + + val wct = controller.handleRequest(Binder(), createTransition(freeformTask)) + + assertNull(wct, "should not handle request") + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS) fun addMoveToDesktopChanges_positionBottomRight() { setUpLandscapeDisplay() val stableBounds = Rect() @@ -1784,6 +1819,19 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + @EnableFlags( + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, + ) + fun handleRequest_backTransition_singleTaskNoToken_withWallpaper_notInDesktop_doesNotHandle() { + val task = setUpFreeformTask() + markTaskHidden(task) + + val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK)) + + assertNull(result, "Should not handle request") + } + + @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) fun handleRequest_backTransition_singleTaskNoToken_noBackNav_doesNotHandle() { @@ -2816,14 +2864,17 @@ class DesktopTasksControllerTest : ShellTestCase() { private fun setUpFreeformTask( displayId: Int = DEFAULT_DISPLAY, - bounds: Rect? = null + bounds: Rect? = null, + active: Boolean = true ): RunningTaskInfo { val task = createFreeformTask(displayId, bounds) val activityInfo = ActivityInfo() task.topActivityInfo = activityInfo whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) - taskRepository.addActiveTask(displayId, task.taskId) - taskRepository.updateTaskVisibility(displayId, task.taskId, visible = true) + if (active) { + taskRepository.addActiveTask(displayId, task.taskId) + taskRepository.updateTaskVisibility(displayId, task.taskId, visible = true) + } taskRepository.addOrMoveFreeformTaskToTop(displayId, task.taskId) runningTasks.add(task) return task diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/PipTransitionStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTransitionStateTest.java index f3f3c37b645d..571ae93e1aec 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/PipTransitionStateTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTransitionStateTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.pip2; +package com.android.wm.shell.pip2.phone; import android.os.Bundle; import android.os.Handler; @@ -22,8 +22,6 @@ import android.os.Parcelable; import android.testing.AndroidTestingRunner; import com.android.wm.shell.ShellTestCase; -import com.android.wm.shell.common.pip.PhoneSizeSpecSource; -import com.android.wm.shell.pip2.phone.PipTransitionState; import junit.framework.Assert; @@ -33,7 +31,7 @@ import org.junit.runner.RunWith; import org.mockito.Mock; /** - * Unit test against {@link PhoneSizeSpecSource}. + * Unit test against {@link PipTransitionState}. * * This test mocks the PiP2 flag to be true. */ diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipUiStateChangeControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipUiStateChangeControllerTests.java new file mode 100644 index 000000000000..82cdfd52d2db --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipUiStateChangeControllerTests.java @@ -0,0 +1,123 @@ +/* + * 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.pip2.phone; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; + +import android.app.Flags; +import android.app.PictureInPictureUiState; +import android.os.Bundle; +import android.platform.test.annotations.EnableFlags; +import android.testing.AndroidTestingRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.function.Consumer; + +/** + * Unit test against {@link PipUiStateChangeController}. + */ +@RunWith(AndroidTestingRunner.class) +public class PipUiStateChangeControllerTests { + + @Mock + private PipTransitionState mPipTransitionState; + + private Consumer<PictureInPictureUiState> mPictureInPictureUiStateConsumer; + private ArgumentCaptor<PictureInPictureUiState> mArgumentCaptor; + + private PipUiStateChangeController mPipUiStateChangeController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mPipUiStateChangeController = new PipUiStateChangeController(mPipTransitionState); + mPictureInPictureUiStateConsumer = spy(pictureInPictureUiState -> {}); + mPipUiStateChangeController.setPictureInPictureUiStateConsumer( + mPictureInPictureUiStateConsumer); + mArgumentCaptor = ArgumentCaptor.forClass(PictureInPictureUiState.class); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_PIP_UI_STATE_CALLBACK_ON_ENTERING) + public void onPipTransitionStateChanged_swipePipStart_callbackIsTransitioningToPipTrue() { + when(mPipTransitionState.isInSwipePipToHomeTransition()).thenReturn(true); + + mPipUiStateChangeController.onPipTransitionStateChanged( + PipTransitionState.UNDEFINED, PipTransitionState.SWIPING_TO_PIP, Bundle.EMPTY); + + verify(mPictureInPictureUiStateConsumer).accept(mArgumentCaptor.capture()); + assertTrue(mArgumentCaptor.getValue().isTransitioningToPip()); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_PIP_UI_STATE_CALLBACK_ON_ENTERING) + public void onPipTransitionStateChanged_swipePipOngoing_noCallbackIsTransitioningToPip() { + when(mPipTransitionState.isInSwipePipToHomeTransition()).thenReturn(true); + + mPipUiStateChangeController.onPipTransitionStateChanged( + PipTransitionState.SWIPING_TO_PIP, PipTransitionState.ENTERING_PIP, Bundle.EMPTY); + + verifyZeroInteractions(mPictureInPictureUiStateConsumer); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_PIP_UI_STATE_CALLBACK_ON_ENTERING) + public void onPipTransitionStateChanged_swipePipFinish_callbackIsTransitioningToPipFalse() { + when(mPipTransitionState.isInSwipePipToHomeTransition()).thenReturn(true); + + mPipUiStateChangeController.onPipTransitionStateChanged( + PipTransitionState.SWIPING_TO_PIP, PipTransitionState.ENTERED_PIP, Bundle.EMPTY); + + verify(mPictureInPictureUiStateConsumer).accept(mArgumentCaptor.capture()); + assertFalse(mArgumentCaptor.getValue().isTransitioningToPip()); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_PIP_UI_STATE_CALLBACK_ON_ENTERING) + public void onPipTransitionStateChanged_tapHomeStart_callbackIsTransitioningToPipTrue() { + when(mPipTransitionState.isInSwipePipToHomeTransition()).thenReturn(false); + + mPipUiStateChangeController.onPipTransitionStateChanged( + PipTransitionState.UNDEFINED, PipTransitionState.ENTERING_PIP, Bundle.EMPTY); + + verify(mPictureInPictureUiStateConsumer).accept(mArgumentCaptor.capture()); + assertTrue(mArgumentCaptor.getValue().isTransitioningToPip()); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_PIP_UI_STATE_CALLBACK_ON_ENTERING) + public void onPipTransitionStateChanged_tapHomeFinish_callbackIsTransitioningToPipFalse() { + when(mPipTransitionState.isInSwipePipToHomeTransition()).thenReturn(false); + + mPipUiStateChangeController.onPipTransitionStateChanged( + PipTransitionState.ENTERING_PIP, PipTransitionState.ENTERED_PIP, Bundle.EMPTY); + + verify(mPictureInPictureUiStateConsumer).accept(mArgumentCaptor.capture()); + assertFalse(mArgumentCaptor.getValue().isTransitioningToPip()); + } +} 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 fa905e2e5c37..a734689abe35 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 @@ -284,11 +284,8 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { @Test @DisableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR) fun testCreateAndDisposeEventReceiver() { - val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM) - setUpMockDecorationForTask(task) - - onTaskOpening(task) - desktopModeWindowDecorViewModel.destroyWindowDecoration(task) + val decor = createOpenTaskDecoration(windowingMode = WINDOWING_MODE_FREEFORM) + desktopModeWindowDecorViewModel.destroyWindowDecoration(decor.mTaskInfo) verify(mockInputMonitorFactory).create(any(), any()) verify(mockInputMonitor).dispose() @@ -357,16 +354,14 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { } @Test - fun testCloseButtonInFreeform() { - val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM) - val windowDecor = setUpMockDecorationForTask(task) - - onTaskOpening(task) - val onClickListenerCaptor = argumentCaptor<View.OnClickListener>() - verify(windowDecor).setCaptionListeners( - onClickListenerCaptor.capture(), any(), any(), any()) + fun testCloseButtonInFreeform_closeWindow() { + val onClickListenerCaptor = forClass(View.OnClickListener::class.java) + as ArgumentCaptor<View.OnClickListener> + val decor = createOpenTaskDecoration( + windowingMode = WINDOWING_MODE_FREEFORM, + onCaptionButtonClickListener = onClickListenerCaptor + ) - val onClickListener = onClickListenerCaptor.firstValue val view = mock(View::class.java) whenever(view.id).thenReturn(R.id.close_window) @@ -374,7 +369,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { desktopModeWindowDecorViewModel .setFreeformTaskTransitionStarter(freeformTaskTransitionStarter) - onClickListener.onClick(view) + onClickListenerCaptor.value.onClick(view) val transactionCaptor = argumentCaptor<WindowContainerTransaction>() verify(freeformTaskTransitionStarter).startRemoveTransition(transactionCaptor.capture()) @@ -383,7 +378,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { assertEquals(1, wct.getHierarchyOps().size) assertEquals(HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_TASK, wct.getHierarchyOps().get(0).getType()) - assertEquals(task.token.asBinder(), wct.getHierarchyOps().get(0).getContainer()) + assertEquals(decor.mTaskInfo.token.asBinder(), wct.getHierarchyOps().get(0).getContainer()) } @Test @@ -458,15 +453,9 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { @Test fun testKeyguardState_notifiesAllDecors() { - val task1 = createTask(windowingMode = WINDOWING_MODE_FREEFORM) - val decoration1 = setUpMockDecorationForTask(task1) - onTaskOpening(task1) - val task2 = createTask(windowingMode = WINDOWING_MODE_FREEFORM) - val decoration2 = setUpMockDecorationForTask(task2) - onTaskOpening(task2) - val task3 = createTask(windowingMode = WINDOWING_MODE_FREEFORM) - val decoration3 = setUpMockDecorationForTask(task3) - onTaskOpening(task3) + val decoration1 = createOpenTaskDecoration(windowingMode = WINDOWING_MODE_FREEFORM) + val decoration2 = createOpenTaskDecoration(windowingMode = WINDOWING_MODE_FREEFORM) + val decoration3 = createOpenTaskDecoration(windowingMode = WINDOWING_MODE_FREEFORM) desktopModeOnKeyguardChangedListener .onKeyguardVisibilityChanged(true /* visible */, true /* occluded */, @@ -1009,6 +998,8 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { forClass(Function0::class.java) as ArgumentCaptor<Function0<Unit>>, onOpenInBrowserClickListener: ArgumentCaptor<Consumer<Uri>> = forClass(Consumer::class.java) as ArgumentCaptor<Consumer<Uri>>, + onCaptionButtonClickListener: ArgumentCaptor<View.OnClickListener> = + forClass(View.OnClickListener::class.java) as ArgumentCaptor<View.OnClickListener> ): DesktopModeWindowDecoration { val decor = setUpMockDecorationForTask(createTask(windowingMode = windowingMode)) onTaskOpening(decor.mTaskInfo) @@ -1019,6 +1010,8 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { verify(decor).setOnToFullscreenClickListener(onToFullscreenClickListenerCaptor.capture()) verify(decor).setOnToSplitScreenClickListener(onToSplitScreenClickListenerCaptor.capture()) verify(decor).setOpenInBrowserClickListener(onOpenInBrowserClickListener.capture()) + verify(decor).setCaptionListeners( + onCaptionButtonClickListener.capture(), any(), any(), any()) return decor } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java index d8f395d76b39..1691f077a030 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java @@ -45,6 +45,9 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.Arrays; +import java.util.List; + /** * Tests for {@link DragResizeWindowGeometry}. * @@ -57,11 +60,12 @@ public class DragResizeWindowGeometryTests extends ShellTestCase { private static final Size TASK_SIZE = new Size(500, 1000); private static final int TASK_CORNER_RADIUS = 10; private static final int EDGE_RESIZE_THICKNESS = 12; + private static final int EDGE_RESIZE_HANDLE_INSET = 4; private static final int FINE_CORNER_SIZE = EDGE_RESIZE_THICKNESS * 2 + 10; private static final int LARGE_CORNER_SIZE = FINE_CORNER_SIZE + 10; private static final DragResizeWindowGeometry GEOMETRY = new DragResizeWindowGeometry( - TASK_CORNER_RADIUS, TASK_SIZE, EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE, - LARGE_CORNER_SIZE); + TASK_CORNER_RADIUS, TASK_SIZE, EDGE_RESIZE_THICKNESS, EDGE_RESIZE_HANDLE_INSET, + FINE_CORNER_SIZE, LARGE_CORNER_SIZE); // Points in the edge resize handle. Note that coordinates start from the top left. private static final Point TOP_EDGE_POINT = new Point(TASK_SIZE.getWidth() / 2, -EDGE_RESIZE_THICKNESS / 2); @@ -71,6 +75,16 @@ public class DragResizeWindowGeometryTests extends ShellTestCase { TASK_SIZE.getWidth() + EDGE_RESIZE_THICKNESS / 2, TASK_SIZE.getHeight() / 2); private static final Point BOTTOM_EDGE_POINT = new Point(TASK_SIZE.getWidth() / 2, TASK_SIZE.getHeight() + EDGE_RESIZE_THICKNESS / 2); + // Points in the inset of the task bounds still within the edge resize handle. + // Note that coordinates start from the top left. + private static final Point TOP_INSET_POINT = new Point(TASK_SIZE.getWidth() / 2, + EDGE_RESIZE_HANDLE_INSET / 2); + private static final Point LEFT_INSET_POINT = new Point(EDGE_RESIZE_HANDLE_INSET / 2, + TASK_SIZE.getHeight() / 2); + private static final Point RIGHT_INSET_POINT = new Point( + TASK_SIZE.getWidth() - EDGE_RESIZE_HANDLE_INSET / 2, TASK_SIZE.getHeight() / 2); + private static final Point BOTTOM_INSET_POINT = new Point(TASK_SIZE.getWidth() / 2, + TASK_SIZE.getHeight() - EDGE_RESIZE_HANDLE_INSET / 2); @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @@ -85,18 +99,23 @@ public class DragResizeWindowGeometryTests extends ShellTestCase { .addEqualityGroup( GEOMETRY, new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE, - EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE, LARGE_CORNER_SIZE)) + EDGE_RESIZE_THICKNESS, EDGE_RESIZE_HANDLE_INSET, FINE_CORNER_SIZE, + LARGE_CORNER_SIZE)) .addEqualityGroup( new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE, - EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE, LARGE_CORNER_SIZE), + EDGE_RESIZE_THICKNESS + 10, EDGE_RESIZE_HANDLE_INSET, + FINE_CORNER_SIZE, LARGE_CORNER_SIZE), new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE, - EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE, LARGE_CORNER_SIZE)) + EDGE_RESIZE_THICKNESS + 10, EDGE_RESIZE_HANDLE_INSET, + FINE_CORNER_SIZE, LARGE_CORNER_SIZE)) .addEqualityGroup( new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE, - EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE, + EDGE_RESIZE_THICKNESS + 10, EDGE_RESIZE_HANDLE_INSET, + FINE_CORNER_SIZE, LARGE_CORNER_SIZE + 5), new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE, - EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE, + EDGE_RESIZE_THICKNESS + 10, EDGE_RESIZE_HANDLE_INSET, + FINE_CORNER_SIZE, LARGE_CORNER_SIZE + 5)) .testEquals(); } @@ -127,7 +146,7 @@ public class DragResizeWindowGeometryTests extends ShellTestCase { assertThat(region.contains(point.x - EDGE_RESIZE_THICKNESS, point.y)).isTrue(); // Vertically along the edge is not contained. assertThat(region.contains(point.x, point.y - EDGE_RESIZE_THICKNESS)).isFalse(); - assertThat(region.contains(point.x, point.y + EDGE_RESIZE_THICKNESS)).isFalse(); + assertThat(region.contains(point.x, point.y + EDGE_RESIZE_THICKNESS + 10)).isFalse(); } private static void verifyVerticalEdge(@NonNull Region region, @NonNull Point point) { @@ -188,18 +207,18 @@ public class DragResizeWindowGeometryTests extends ShellTestCase { } private void validateCtrlTypeForEdges(boolean isTouchscreen, boolean isEdgeResizePermitted) { - assertThat(GEOMETRY.calculateCtrlType(mContext, isTouchscreen, isEdgeResizePermitted, - LEFT_EDGE_POINT.x, LEFT_EDGE_POINT.y)).isEqualTo( - isEdgeResizePermitted ? CTRL_TYPE_LEFT : CTRL_TYPE_UNDEFINED); - assertThat(GEOMETRY.calculateCtrlType(mContext, isTouchscreen, isEdgeResizePermitted, - TOP_EDGE_POINT.x, TOP_EDGE_POINT.y)).isEqualTo( - isEdgeResizePermitted ? CTRL_TYPE_TOP : CTRL_TYPE_UNDEFINED); - assertThat(GEOMETRY.calculateCtrlType(mContext, isTouchscreen, isEdgeResizePermitted, - RIGHT_EDGE_POINT.x, RIGHT_EDGE_POINT.y)).isEqualTo( - isEdgeResizePermitted ? CTRL_TYPE_RIGHT : CTRL_TYPE_UNDEFINED); - assertThat(GEOMETRY.calculateCtrlType(mContext, isTouchscreen, isEdgeResizePermitted, - BOTTOM_EDGE_POINT.x, BOTTOM_EDGE_POINT.y)).isEqualTo( - isEdgeResizePermitted ? CTRL_TYPE_BOTTOM : CTRL_TYPE_UNDEFINED); + List<Point> points = Arrays.asList(LEFT_EDGE_POINT, TOP_EDGE_POINT, RIGHT_EDGE_POINT, + BOTTOM_EDGE_POINT, LEFT_INSET_POINT, TOP_INSET_POINT, RIGHT_INSET_POINT, + BOTTOM_INSET_POINT); + List<Integer> expectedCtrlType = Arrays.asList(CTRL_TYPE_LEFT, CTRL_TYPE_TOP, + CTRL_TYPE_RIGHT, CTRL_TYPE_BOTTOM, CTRL_TYPE_LEFT, CTRL_TYPE_TOP, CTRL_TYPE_RIGHT, + CTRL_TYPE_BOTTOM); + + for (int i = 0; i < points.size(); i++) { + assertThat(GEOMETRY.calculateCtrlType(mContext, isTouchscreen, isEdgeResizePermitted, + points.get(i).x, points.get(i).y)).isEqualTo( + isEdgeResizePermitted ? expectedCtrlType.get(i) : CTRL_TYPE_UNDEFINED); + } } @Test diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig index a1f51687b077..faea6d42b156 100644 --- a/libs/hwui/aconfig/hwui_flags.aconfig +++ b/libs/hwui/aconfig/hwui_flags.aconfig @@ -2,6 +2,13 @@ package: "com.android.graphics.hwui.flags" container: "system" flag { + name: "runtime_color_filters_blenders" + namespace: "core_graphics" + description: "API for AGSL authored runtime color filters and blenders" + bug: "358126864" +} + +flag { name: "clip_shader" is_exported: true namespace: "core_graphics" diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 25c767a88b17..c36eda908d32 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -84,6 +84,7 @@ import android.util.ArrayMap; import android.util.IntArray; import android.util.Log; import android.util.Pair; +import android.util.Slog; import android.view.KeyEvent; import com.android.internal.annotations.GuardedBy; @@ -4283,7 +4284,7 @@ public class AudioManager { final OnAudioFocusChangeListener listener = fri.mRequest.getOnAudioFocusChangeListener(); if (listener != null) { - Log.d(TAG, "dispatching onAudioFocusChange(" + Slog.i(TAG, "dispatching onAudioFocusChange(" + msg.arg1 + ") to " + msg.obj); listener.onAudioFocusChange(msg.arg1); } diff --git a/media/java/android/media/MediaCas.java b/media/java/android/media/MediaCas.java index 2d7db5e6ed94..9896f6404cda 100644 --- a/media/java/android/media/MediaCas.java +++ b/media/java/android/media/MediaCas.java @@ -131,7 +131,7 @@ public final class MediaCas implements AutoCloseable { private int mCasSystemId; private int mUserId; private TunerResourceManager mTunerResourceManager = null; - private final Map<Session, Integer> mSessionMap = new HashMap<>(); + private final Map<Session, Long> mSessionMap = new HashMap<>(); /** * Scrambling modes used to open cas sessions. @@ -1126,10 +1126,10 @@ public final class MediaCas implements AutoCloseable { } } - private int getSessionResourceHandle() throws MediaCasException { + private long getSessionResourceHandle() throws MediaCasException { validateInternalStates(); - int[] sessionResourceHandle = new int[1]; + long[] sessionResourceHandle = new long[1]; sessionResourceHandle[0] = -1; if (mTunerResourceManager != null) { CasSessionRequest casSessionRequest = new CasSessionRequest(); @@ -1144,8 +1144,7 @@ public final class MediaCas implements AutoCloseable { return sessionResourceHandle[0]; } - private void addSessionToResourceMap(Session session, int sessionResourceHandle) { - + private void addSessionToResourceMap(Session session, long sessionResourceHandle) { if (sessionResourceHandle != TunerResourceManager.INVALID_RESOURCE_HANDLE) { synchronized (mSessionMap) { mSessionMap.put(session, sessionResourceHandle); @@ -1178,13 +1177,14 @@ public final class MediaCas implements AutoCloseable { * @throws MediaCasStateException for CAS-specific state exceptions. */ public Session openSession() throws MediaCasException { - int sessionResourceHandle = getSessionResourceHandle(); + long sessionResourceHandle = getSessionResourceHandle(); try { if (mICas != null) { try { byte[] sessionId = mICas.openSessionDefault(); Session session = createFromSessionId(sessionId); + addSessionToResourceMap(session, sessionResourceHandle); Log.d(TAG, "Write Stats Log for succeed to Open Session."); FrameworkStatsLog.write( FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS, @@ -1238,7 +1238,7 @@ public final class MediaCas implements AutoCloseable { @Nullable public Session openSession(@SessionUsage int sessionUsage, @ScramblingMode int scramblingMode) throws MediaCasException { - int sessionResourceHandle = getSessionResourceHandle(); + long sessionResourceHandle = getSessionResourceHandle(); if (mICas != null) { try { diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java index 2c71ee01b3f1..300ae5d9a26b 100644 --- a/media/java/android/media/tv/tuner/Tuner.java +++ b/media/java/android/media/tv/tuner/Tuner.java @@ -293,13 +293,13 @@ public class Tuner implements AutoCloseable { private EventHandler mHandler; @Nullable private FrontendInfo mFrontendInfo; - private Integer mFrontendHandle; + private Long mFrontendHandle; private Tuner mFeOwnerTuner = null; private int mFrontendType = FrontendSettings.TYPE_UNDEFINED; private Integer mDesiredFrontendId = null; private int mUserId; private Lnb mLnb; - private Integer mLnbHandle; + private Long mLnbHandle; @Nullable private OnTuneEventListener mOnTuneEventListener; @Nullable @@ -322,10 +322,10 @@ public class Tuner implements AutoCloseable { private final ReentrantLock mDemuxLock = new ReentrantLock(); private int mRequestedCiCamId; - private Integer mDemuxHandle; - private Integer mFrontendCiCamHandle; + private Long mDemuxHandle; + private Long mFrontendCiCamHandle; private Integer mFrontendCiCamId; - private Map<Integer, WeakReference<Descrambler>> mDescramblers = new HashMap<>(); + private Map<Long, WeakReference<Descrambler>> mDescramblers = new HashMap<>(); private List<WeakReference<Filter>> mFilters = new ArrayList<WeakReference<Filter>>(); private final TunerResourceManager.ResourcesReclaimListener mResourceListener = @@ -947,7 +947,7 @@ public class Tuner implements AutoCloseable { private void releaseDescramblers() { synchronized (mDescramblers) { if (!mDescramblers.isEmpty()) { - for (Map.Entry<Integer, WeakReference<Descrambler>> d : mDescramblers.entrySet()) { + for (Map.Entry<Long, WeakReference<Descrambler>> d : mDescramblers.entrySet()) { Descrambler descrambler = d.getValue().get(); if (descrambler != null) { descrambler.close(); @@ -1008,7 +1008,7 @@ public class Tuner implements AutoCloseable { /** * Native method to open frontend of the given ID. */ - private native Frontend nativeOpenFrontendByHandle(int handle); + private native Frontend nativeOpenFrontendByHandle(long handle); private native int nativeShareFrontend(int id); private native int nativeUnshareFrontend(); private native void nativeRegisterFeCbListener(long nativeContext); @@ -1037,21 +1037,21 @@ public class Tuner implements AutoCloseable { private native int nativeSetMaxNumberOfFrontends(int frontendType, int maxNumber); private native int nativeGetMaxNumberOfFrontends(int frontendType); private native int nativeRemoveOutputPid(int pid); - private native Lnb nativeOpenLnbByHandle(int handle); + private native Lnb nativeOpenLnbByHandle(long handle); private native Lnb nativeOpenLnbByName(String name); private native FrontendStatusReadiness[] nativeGetFrontendStatusReadiness(int[] statusTypes); - private native Descrambler nativeOpenDescramblerByHandle(int handle); - private native int nativeOpenDemuxByhandle(int handle); + private native Descrambler nativeOpenDescramblerByHandle(long handle); + private native int nativeOpenDemuxByhandle(long handle); private native DvrRecorder nativeOpenDvrRecorder(long bufferSize); private native DvrPlayback nativeOpenDvrPlayback(long bufferSize); private native DemuxCapabilities nativeGetDemuxCapabilities(); - private native DemuxInfo nativeGetDemuxInfo(int demuxHandle); + private native DemuxInfo nativeGetDemuxInfo(long demuxHandle); - private native int nativeCloseDemux(int handle); - private native int nativeCloseFrontend(int handle); + private native int nativeCloseDemux(long handle); + private native int nativeCloseFrontend(long handle); private native int nativeClose(); private static native SharedFilter nativeOpenSharedFilter(String token); @@ -1369,7 +1369,7 @@ public class Tuner implements AutoCloseable { } private boolean requestFrontend() { - int[] feHandle = new int[1]; + long[] feHandle = new long[1]; boolean granted = false; try { TunerFrontendRequest request = new TunerFrontendRequest(); @@ -2377,7 +2377,7 @@ public class Tuner implements AutoCloseable { } private boolean requestLnb() { - int[] lnbHandle = new int[1]; + long[] lnbHandle = new long[1]; TunerLnbRequest request = new TunerLnbRequest(); request.clientId = mClientId; boolean granted = mTunerResourceManager.requestLnb(request, lnbHandle); @@ -2660,7 +2660,7 @@ public class Tuner implements AutoCloseable { } private boolean requestDemux() { - int[] demuxHandle = new int[1]; + long[] demuxHandle = new long[1]; TunerDemuxRequest request = new TunerDemuxRequest(); request.clientId = mClientId; request.desiredFilterTypes = mDesiredDemuxInfo.getFilterTypes(); @@ -2673,14 +2673,14 @@ public class Tuner implements AutoCloseable { } private Descrambler requestDescrambler() { - int[] descramblerHandle = new int[1]; + long[] descramblerHandle = new long[1]; TunerDescramblerRequest request = new TunerDescramblerRequest(); request.clientId = mClientId; boolean granted = mTunerResourceManager.requestDescrambler(request, descramblerHandle); if (!granted) { return null; } - int handle = descramblerHandle[0]; + long handle = descramblerHandle[0]; Descrambler descrambler = nativeOpenDescramblerByHandle(handle); if (descrambler != null) { synchronized (mDescramblers) { @@ -2694,7 +2694,7 @@ public class Tuner implements AutoCloseable { } private boolean requestFrontendCiCam(int ciCamId) { - int[] ciCamHandle = new int[1]; + long[] ciCamHandle = new long[1]; TunerCiCamRequest request = new TunerCiCamRequest(); request.clientId = mClientId; request.ciCamId = ciCamId; diff --git a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java index d268aeba8011..bb581ebe1778 100644 --- a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java +++ b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java @@ -66,7 +66,7 @@ public class TunerResourceManager { private static final String TAG = "TunerResourceManager"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - public static final int INVALID_RESOURCE_HANDLE = -1; + public static final long INVALID_RESOURCE_HANDLE = -1; public static final int INVALID_OWNER_ID = -1; /** * Tuner resource type to help generate resource handle @@ -275,7 +275,7 @@ public class TunerResourceManager { * Updates the current TRM of the TunerHAL Frontend information. * * <p><strong>Note:</strong> This update must happen before the first - * {@link #requestFrontend(TunerFrontendRequest, int[])} and + * {@link #requestFrontend(TunerFrontendRequest, long[])} and * {@link #releaseFrontend(int, int)} call. * * @param infos an array of the available {@link TunerFrontendInfo} information. @@ -331,7 +331,7 @@ public class TunerResourceManager { * * @param lnbIds ids of the updating lnbs. */ - public void setLnbInfoList(int[] lnbIds) { + public void setLnbInfoList(long[] lnbIds) { try { mService.setLnbInfoList(lnbIds); } catch (RemoteException e) { @@ -406,8 +406,8 @@ public class TunerResourceManager { * * @return true if there is frontend granted. */ - public boolean requestFrontend(@NonNull TunerFrontendRequest request, - @Nullable int[] frontendHandle) { + public boolean requestFrontend( + @NonNull TunerFrontendRequest request, @Nullable long[] frontendHandle) { boolean result = false; try { result = mService.requestFrontend(request, frontendHandle); @@ -511,7 +511,7 @@ public class TunerResourceManager { * * @return true if there is Demux granted. */ - public boolean requestDemux(@NonNull TunerDemuxRequest request, @NonNull int[] demuxHandle) { + public boolean requestDemux(@NonNull TunerDemuxRequest request, @NonNull long[] demuxHandle) { boolean result = false; try { result = mService.requestDemux(request, demuxHandle); @@ -544,8 +544,8 @@ public class TunerResourceManager { * * @return true if there is Descrambler granted. */ - public boolean requestDescrambler(@NonNull TunerDescramblerRequest request, - @NonNull int[] descramblerHandle) { + public boolean requestDescrambler( + @NonNull TunerDescramblerRequest request, @NonNull long[] descramblerHandle) { boolean result = false; try { result = mService.requestDescrambler(request, descramblerHandle); @@ -577,8 +577,8 @@ public class TunerResourceManager { * * @return true if there is CAS session granted. */ - public boolean requestCasSession(@NonNull CasSessionRequest request, - @NonNull int[] casSessionHandle) { + public boolean requestCasSession( + @NonNull CasSessionRequest request, @NonNull long[] casSessionHandle) { boolean result = false; try { result = mService.requestCasSession(request, casSessionHandle); @@ -610,7 +610,7 @@ public class TunerResourceManager { * * @return true if there is ciCam granted. */ - public boolean requestCiCam(TunerCiCamRequest request, int[] ciCamHandle) { + public boolean requestCiCam(TunerCiCamRequest request, long[] ciCamHandle) { boolean result = false; try { result = mService.requestCiCam(request, ciCamHandle); @@ -635,7 +635,7 @@ public class TunerResourceManager { * <li>If no Lnb system can be granted, the API would return false. * <ul> * - * <p><strong>Note:</strong> {@link #setLnbInfoList(int[])} must be called before this request. + * <p><strong>Note:</strong> {@link #setLnbInfoList(long[])} must be called before this request. * * @param request {@link TunerLnbRequest} information of the current request. * @param lnbHandle a one-element array to return the granted Lnb handle. @@ -643,7 +643,7 @@ public class TunerResourceManager { * * @return true if there is Lnb granted. */ - public boolean requestLnb(@NonNull TunerLnbRequest request, @NonNull int[] lnbHandle) { + public boolean requestLnb(@NonNull TunerLnbRequest request, @NonNull long[] lnbHandle) { boolean result = false; try { result = mService.requestLnb(request, lnbHandle); @@ -664,7 +664,7 @@ public class TunerResourceManager { * @param frontendHandle the handle of the released frontend. * @param clientId the id of the client that is releasing the frontend. */ - public void releaseFrontend(int frontendHandle, int clientId) { + public void releaseFrontend(long frontendHandle, int clientId) { try { mService.releaseFrontend(frontendHandle, clientId); } catch (RemoteException e) { @@ -680,7 +680,7 @@ public class TunerResourceManager { * @param demuxHandle the handle of the released Tuner Demux. * @param clientId the id of the client that is releasing the demux. */ - public void releaseDemux(int demuxHandle, int clientId) { + public void releaseDemux(long demuxHandle, int clientId) { try { mService.releaseDemux(demuxHandle, clientId); } catch (RemoteException e) { @@ -696,7 +696,7 @@ public class TunerResourceManager { * @param descramblerHandle the handle of the released Tuner Descrambler. * @param clientId the id of the client that is releasing the descrambler. */ - public void releaseDescrambler(int descramblerHandle, int clientId) { + public void releaseDescrambler(long descramblerHandle, int clientId) { try { mService.releaseDescrambler(descramblerHandle, clientId); } catch (RemoteException e) { @@ -715,7 +715,7 @@ public class TunerResourceManager { * @param casSessionHandle the handle of the released CAS session. * @param clientId the id of the client that is releasing the cas session. */ - public void releaseCasSession(int casSessionHandle, int clientId) { + public void releaseCasSession(long casSessionHandle, int clientId) { try { mService.releaseCasSession(casSessionHandle, clientId); } catch (RemoteException e) { @@ -734,7 +734,7 @@ public class TunerResourceManager { * @param ciCamHandle the handle of the releasing CiCam. * @param clientId the id of the client that is releasing the CiCam. */ - public void releaseCiCam(int ciCamHandle, int clientId) { + public void releaseCiCam(long ciCamHandle, int clientId) { try { mService.releaseCiCam(ciCamHandle, clientId); } catch (RemoteException e) { @@ -747,12 +747,12 @@ public class TunerResourceManager { * * <p>Client must call this whenever it releases an Lnb. * - * <p><strong>Note:</strong> {@link #setLnbInfoList(int[])} must be called before this release. + * <p><strong>Note:</strong> {@link #setLnbInfoList(long[])} must be called before this release. * * @param lnbHandle the handle of the released Tuner Lnb. * @param clientId the id of the client that is releasing the lnb. */ - public void releaseLnb(int lnbHandle, int clientId) { + public void releaseLnb(long lnbHandle, int clientId) { try { mService.releaseLnb(lnbHandle, clientId); } catch (RemoteException e) { diff --git a/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl b/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl index 539969762a82..109c791c1748 100644 --- a/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl +++ b/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl @@ -149,7 +149,7 @@ interface ITunerResourceManager { * * @param lnbIds ids of the updating lnbs. */ - void setLnbInfoList(in int[] lnbIds); + void setLnbInfoList(in long[] lnbIds); /* * This API is used by the Tuner framework to request a frontend from the TunerHAL. @@ -185,7 +185,7 @@ interface ITunerResourceManager { * * @return true if there is frontend granted. */ - boolean requestFrontend(in TunerFrontendRequest request, out int[] frontendHandle); + boolean requestFrontend(in TunerFrontendRequest request, out long[] frontendHandle); /* * Sets the maximum usable frontends number of a given frontend type. It is used to enable or @@ -253,7 +253,7 @@ interface ITunerResourceManager { * * @return true if there is demux granted. */ - boolean requestDemux(in TunerDemuxRequest request, out int[] demuxHandle); + boolean requestDemux(in TunerDemuxRequest request, out long[] demuxHandle); /* * This API is used by the Tuner framework to request an available descrambler from the @@ -277,7 +277,7 @@ interface ITunerResourceManager { * * @return true if there is Descrambler granted. */ - boolean requestDescrambler(in TunerDescramblerRequest request, out int[] descramblerHandle); + boolean requestDescrambler(in TunerDescramblerRequest request, out long[] descramblerHandle); /* * This API is used by the Tuner framework to request an available Cas session. This session @@ -303,7 +303,7 @@ interface ITunerResourceManager { * * @return true if there is CAS session granted. */ - boolean requestCasSession(in CasSessionRequest request, out int[] casSessionHandle); + boolean requestCasSession(in CasSessionRequest request, out long[] casSessionHandle); /* * This API is used by the Tuner framework to request an available CuCam. @@ -328,7 +328,7 @@ interface ITunerResourceManager { * * @return true if there is CiCam granted. */ - boolean requestCiCam(in TunerCiCamRequest request, out int[] ciCamHandle); + boolean requestCiCam(in TunerCiCamRequest request, out long[] ciCamHandle); /* * This API is used by the Tuner framework to request an available Lnb from the TunerHAL. @@ -352,7 +352,7 @@ interface ITunerResourceManager { * * @return true if there is Lnb granted. */ - boolean requestLnb(in TunerLnbRequest request, out int[] lnbHandle); + boolean requestLnb(in TunerLnbRequest request, out long[] lnbHandle); /* * Notifies the TRM that the given frontend has been released. @@ -365,7 +365,7 @@ interface ITunerResourceManager { * @param frontendHandle the handle of the released frontend. * @param clientId the id of the client that is releasing the frontend. */ - void releaseFrontend(in int frontendHandle, int clientId); + void releaseFrontend(in long frontendHandle, int clientId); /* * Notifies the TRM that the Demux with the given handle was released. @@ -375,7 +375,7 @@ interface ITunerResourceManager { * @param demuxHandle the handle of the released Tuner Demux. * @param clientId the id of the client that is releasing the demux. */ - void releaseDemux(in int demuxHandle, int clientId); + void releaseDemux(in long demuxHandle, int clientId); /* * Notifies the TRM that the Descrambler with the given handle was released. @@ -385,7 +385,7 @@ interface ITunerResourceManager { * @param descramblerHandle the handle of the released Tuner Descrambler. * @param clientId the id of the client that is releasing the descrambler. */ - void releaseDescrambler(in int descramblerHandle, int clientId); + void releaseDescrambler(in long descramblerHandle, int clientId); /* * Notifies the TRM that the given Cas session has been released. @@ -397,7 +397,7 @@ interface ITunerResourceManager { * @param casSessionHandle the handle of the released CAS session. * @param clientId the id of the client that is releasing the cas session. */ - void releaseCasSession(in int casSessionHandle, int clientId); + void releaseCasSession(in long casSessionHandle, int clientId); /** * Notifies the TRM that the given CiCam has been released. @@ -410,7 +410,7 @@ interface ITunerResourceManager { * @param ciCamHandle the handle of the releasing CiCam. * @param clientId the id of the client that is releasing the CiCam. */ - void releaseCiCam(in int ciCamHandle, int clientId); + void releaseCiCam(in long ciCamHandle, int clientId); /* * Notifies the TRM that the Lnb with the given handle was released. @@ -422,7 +422,7 @@ interface ITunerResourceManager { * @param lnbHandle the handle of the released Tuner Lnb. * @param clientId the id of the client that is releasing the lnb. */ - void releaseLnb(in int lnbHandle, int clientId); + void releaseLnb(in long lnbHandle, int clientId); /* * Compare two clients' priority. diff --git a/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/TunerDemuxInfo.aidl b/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/TunerDemuxInfo.aidl index c14caf50fa14..7984c38d33ab 100644 --- a/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/TunerDemuxInfo.aidl +++ b/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/TunerDemuxInfo.aidl @@ -26,7 +26,7 @@ parcelable TunerDemuxInfo { /** * Demux handle */ - int handle; + long handle; /** * Supported filter types (defined in {@link android.media.tv.tuner.filter.Filter}) diff --git a/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/TunerFrontendInfo.aidl b/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/TunerFrontendInfo.aidl index 8981ce00d509..274367e9aeb6 100644 --- a/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/TunerFrontendInfo.aidl +++ b/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/TunerFrontendInfo.aidl @@ -26,7 +26,7 @@ parcelable TunerFrontendInfo { /** * Frontend Handle */ - int handle; + long handle; /** * Frontend Type diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp index 00b0e57c09ea..5eb2485b8dd6 100644 --- a/media/jni/android_media_tv_Tuner.cpp +++ b/media/jni/android_media_tv_Tuner.cpp @@ -1448,7 +1448,7 @@ jobject JTuner::getFrontendIds() { return obj; } -jobject JTuner::openFrontendByHandle(int feHandle) { +jobject JTuner::openFrontendByHandle(jlong feHandle) { // TODO: Handle reopening frontend with different handle sp<FrontendClient> feClient = sTunerClient->openFrontend(feHandle); if (feClient == nullptr) { @@ -1824,7 +1824,7 @@ jobjectArray JTuner::getFrontendStatusReadiness(jintArray types) { return valObj; } -jobject JTuner::openLnbByHandle(int handle) { +jobject JTuner::openLnbByHandle(jlong handle) { if (sTunerClient == nullptr) { return nullptr; } @@ -1833,7 +1833,7 @@ jobject JTuner::openLnbByHandle(int handle) { sp<LnbClientCallbackImpl> callback = new LnbClientCallbackImpl(); lnbClient = sTunerClient->openLnb(handle); if (lnbClient == nullptr) { - ALOGD("Failed to open lnb, handle = %d", handle); + ALOGD("Failed to open lnb, handle = %ld", handle); return nullptr; } @@ -1947,7 +1947,7 @@ int JTuner::setLna(bool enable) { return (int)result; } -Result JTuner::openDemux(int handle) { +Result JTuner::openDemux(jlong handle) { if (sTunerClient == nullptr) { return Result::NOT_INITIALIZED; } @@ -2215,7 +2215,7 @@ jobject JTuner::getDemuxCaps() { numBytesInSectionFilter, filterCaps, filterCapsList, linkCaps, bTimeFilter); } -jobject JTuner::getDemuxInfo(int handle) { +jobject JTuner::getDemuxInfo(jlong handle) { if (sTunerClient == nullptr) { ALOGE("tuner is not initialized"); return nullptr; @@ -3768,8 +3768,8 @@ static jobject android_media_tv_Tuner_get_frontend_ids(JNIEnv *env, jobject thiz return tuner->getFrontendIds(); } -static jobject android_media_tv_Tuner_open_frontend_by_handle( - JNIEnv *env, jobject thiz, jint handle) { +static jobject android_media_tv_Tuner_open_frontend_by_handle(JNIEnv *env, jobject thiz, + jlong handle) { sp<JTuner> tuner = getTuner(env, thiz); return tuner->openFrontendByHandle(handle); } @@ -3901,7 +3901,7 @@ static jobject android_media_tv_Tuner_get_frontend_info(JNIEnv *env, jobject thi return tuner->getFrontendInfo(id); } -static jobject android_media_tv_Tuner_open_lnb_by_handle(JNIEnv *env, jobject thiz, jint handle) { +static jobject android_media_tv_Tuner_open_lnb_by_handle(JNIEnv *env, jobject thiz, jlong handle) { sp<JTuner> tuner = getTuner(env, thiz); return tuner->openLnbByHandle(handle); } @@ -4622,7 +4622,7 @@ static int android_media_tv_Tuner_time_filter_close(JNIEnv *env, jobject filter) return (int)r; } -static jobject android_media_tv_Tuner_open_descrambler(JNIEnv *env, jobject thiz, jint) { +static jobject android_media_tv_Tuner_open_descrambler(JNIEnv *env, jobject thiz, jlong) { sp<JTuner> tuner = getTuner(env, thiz); return tuner->openDescrambler(); } @@ -4690,12 +4690,12 @@ static jobject android_media_tv_Tuner_get_demux_caps(JNIEnv* env, jobject thiz) return tuner->getDemuxCaps(); } -static jobject android_media_tv_Tuner_get_demux_info(JNIEnv* env, jobject thiz, jint handle) { +static jobject android_media_tv_Tuner_get_demux_info(JNIEnv *env, jobject thiz, jlong handle) { sp<JTuner> tuner = getTuner(env, thiz); return tuner->getDemuxInfo(handle); } -static jint android_media_tv_Tuner_open_demux(JNIEnv* env, jobject thiz, jint handle) { +static jint android_media_tv_Tuner_open_demux(JNIEnv *env, jobject thiz, jlong handle) { sp<JTuner> tuner = getTuner(env, thiz); return (jint)tuner->openDemux(handle); } @@ -4706,7 +4706,7 @@ static jint android_media_tv_Tuner_close_tuner(JNIEnv* env, jobject thiz) { return (jint)tuner->close(); } -static jint android_media_tv_Tuner_close_demux(JNIEnv* env, jobject thiz, jint /* handle */) { +static jint android_media_tv_Tuner_close_demux(JNIEnv *env, jobject thiz, jlong /* handle */) { sp<JTuner> tuner = getTuner(env, thiz); return tuner->closeDemux(); } @@ -4766,7 +4766,7 @@ static jobjectArray android_media_tv_Tuner_get_frontend_status_readiness(JNIEnv return tuner->getFrontendStatusReadiness(types); } -static jint android_media_tv_Tuner_close_frontend(JNIEnv* env, jobject thiz, jint /* handle */) { +static jint android_media_tv_Tuner_close_frontend(JNIEnv *env, jobject thiz, jlong /* handle */) { sp<JTuner> tuner = getTuner(env, thiz); return tuner->closeFrontend(); } @@ -5035,7 +5035,7 @@ static const JNINativeMethod gTunerMethods[] = { { "nativeGetTunerVersion", "()I", (void *)android_media_tv_Tuner_native_get_tuner_version }, { "nativeGetFrontendIds", "()Ljava/util/List;", (void *)android_media_tv_Tuner_get_frontend_ids }, - { "nativeOpenFrontendByHandle", "(I)Landroid/media/tv/tuner/Tuner$Frontend;", + { "nativeOpenFrontendByHandle", "(J)Landroid/media/tv/tuner/Tuner$Frontend;", (void *)android_media_tv_Tuner_open_frontend_by_handle }, { "nativeShareFrontend", "(I)I", (void *)android_media_tv_Tuner_share_frontend }, @@ -5074,11 +5074,11 @@ static const JNINativeMethod gTunerMethods[] = { (void *)android_media_tv_Tuner_open_filter }, { "nativeOpenTimeFilter", "()Landroid/media/tv/tuner/filter/TimeFilter;", (void *)android_media_tv_Tuner_open_time_filter }, - { "nativeOpenLnbByHandle", "(I)Landroid/media/tv/tuner/Lnb;", + { "nativeOpenLnbByHandle", "(J)Landroid/media/tv/tuner/Lnb;", (void *)android_media_tv_Tuner_open_lnb_by_handle }, { "nativeOpenLnbByName", "(Ljava/lang/String;)Landroid/media/tv/tuner/Lnb;", (void *)android_media_tv_Tuner_open_lnb_by_name }, - { "nativeOpenDescramblerByHandle", "(I)Landroid/media/tv/tuner/Descrambler;", + { "nativeOpenDescramblerByHandle", "(J)Landroid/media/tv/tuner/Descrambler;", (void *)android_media_tv_Tuner_open_descrambler }, { "nativeOpenDvrRecorder", "(J)Landroid/media/tv/tuner/dvr/DvrRecorder;", (void *)android_media_tv_Tuner_open_dvr_recorder }, @@ -5086,12 +5086,12 @@ static const JNINativeMethod gTunerMethods[] = { (void *)android_media_tv_Tuner_open_dvr_playback }, { "nativeGetDemuxCapabilities", "()Landroid/media/tv/tuner/DemuxCapabilities;", (void *)android_media_tv_Tuner_get_demux_caps }, - { "nativeGetDemuxInfo", "(I)Landroid/media/tv/tuner/DemuxInfo;", + { "nativeGetDemuxInfo", "(J)Landroid/media/tv/tuner/DemuxInfo;", (void *)android_media_tv_Tuner_get_demux_info }, - { "nativeOpenDemuxByhandle", "(I)I", (void *)android_media_tv_Tuner_open_demux }, + { "nativeOpenDemuxByhandle", "(J)I", (void *)android_media_tv_Tuner_open_demux }, { "nativeClose", "()I", (void *)android_media_tv_Tuner_close_tuner }, - { "nativeCloseFrontend", "(I)I", (void *)android_media_tv_Tuner_close_frontend }, - { "nativeCloseDemux", "(I)I", (void *)android_media_tv_Tuner_close_demux }, + { "nativeCloseFrontend", "(J)I", (void *)android_media_tv_Tuner_close_frontend }, + { "nativeCloseDemux", "(J)I", (void *)android_media_tv_Tuner_close_demux }, { "nativeOpenSharedFilter", "(Ljava/lang/String;)Landroid/media/tv/tuner/filter/SharedFilter;", (void *)android_media_tv_Tuner_open_shared_filter}, diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h index 3de3ab91326c..7af2cd7bd5b7 100644 --- a/media/jni/android_media_tv_Tuner.h +++ b/media/jni/android_media_tv_Tuner.h @@ -206,7 +206,7 @@ struct JTuner : public RefBase { int disconnectCiCam(); int unlinkCiCam(jint id); jobject getFrontendIds(); - jobject openFrontendByHandle(int feHandle); + jobject openFrontendByHandle(jlong feHandle); int shareFrontend(int feId); int unshareFrontend(); void registerFeCbListener(JTuner* jtuner); @@ -221,16 +221,16 @@ struct JTuner : public RefBase { int setLnb(sp<LnbClient> lnbClient); bool isLnaSupported(); int setLna(bool enable); - jobject openLnbByHandle(int handle); + jobject openLnbByHandle(jlong handle); jobject openLnbByName(jstring name); jobject openFilter(DemuxFilterType type, int bufferSize); jobject openTimeFilter(); jobject openDescrambler(); jobject openDvr(DvrType type, jlong bufferSize); jobject getDemuxCaps(); - jobject getDemuxInfo(int handle); + jobject getDemuxInfo(jlong handle); jobject getFrontendStatus(jintArray types); - Result openDemux(int handle); + Result openDemux(jlong handle); jint close(); jint closeFrontend(); jint closeDemux(); diff --git a/media/jni/tuner/TunerClient.cpp b/media/jni/tuner/TunerClient.cpp index ea623d97a394..0097710f5c64 100644 --- a/media/jni/tuner/TunerClient.cpp +++ b/media/jni/tuner/TunerClient.cpp @@ -56,7 +56,7 @@ vector<int32_t> TunerClient::getFrontendIds() { return ids; } -sp<FrontendClient> TunerClient::openFrontend(int32_t frontendHandle) { +sp<FrontendClient> TunerClient::openFrontend(int64_t frontendHandle) { if (mTunerService != nullptr) { shared_ptr<ITunerFrontend> tunerFrontend; Status s = mTunerService->openFrontend(frontendHandle, &tunerFrontend); @@ -94,7 +94,7 @@ shared_ptr<FrontendInfo> TunerClient::getFrontendInfo(int32_t id) { return nullptr; } -sp<DemuxClient> TunerClient::openDemux(int32_t demuxHandle) { +sp<DemuxClient> TunerClient::openDemux(int64_t demuxHandle) { if (mTunerService != nullptr) { shared_ptr<ITunerDemux> tunerDemux; Status s = mTunerService->openDemux(demuxHandle, &tunerDemux); @@ -107,7 +107,7 @@ sp<DemuxClient> TunerClient::openDemux(int32_t demuxHandle) { return nullptr; } -shared_ptr<DemuxInfo> TunerClient::getDemuxInfo(int32_t demuxHandle) { +shared_ptr<DemuxInfo> TunerClient::getDemuxInfo(int64_t demuxHandle) { if (mTunerService != nullptr) { DemuxInfo aidlDemuxInfo; Status s = mTunerService->getDemuxInfo(demuxHandle, &aidlDemuxInfo); @@ -141,7 +141,7 @@ shared_ptr<DemuxCapabilities> TunerClient::getDemuxCaps() { return nullptr; } -sp<DescramblerClient> TunerClient::openDescrambler(int32_t descramblerHandle) { +sp<DescramblerClient> TunerClient::openDescrambler(int64_t descramblerHandle) { if (mTunerService != nullptr) { shared_ptr<ITunerDescrambler> tunerDescrambler; Status s = mTunerService->openDescrambler(descramblerHandle, &tunerDescrambler); @@ -154,7 +154,7 @@ sp<DescramblerClient> TunerClient::openDescrambler(int32_t descramblerHandle) { return nullptr; } -sp<LnbClient> TunerClient::openLnb(int32_t lnbHandle) { +sp<LnbClient> TunerClient::openLnb(int64_t lnbHandle) { if (mTunerService != nullptr) { shared_ptr<ITunerLnb> tunerLnb; Status s = mTunerService->openLnb(lnbHandle, &tunerLnb); diff --git a/media/jni/tuner/TunerClient.h b/media/jni/tuner/TunerClient.h index 6ab120b56d97..a348586454b8 100644 --- a/media/jni/tuner/TunerClient.h +++ b/media/jni/tuner/TunerClient.h @@ -65,7 +65,7 @@ public: * @param frontendHandle the handle of the frontend granted by TRM. * @return a newly created FrontendClient interface. */ - sp<FrontendClient> openFrontend(int32_t frontendHandle); + sp<FrontendClient> openFrontend(int64_t frontendHandle); /** * Retrieve the granted frontend's information. @@ -81,7 +81,7 @@ public: * @param demuxHandle the handle of the demux granted by TRM. * @return a newly created DemuxClient interface. */ - sp<DemuxClient> openDemux(int32_t demuxHandle); + sp<DemuxClient> openDemux(int64_t demuxHandle); /** * Retrieve the DemuxInfo of a specific demux @@ -89,7 +89,7 @@ public: * @param demuxHandle the handle of the demux to query demux info for * @return the demux info */ - shared_ptr<DemuxInfo> getDemuxInfo(int32_t demuxHandle); + shared_ptr<DemuxInfo> getDemuxInfo(int64_t demuxHandle); /** * Retrieve a list of demux info @@ -111,7 +111,7 @@ public: * @param descramblerHandle the handle of the descrambler granted by TRM. * @return a newly created DescramblerClient interface. */ - sp<DescramblerClient> openDescrambler(int32_t descramblerHandle); + sp<DescramblerClient> openDescrambler(int64_t descramblerHandle); /** * Open a new interface of LnbClient given an lnbHandle. @@ -119,7 +119,7 @@ public: * @param lnbHandle the handle of the LNB granted by TRM. * @return a newly created LnbClient interface. */ - sp<LnbClient> openLnb(int32_t lnbHandle); + sp<LnbClient> openLnb(int64_t lnbHandle); /** * Open a new interface of LnbClient given a LNB name. diff --git a/nfc/api/current.txt b/nfc/api/current.txt index b0d1f71e749f..447e980adaee 100644 --- a/nfc/api/current.txt +++ b/nfc/api/current.txt @@ -220,11 +220,14 @@ package android.nfc.cardemulation { field @Deprecated public static final String ACTION_CHANGE_DEFAULT = "android.nfc.cardemulation.action.ACTION_CHANGE_DEFAULT"; field public static final String CATEGORY_OTHER = "other"; field public static final String CATEGORY_PAYMENT = "payment"; + field @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public static final String DH = "DH"; + field @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public static final String ESE = "ESE"; field public static final String EXTRA_CATEGORY = "category"; field public static final String EXTRA_SERVICE_COMPONENT = "component"; field public static final int SELECTION_MODE_ALWAYS_ASK = 1; // 0x1 field public static final int SELECTION_MODE_ASK_IF_CONFLICT = 2; // 0x2 field public static final int SELECTION_MODE_PREFER_DEFAULT = 0; // 0x0 + field @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public static final String UICC = "UICC"; } public abstract class HostApduService extends android.app.Service { diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt index 3375e18c001d..2ff9829ddfd7 100644 --- a/nfc/api/system-current.txt +++ b/nfc/api/system-current.txt @@ -57,6 +57,7 @@ package android.nfc { @FlaggedApi("android.nfc.nfc_oem_extension") public final class NfcOemExtension { method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void clearPreference(); + method @FlaggedApi("android.nfc.nfc_oem_extension") @NonNull public java.util.List<java.lang.String> getActiveNfceeList(); method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void maybeTriggerFirmwareUpdate(); method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcOemExtension.Callback); method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void synchronizeScreenState(); @@ -74,6 +75,8 @@ package android.nfc.cardemulation { public final class CardEmulation { method @FlaggedApi("android.permission.flags.wallet_role_enabled") @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public static android.content.ComponentName getPreferredPaymentService(@NonNull android.content.Context); method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public java.util.List<android.nfc.cardemulation.ApduServiceInfo> getServices(@NonNull String, int); + method @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public void overrideRoutingTable(@NonNull android.app.Activity, @Nullable String, @Nullable String); + method @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public void recoverRoutingTable(@NonNull android.app.Activity); } } diff --git a/nfc/java/android/nfc/INfcAdapter.aidl b/nfc/java/android/nfc/INfcAdapter.aidl index c5b82ed0f8f2..6c0f93354683 100644 --- a/nfc/java/android/nfc/INfcAdapter.aidl +++ b/nfc/java/android/nfc/INfcAdapter.aidl @@ -113,4 +113,5 @@ interface INfcAdapter void clearPreference(); void setScreenState(); void checkFirmware(); + List<String> fetchActiveNfceeList(); } diff --git a/nfc/java/android/nfc/INfcCardEmulation.aidl b/nfc/java/android/nfc/INfcCardEmulation.aidl index cb97f23e813c..79f1275ec629 100644 --- a/nfc/java/android/nfc/INfcCardEmulation.aidl +++ b/nfc/java/android/nfc/INfcCardEmulation.aidl @@ -48,6 +48,6 @@ interface INfcCardEmulation boolean setServiceEnabledForCategoryOther(int userHandle, in ComponentName app, boolean status); boolean isDefaultPaymentRegistered(); - boolean overrideRoutingTable(int userHandle, String protocol, String technology); - boolean recoverRoutingTable(int userHandle); + void overrideRoutingTable(int userHandle, String protocol, String technology, in String pkg); + void recoverRoutingTable(int userHandle); } diff --git a/nfc/java/android/nfc/NfcOemExtension.java b/nfc/java/android/nfc/NfcOemExtension.java index 2ec819cdc1a9..204ba9fbefad 100644 --- a/nfc/java/android/nfc/NfcOemExtension.java +++ b/nfc/java/android/nfc/NfcOemExtension.java @@ -26,6 +26,8 @@ import android.os.Binder; import android.os.RemoteException; import android.util.Log; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.Executor; /** @@ -153,6 +155,19 @@ public final class NfcOemExtension { NfcAdapter.callService(() -> NfcAdapter.sService.checkFirmware()); } + /** + * Get the Active NFCEE (NFC Execution Environment) List + * + * @return List of activated secure elements on success + * which can contain "eSE" and "UICC", otherwise empty list. + */ + @NonNull + @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION) + public List<String> getActiveNfceeList() { + return NfcAdapter.callServiceReturn(() -> + NfcAdapter.sService.fetchActiveNfceeList(), new ArrayList<String>()); + } + private final class NfcOemExtensionCallback extends INfcOemExtensionCallback.Stub { @Override public void onTagConnected(boolean connected, Tag tag) throws RemoteException { diff --git a/nfc/java/android/nfc/cardemulation/CardEmulation.java b/nfc/java/android/nfc/cardemulation/CardEmulation.java index e0438ce9258c..03372b206f48 100644 --- a/nfc/java/android/nfc/cardemulation/CardEmulation.java +++ b/nfc/java/android/nfc/cardemulation/CardEmulation.java @@ -23,6 +23,7 @@ import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; +import android.annotation.StringDef; import android.annotation.SystemApi; import android.annotation.UserHandleAware; import android.annotation.UserIdInt; @@ -43,6 +44,8 @@ import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; import android.util.Log; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.HashMap; import java.util.HexFormat; import java.util.List; @@ -148,6 +151,21 @@ public final class CardEmulation { * that service will be invoked directly. */ public static final int SELECTION_MODE_ASK_IF_CONFLICT = 2; + /** + * Route to Device Host (DH). + */ + @FlaggedApi(Flags.FLAG_NFC_OVERRIDE_RECOVER_ROUTING_TABLE) + public static final String DH = "DH"; + /** + * Route to eSE. + */ + @FlaggedApi(Flags.FLAG_NFC_OVERRIDE_RECOVER_ROUTING_TABLE) + public static final String ESE = "ESE"; + /** + * Route to UICC. + */ + @FlaggedApi(Flags.FLAG_NFC_OVERRIDE_RECOVER_ROUTING_TABLE) + public static final String UICC = "UICC"; static boolean sIsInitialized = false; static HashMap<Context, CardEmulation> sCardEmus = new HashMap<Context, CardEmulation>(); @@ -865,11 +883,22 @@ public final class CardEmulation { sService.setServiceEnabledForCategoryOther(userId, service, status), false); } + /** @hide */ + @StringDef({ + DH, + ESE, + UICC + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ProtocolAndTechnologyRoute {} + /** * Setting NFC controller routing table, which includes Protocol Route and Technology Route, * while this Activity is in the foreground. * - * The parameter set to null can be used to keep current values for that entry. + * The parameter set to null can be used to keep current values for that entry. Either + * Protocol Route or Technology Route should be override when calling this API, otherwise + * throw {@link IllegalArgumentException}. * <p> * Example usage in an Activity that requires to set proto route to "ESE" and keep tech route: * <pre> @@ -877,26 +906,39 @@ public final class CardEmulation { * mNfcAdapter.overrideRoutingTable(this , "ESE" , null); * }</pre> * </p> - * Also activities must call this method when it goes to the background, - * with all parameters set to null. + * Also activities must call {@link #recoverRoutingTable(Activity)} + * when it goes to the background. Only the package of the + * currently preferred service (the service set as preferred by the current foreground + * application via {@link CardEmulation#setPreferredService(Activity, ComponentName)} or the + * current Default Wallet Role Holder {@link android.app.role.RoleManager#ROLE_WALLET}), + * otherwise a call to this method will fail and throw {@link SecurityException}. * @param activity The Activity that requests NFC controller routing table to be changed. * @param protocol ISO-DEP route destination, which can be "DH" or "UICC" or "ESE". * @param technology Tech-A, Tech-B route destination, which can be "DH" or "UICC" or "ESE". - * @return true if operation is successful and false otherwise - * + * @throws SecurityException if the caller is not the preferred NFC service + * @throws IllegalArgumentException if the activity is not resumed or the caller is not in the + * foreground, or both protocol route and technology route are null. + * <p> * This is a high risk API and only included to support mainline effort * @hide */ - public boolean overrideRoutingTable(Activity activity, String protocol, String technology) { - if (activity == null) { - throw new NullPointerException("activity or service or category is null"); - } + @SystemApi + @FlaggedApi(Flags.FLAG_NFC_OVERRIDE_RECOVER_ROUTING_TABLE) + public void overrideRoutingTable( + @NonNull Activity activity, @ProtocolAndTechnologyRoute @Nullable String protocol, + @ProtocolAndTechnologyRoute @Nullable String technology) { if (!activity.isResumed()) { throw new IllegalArgumentException("Activity must be resumed."); } - return callServiceReturn(() -> + if (protocol == null && technology == null) { + throw new IllegalArgumentException(("Both Protocol and Technology are null.")); + } + callService(() -> sService.overrideRoutingTable( - mContext.getUser().getIdentifier(), protocol, technology), false); + mContext.getUser().getIdentifier(), + protocol, + technology, + mContext.getPackageName())); } /** @@ -904,20 +946,19 @@ public final class CardEmulation { * which was changed by {@link #overrideRoutingTable(Activity, String, String)} * * @param activity The Activity that requested NFC controller routing table to be changed. - * @return true if operation is successful and false otherwise + * @throws IllegalArgumentException if the caller is not in the foreground. * * @hide */ - public boolean recoverRoutingTable(Activity activity) { - if (activity == null) { - throw new NullPointerException("activity is null"); - } + @SystemApi + @FlaggedApi(Flags.FLAG_NFC_OVERRIDE_RECOVER_ROUTING_TABLE) + public void recoverRoutingTable(@NonNull Activity activity) { if (!activity.isResumed()) { throw new IllegalArgumentException("Activity must be resumed."); } - return callServiceReturn(() -> + callService(() -> sService.recoverRoutingTable( - mContext.getUser().getIdentifier()), false); + mContext.getUser().getIdentifier())); } /** diff --git a/nfc/java/android/nfc/flags.aconfig b/nfc/java/android/nfc/flags.aconfig index 5819b98dd411..0fda91d2b48e 100644 --- a/nfc/java/android/nfc/flags.aconfig +++ b/nfc/java/android/nfc/flags.aconfig @@ -133,3 +133,11 @@ flag { description: "Add Settings.ACTION_MANAGE_OTHER_NFC_SERVICES_SETTINGS" bug: "358129872" } + +flag { + name: "nfc_override_recover_routing_table" + is_exported: true + namespace: "nfc" + description: "Enable override and recover routing table" + bug: "329043523" +} diff --git a/packages/PackageInstaller/TEST_MAPPING b/packages/PackageInstaller/TEST_MAPPING index be4e87eba5bf..da6efeead956 100644 --- a/packages/PackageInstaller/TEST_MAPPING +++ b/packages/PackageInstaller/TEST_MAPPING @@ -1,17 +1,4 @@ { - "presubmit": [ - { - "name": "CtsPackageInstallerCUJTestCases", - "options":[ - { - "exclude-annotation":"androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation":"org.junit.Ignore" - } - ] - } - ], "postsubmit": [ { "name": "CtsPackageInstallTestCases", @@ -43,14 +30,47 @@ "name": "CtsIntentSignatureTestCases" }, { - "name": "CtsPackageInstallerCUJTestCases", + "name": "CtsPackageInstallerCUJInstallationTestCases", + "options":[ + { + "exclude-annotation":"androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation":"org.junit.Ignore" + } + ] + }, + { + "name": "CtsPackageInstallerCUJUninstallationTestCases", + "options":[ + { + "exclude-annotation":"androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation":"org.junit.Ignore" + } + ] + }, + { + "name": "CtsPackageInstallerCUJUpdateOwnerShipTestCases", + "options":[ + { + "exclude-annotation":"androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation":"org.junit.Ignore" + } + ] + }, + { + "name": "CtsPackageInstallerCUJUpdateSelfTestCases", "options":[ - { - "exclude-annotation":"androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation":"org.junit.Ignore" - } + { + "exclude-annotation":"androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation":"org.junit.Ignore" + } ] } ] diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveErrorFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveErrorFragment.java index d33433f3983b..2fb32a7da432 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveErrorFragment.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveErrorFragment.java @@ -16,10 +16,12 @@ package com.android.packageinstaller; +import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import android.app.Activity; import android.app.AlertDialog; +import android.app.BroadcastOptions; import android.app.Dialog; import android.app.DialogFragment; import android.app.PendingIntent; @@ -161,25 +163,31 @@ public class UnarchiveErrorFragment extends DialogFragment implements return; } + // Allow the error handling actvities to start in the background. + final BroadcastOptions options = BroadcastOptions.makeBasic(); + options.setPendingIntentBackgroundActivityStartMode( + MODE_BACKGROUND_ACTIVITY_START_ALLOWED); switch (mStatus) { case PackageInstaller.UNARCHIVAL_ERROR_USER_ACTION_NEEDED: activity.startIntentSender(mExtraIntent.getIntentSender(), /* fillInIntent= */ - null, /* flagsMask= */ 0, FLAG_ACTIVITY_NEW_TASK, /* extraFlags= */ 0); + null, /* flagsMask= */ 0, FLAG_ACTIVITY_NEW_TASK, /* extraFlags= */ 0, + options.toBundle()); break; case PackageInstaller.UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE: if (mExtraIntent != null) { activity.startIntentSender(mExtraIntent.getIntentSender(), /* fillInIntent= */ - null, /* flagsMask= */ 0, FLAG_ACTIVITY_NEW_TASK, /* extraFlags= */ 0); + null, /* flagsMask= */ 0, FLAG_ACTIVITY_NEW_TASK, /* extraFlags= */ 0, + options.toBundle()); } else { Intent intent = new Intent("android.intent.action.MANAGE_PACKAGE_STORAGE"); - startActivity(intent); + startActivity(intent, options.toBundle()); } break; case PackageInstaller.UNARCHIVAL_ERROR_INSTALLER_DISABLED: Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); Uri uri = Uri.fromParts("package", mInstallerPackageName, null); intent.setData(uri); - startActivity(intent); + startActivity(intent, options.toBundle()); break; default: // Do nothing. The rest of the dialogs are purely informational. diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts index f36344aa4846..a543450821b8 100644 --- a/packages/SettingsLib/Spa/build.gradle.kts +++ b/packages/SettingsLib/Spa/build.gradle.kts @@ -29,7 +29,7 @@ val androidTop: String = File(rootDir, "../../../../..").canonicalPath allprojects { extra["androidTop"] = androidTop - extra["jetpackComposeVersion"] = "1.7.0-beta05" + extra["jetpackComposeVersion"] = "1.7.0-beta07" } subprojects { @@ -37,7 +37,7 @@ subprojects { plugins.withType<AndroidBasePlugin> { configure<BaseExtension> { - compileSdkVersion(34) + compileSdkVersion(35) defaultConfig { minSdk = 21 diff --git a/packages/SettingsLib/Spa/gradle/libs.versions.toml b/packages/SettingsLib/Spa/gradle/libs.versions.toml index 1cca73af9a42..3507605c5ad2 100644 --- a/packages/SettingsLib/Spa/gradle/libs.versions.toml +++ b/packages/SettingsLib/Spa/gradle/libs.versions.toml @@ -15,7 +15,7 @@ # [versions] -agp = "8.5.1" +agp = "8.5.2" compose-compiler = "1.5.11" dexmaker-mockito = "2.28.3" jvm = "17" diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts index ce3d96e2d388..e9153e3e6010 100644 --- a/packages/SettingsLib/Spa/spa/build.gradle.kts +++ b/packages/SettingsLib/Spa/spa/build.gradle.kts @@ -54,13 +54,13 @@ android { dependencies { api(project(":SettingsLibColor")) api("androidx.appcompat:appcompat:1.7.0") - api("androidx.compose.material3:material3:1.3.0-beta04") + api("androidx.compose.material3:material3:1.3.0-beta05") api("androidx.compose.material:material-icons-extended:$jetpackComposeVersion") api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion") api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion") api("androidx.lifecycle:lifecycle-livedata-ktx") api("androidx.lifecycle:lifecycle-runtime-compose") - api("androidx.navigation:navigation-compose:2.8.0-beta05") + api("androidx.navigation:navigation-compose:2.8.0-beta07") api("com.github.PhilJay:MPAndroidChart:v3.1.0-alpha") api("com.google.android.material:material:1.11.0") debugApi("androidx.compose.ui:ui-tooling:$jetpackComposeVersion") diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java index de60fdc2b47e..b3ac54a9f7bc 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java @@ -21,6 +21,8 @@ import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; +import android.media.AudioDeviceAttributes; +import android.media.AudioDeviceInfo; import android.media.AudioManager; import android.net.Uri; import android.provider.DeviceConfig; @@ -41,9 +43,12 @@ import com.android.settingslib.flags.Flags; import com.android.settingslib.widget.AdaptiveIcon; import com.android.settingslib.widget.AdaptiveOutlineDrawable; +import com.google.common.collect.ImmutableSet; + import java.io.IOException; import java.util.List; import java.util.Locale; +import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -57,6 +62,9 @@ public class BluetoothUtils { public static final String BT_ADVANCED_HEADER_ENABLED = "bt_advanced_header_enabled"; private static final int METADATA_FAST_PAIR_CUSTOMIZED_FIELDS = 25; private static final String KEY_HEARABLE_CONTROL_SLICE = "HEARABLE_CONTROL_SLICE_WITH_WIDTH"; + private static final Set<Integer> SA_PROFILES = + ImmutableSet.of( + BluetoothProfile.A2DP, BluetoothProfile.LE_AUDIO, BluetoothProfile.HEARING_AID); private static ErrorListener sErrorListener; @@ -895,4 +903,62 @@ public class BluetoothUtils { } return null; } + + /** + * Gets {@link AudioDeviceAttributes} of bluetooth device for spatial audio. Returns null if + * it's not an audio device(no A2DP, LE Audio and Hearing Aid profile). + */ + @Nullable + public static AudioDeviceAttributes getAudioDeviceAttributesForSpatialAudio( + CachedBluetoothDevice cachedDevice, + @AudioManager.AudioDeviceCategory int audioDeviceCategory) { + AudioDeviceAttributes saDevice = null; + for (LocalBluetoothProfile profile : cachedDevice.getProfiles()) { + // pick first enabled profile that is compatible with spatial audio + if (SA_PROFILES.contains(profile.getProfileId()) + && profile.isEnabled(cachedDevice.getDevice())) { + switch (profile.getProfileId()) { + case BluetoothProfile.A2DP: + saDevice = + new AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, + AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, + cachedDevice.getAddress()); + break; + case BluetoothProfile.LE_AUDIO: + if (audioDeviceCategory + == AudioManager.AUDIO_DEVICE_CATEGORY_SPEAKER) { + saDevice = + new AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, + AudioDeviceInfo.TYPE_BLE_SPEAKER, + cachedDevice.getAddress()); + } else { + saDevice = + new AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, + AudioDeviceInfo.TYPE_BLE_HEADSET, + cachedDevice.getAddress()); + } + + break; + case BluetoothProfile.HEARING_AID: + saDevice = + new AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, + AudioDeviceInfo.TYPE_HEARING_AID, + cachedDevice.getAddress()); + break; + default: + Log.i( + TAG, + "unrecognized profile for spatial audio: " + + profile.getProfileId()); + break; + } + break; + } + } + return saDevice; + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingId.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingId.java index 20a0339b9182..58dc8c7aad6c 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingId.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingId.java @@ -108,6 +108,12 @@ public @interface DeviceSettingId { /** Device setting ID for device details footer. */ int DEVICE_SETTING_ID_DEVICE_DETAILS_FOOTER = 19; + /** Device setting ID for spatial audio group. */ + int DEVICE_SETTING_ID_SPATIAL_AUDIO_MULTI_TOGGLE = 20; + + /** Device setting ID for "More Settings" page. */ + int DEVICE_SETTING_ID_MORE_SETTINGS = 21; + /** Device setting ID for ANC. */ int DEVICE_SETTING_ID_ANC = 1001; } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingItem.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingItem.kt index 9ee33b0dbffe..a0fe5d275b36 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingItem.kt +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingItem.kt @@ -27,6 +27,7 @@ import android.os.Parcelable * @property packageName The package name for service binding. * @property className The class name for service binding. * @property intentAction The intent action for service binding. + * @property preferenceKey The preference key if it's a built-in preference. * @property extras Extra bundle */ data class DeviceSettingItem( @@ -34,6 +35,7 @@ data class DeviceSettingItem( val packageName: String, val className: String, val intentAction: String, + val preferenceKey: String? = null, val extras: Bundle = Bundle.EMPTY, ) : Parcelable { @@ -45,6 +47,7 @@ data class DeviceSettingItem( writeString(packageName) writeString(className) writeString(intentAction) + writeString(preferenceKey) writeBundle(extras) } } @@ -60,6 +63,7 @@ data class DeviceSettingItem( packageName = readString() ?: "", className = readString() ?: "", intentAction = readString() ?: "", + preferenceKey = readString() ?: "", extras = readBundle((Bundle::class.java.classLoader)) ?: Bundle.EMPTY, ) } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt index 326bb31bdb9f..ce7064c9d781 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt @@ -18,6 +18,7 @@ package com.android.settingslib.bluetooth.devicesettings.data.repository import android.bluetooth.BluetoothAdapter import android.content.Context +import android.text.TextUtils import com.android.settingslib.bluetooth.CachedBluetoothDevice import com.android.settingslib.bluetooth.devicesettings.ActionSwitchPreference import com.android.settingslib.bluetooth.devicesettings.DeviceSetting @@ -28,6 +29,7 @@ import com.android.settingslib.bluetooth.devicesettings.MultiTogglePreference import com.android.settingslib.bluetooth.devicesettings.ToggleInfo import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigItemModel import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigModel +import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingIcon import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingModel import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingStateModel import com.android.settingslib.bluetooth.devicesettings.shared.model.ToggleModel @@ -76,8 +78,7 @@ class DeviceSettingRepositoryImpl( coroutineScope, backgroundCoroutineContext, ) - } - ) + }) override suspend fun getDeviceSettingsConfig( cachedDevice: CachedBluetoothDevice @@ -96,11 +97,15 @@ class DeviceSettingRepositoryImpl( DeviceSettingConfigModel( mainItems = mainContentItems.map { it.toModel() }, moreSettingsItems = moreSettingsItems.map { it.toModel() }, - moreSettingsPageFooter = moreSettingsFooter - ) + moreSettingsPageFooter = moreSettingsFooter) - private fun DeviceSettingItem.toModel(): DeviceSettingConfigItemModel = - DeviceSettingConfigItemModel(settingId) + private fun DeviceSettingItem.toModel(): DeviceSettingConfigItemModel { + return if (!TextUtils.isEmpty(preferenceKey)) { + DeviceSettingConfigItemModel.BuiltinItem(settingId, preferenceKey!!) + } else { + DeviceSettingConfigItemModel.AppProvidedItem(settingId) + } + } private fun DeviceSetting.toModel( cachedDevice: CachedBluetoothDevice, @@ -113,7 +118,7 @@ class DeviceSettingRepositoryImpl( id = settingId, title = pref.title, summary = pref.summary, - icon = pref.icon, + icon = pref.icon?.let { DeviceSettingIcon.BitmapIcon(it) }, isAllowedChangingState = pref.isAllowedChangingState, intent = pref.intent, switchState = @@ -149,5 +154,6 @@ class DeviceSettingRepositoryImpl( else -> DeviceSettingModel.Unknown(cachedDevice, settingId) } - private fun ToggleInfo.toModel(): ToggleModel = ToggleModel(label, icon) + private fun ToggleInfo.toModel(): ToggleModel = + ToggleModel(label, DeviceSettingIcon.BitmapIcon(icon)) } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingConfigModel.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingConfigModel.kt index cd597ee65bce..e97f76cca3a9 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingConfigModel.kt +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingConfigModel.kt @@ -25,9 +25,20 @@ data class DeviceSettingConfigModel( /** Items need to be shown in device details more settings page. */ val moreSettingsItems: List<DeviceSettingConfigItemModel>, /** Footer text in more settings page. */ - val moreSettingsPageFooter: String) + val moreSettingsPageFooter: String +) /** Models a device setting item in config. */ -data class DeviceSettingConfigItemModel( - @DeviceSettingId val settingId: Int, -) +sealed interface DeviceSettingConfigItemModel { + @DeviceSettingId val settingId: Int + + /** A built-in item in Settings. */ + data class BuiltinItem( + @DeviceSettingId override val settingId: Int, + val preferenceKey: String? + ) : DeviceSettingConfigItemModel + + /** A remote item provided by other apps. */ + data class AppProvidedItem(@DeviceSettingId override val settingId: Int) : + DeviceSettingConfigItemModel +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingModel.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingModel.kt index db782803937c..2a6321704a1a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingModel.kt +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingModel.kt @@ -18,6 +18,7 @@ package com.android.settingslib.bluetooth.devicesettings.shared.model import android.content.Intent import android.graphics.Bitmap +import androidx.annotation.DrawableRes import com.android.settingslib.bluetooth.CachedBluetoothDevice import com.android.settingslib.bluetooth.devicesettings.DeviceSettingId @@ -32,7 +33,7 @@ sealed interface DeviceSettingModel { @DeviceSettingId override val id: Int, val title: String, val summary: String? = null, - val icon: Bitmap? = null, + val icon: DeviceSettingIcon? = null, val intent: Intent? = null, val switchState: DeviceSettingStateModel.ActionSwitchPreferenceState? = null, val isAllowedChangingState: Boolean = true, @@ -59,4 +60,12 @@ sealed interface DeviceSettingModel { } /** Models a toggle in [DeviceSettingModel.MultiTogglePreference]. */ -data class ToggleModel(val label: String, val icon: Bitmap) +data class ToggleModel(val label: String, val icon: DeviceSettingIcon) + +/** Models an icon in device settings. */ +sealed interface DeviceSettingIcon { + + data class BitmapIcon(val bitmap: Bitmap) : DeviceSettingIcon + + data class ResourceIcon(@DrawableRes val resId: Int) : DeviceSettingIcon +} diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java index 2f4b2efeec7f..69ef718200b9 100644 --- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java +++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java @@ -22,6 +22,7 @@ import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL; import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY; import static android.service.notification.SystemZenRules.getTriggerDescriptionForScheduleEvent; import static android.service.notification.SystemZenRules.getTriggerDescriptionForScheduleTime; +import static android.service.notification.SystemZenRules.PACKAGE_ANDROID; import static android.service.notification.ZenModeConfig.tryParseCountdownConditionId; import static android.service.notification.ZenModeConfig.tryParseEventConditionId; import static android.service.notification.ZenModeConfig.tryParseScheduleConditionId; @@ -129,7 +130,11 @@ public class ZenMode implements Parcelable { } public static ZenMode manualDndMode(AutomaticZenRule manualRule, boolean isActive) { - return new ZenMode(MANUAL_DND_MODE_ID, manualRule, + // Manual rule is owned by the system, so we set it here + AutomaticZenRule manualRuleWithPkg = new AutomaticZenRule.Builder(manualRule) + .setPackage(PACKAGE_ANDROID) + .build(); + return new ZenMode(MANUAL_DND_MODE_ID, manualRuleWithPkg, isActive ? Status.ENABLED_AND_ACTIVE : Status.ENABLED, true); } @@ -220,6 +225,16 @@ public class ZenMode implements Parcelable { return getTriggerDescription(); } + /** + * Returns an icon "key" that is guaranteed to be different if the icon is different. Note that + * the inverse is not true, i.e. two keys can be different and the icon still be visually the + * same. + */ + @NonNull + public String getIconKey() { + return mRule.getType() + ":" + mRule.getPackageName() + ":" + mRule.getIconResId(); + } + @NonNull public ListenableFuture<Drawable> getIcon(@NonNull Context context, @NonNull ZenIconLoader iconLoader) { diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt index ebba7f152b90..2f8105ae461d 100644 --- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt +++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt @@ -63,7 +63,7 @@ typealias GroupIdToVolumes = Map<Int, Int> /** Provides audio sharing functionality. */ interface AudioSharingRepository { /** Whether the device is in audio sharing. */ - val inAudioSharing: Flow<Boolean> + val inAudioSharing: StateFlow<Boolean> /** The primary headset groupId in audio sharing. */ val primaryGroupId: StateFlow<Int> @@ -101,7 +101,7 @@ class AudioSharingRepositoryImpl( .flowOn(backgroundCoroutineContext) .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), false) - override val inAudioSharing: Flow<Boolean> = + override val inAudioSharing: StateFlow<Boolean> = isAudioSharingProfilesReady.flatMapLatest { ready -> if (ready) { btManager.profileManager.leAudioBroadcastProfile.onBroadcastStartedOrStopped @@ -113,6 +113,7 @@ class AudioSharingRepositoryImpl( flowOf(false) } } + .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), false) private val primaryChange: Flow<Unit> = callbackFlow { val callback = @@ -254,7 +255,7 @@ class AudioSharingRepositoryImpl( } class AudioSharingRepositoryEmptyImpl : AudioSharingRepository { - override val inAudioSharing: Flow<Boolean> = flowOf(false) + override val inAudioSharing: StateFlow<Boolean> = MutableStateFlow(false) override val primaryGroupId: StateFlow<Int> = MutableStateFlow(BluetoothCsipSetCoordinator.GROUP_ID_INVALID) override val secondaryGroupId: StateFlow<Int> = diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java index 4551f1eba84b..926d3cb448e8 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java @@ -31,10 +31,13 @@ import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothCsipSetCoordinator; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothLeBroadcastReceiveState; +import android.bluetooth.BluetoothProfile; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.graphics.drawable.Drawable; +import android.media.AudioDeviceAttributes; +import android.media.AudioDeviceInfo; import android.media.AudioManager; import android.net.Uri; import android.platform.test.flag.junit.SetFlagsRule; @@ -70,6 +73,9 @@ public class BluetoothUtilsTest { @Mock private BluetoothDevice mBluetoothDevice; @Mock private AudioManager mAudioManager; @Mock private PackageManager mPackageManager; + @Mock private LeAudioProfile mA2dpProfile; + @Mock private LeAudioProfile mLeAudioProfile; + @Mock private LeAudioProfile mHearingAid; @Mock private LocalBluetoothLeBroadcast mBroadcast; @Mock private LocalBluetoothProfileManager mProfileManager; @Mock private LocalBluetoothManager mLocalBluetoothManager; @@ -100,6 +106,9 @@ public class BluetoothUtilsTest { when(mLocalBluetoothManager.getProfileManager()).thenReturn(mProfileManager); when(mProfileManager.getLeAudioBroadcastProfile()).thenReturn(mBroadcast); when(mProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(mAssistant); + when(mA2dpProfile.getProfileId()).thenReturn(BluetoothProfile.A2DP); + when(mLeAudioProfile.getProfileId()).thenReturn(BluetoothProfile.LE_AUDIO); + when(mHearingAid.getProfileId()).thenReturn(BluetoothProfile.HEARING_AID); } @Test @@ -756,4 +765,84 @@ public class BluetoothUtilsTest { mContext.getContentResolver(), mLocalBluetoothManager)) .isEqualTo(mCachedBluetoothDevice); } + + @Test + public void getAudioDeviceAttributesForSpatialAudio_bleHeadset() { + String address = "11:22:33:44:55:66"; + when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice); + when(mCachedBluetoothDevice.getAddress()).thenReturn(address); + when(mCachedBluetoothDevice.getProfiles()).thenReturn(List.of(mLeAudioProfile)); + when(mLeAudioProfile.isEnabled(mBluetoothDevice)).thenReturn(true); + + AudioDeviceAttributes attr = + BluetoothUtils.getAudioDeviceAttributesForSpatialAudio( + mCachedBluetoothDevice, AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES); + + assertThat(attr) + .isEqualTo( + new AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, + AudioDeviceInfo.TYPE_BLE_HEADSET, + address)); + } + + @Test + public void getAudioDeviceAttributesForSpatialAudio_bleSpeaker() { + String address = "11:22:33:44:55:66"; + when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice); + when(mCachedBluetoothDevice.getAddress()).thenReturn(address); + when(mCachedBluetoothDevice.getProfiles()).thenReturn(List.of(mLeAudioProfile)); + when(mLeAudioProfile.isEnabled(mBluetoothDevice)).thenReturn(true); + + AudioDeviceAttributes attr = + BluetoothUtils.getAudioDeviceAttributesForSpatialAudio( + mCachedBluetoothDevice, AudioManager.AUDIO_DEVICE_CATEGORY_SPEAKER); + + assertThat(attr) + .isEqualTo( + new AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, + AudioDeviceInfo.TYPE_BLE_SPEAKER, + address)); + } + + @Test + public void getAudioDeviceAttributesForSpatialAudio_a2dp() { + String address = "11:22:33:44:55:66"; + when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice); + when(mCachedBluetoothDevice.getAddress()).thenReturn(address); + when(mCachedBluetoothDevice.getProfiles()).thenReturn(List.of(mA2dpProfile)); + when(mA2dpProfile.isEnabled(mBluetoothDevice)).thenReturn(true); + + AudioDeviceAttributes attr = + BluetoothUtils.getAudioDeviceAttributesForSpatialAudio( + mCachedBluetoothDevice, AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES); + + assertThat(attr) + .isEqualTo( + new AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, + AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, + address)); + } + + @Test + public void getAudioDeviceAttributesForSpatialAudio_hearingAid() { + String address = "11:22:33:44:55:66"; + when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice); + when(mCachedBluetoothDevice.getAddress()).thenReturn(address); + when(mCachedBluetoothDevice.getProfiles()).thenReturn(List.of(mHearingAid)); + when(mHearingAid.isEnabled(mBluetoothDevice)).thenReturn(true); + + AudioDeviceAttributes attr = + BluetoothUtils.getAudioDeviceAttributesForSpatialAudio( + mCachedBluetoothDevice, AudioManager.AUDIO_DEVICE_CATEGORY_HEARING_AID); + + assertThat(attr) + .isEqualTo( + new AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, + AudioDeviceInfo.TYPE_HEARING_AID, + address)); + } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfigTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfigTest.kt index 2b29a6ef5e5a..9568d66ac7db 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfigTest.kt +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfigTest.kt @@ -37,9 +37,9 @@ class DeviceSettingsConfigTest { "package_name_1", "class_name_1", "intent_action_1", - Bundle() - ) - ), + null, + Bundle(), + )), moreSettingsItems = listOf( DeviceSettingItem( @@ -47,9 +47,9 @@ class DeviceSettingsConfigTest { "package_name_2", "class_name_2", "intent_action_2", - Bundle() - ) - ), + null, + Bundle(), + )), moreSettingsFooter = "footer", extras = Bundle().apply { putString("key1", "value1") }, ) diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt index fee23945f7b5..4c5ee9e4ee4c 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt @@ -40,6 +40,7 @@ import com.android.settingslib.bluetooth.devicesettings.MultiTogglePreferenceSta import com.android.settingslib.bluetooth.devicesettings.ToggleInfo import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigItemModel import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigModel +import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingIcon import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingModel import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingStateModel import com.android.settingslib.bluetooth.devicesettings.shared.model.ToggleModel @@ -352,7 +353,7 @@ class DeviceSettingRepositoryTest { val pref = serviceResponse.preference as ActionSwitchPreference assertThat(actual.title).isEqualTo(pref.title) assertThat(actual.summary).isEqualTo(pref.summary) - assertThat(actual.icon).isEqualTo(pref.icon) + assertThat(actual.icon).isEqualTo(DeviceSettingIcon.BitmapIcon(pref.icon!!)) assertThat(actual.isAllowedChangingState).isEqualTo(pref.isAllowedChangingState) if (pref.hasSwitch()) { assertThat(actual.switchState!!.checked).isEqualTo(pref.checked) diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java index d9fdcc38b576..f46424773fef 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java @@ -19,6 +19,7 @@ package com.android.settingslib.notification.modes; import static android.app.NotificationManager.INTERRUPTION_FILTER_ALARMS; import static android.app.NotificationManager.INTERRUPTION_FILTER_NONE; import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY; +import static android.service.notification.SystemZenRules.PACKAGE_ANDROID; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; @@ -67,6 +68,7 @@ public class ZenModeTest { assertThat(manualMode.canEditNameAndIcon()).isFalse(); assertThat(manualMode.canBeDeleted()).isFalse(); assertThat(manualMode.isActive()).isFalse(); + assertThat(manualMode.getRule().getPackageName()).isEqualTo(PACKAGE_ANDROID); } @Test diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 6d78705d1fbc..5ea75be11c47 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -748,6 +748,7 @@ android_library { "//frameworks/libs/systemui:motion_tool_lib", "//frameworks/libs/systemui:contextualeducationlib", "androidx.core_core-animation-testing", + "androidx.lifecycle_lifecycle-runtime-testing", "androidx.compose.ui_ui", "flag-junit", "ravenwood-junit", @@ -789,6 +790,7 @@ android_library { "SystemUI-tests-base", "androidx.test.uiautomator_uiautomator", "androidx.core_core-animation-testing", + "androidx.lifecycle_lifecycle-runtime-testing", "mockito-target-extended-minus-junit4", "mockito-kotlin-nodeps", "androidx.test.ext.junit", diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index cdbac338e0be..047c097c8847 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -281,13 +281,6 @@ flag { } flag { - name: "qs_new_pipeline" - namespace: "systemui" - description: "Use the new pipeline for Quick Settings. Should have no behavior changes." - bug: "241772429" -} - -flag { name: "qs_new_tiles" namespace: "systemui" description: "Use the new tiles in the Quick Settings. Should have no behavior changes." @@ -387,6 +380,17 @@ flag { } flag { + name: "status_bar_stop_updating_window_height" + namespace: "systemui" + description: "Don't have PhoneStatusBarView manually trigger an update of the height in " + "StatusBarWindowController" + bug: "360115167" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "compose_bouncer" namespace: "systemui" description: "Use the new compose bouncer in SystemUI" @@ -998,6 +1002,16 @@ flag { } flag { + name: "communal_widget_trampoline_fix" + namespace: "systemui" + description: "fixes activity starts caused by non-activity trampolines from widgets." + bug: "350468769" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "app_clips_backlinks" namespace: "systemui" description: "Enables Backlinks improvement feature in App Clips" @@ -1035,6 +1049,13 @@ flag { } flag { + name: "media_controls_button_media3" + namespace: "systemui" + description: "Enable media action buttons updates using media3" + bug: "360196209" +} + +flag { namespace: "systemui" name: "enable_view_capture_tracing" description: "Enables view capture tracing in System UI." diff --git a/packages/SystemUI/compose/core/src/com/android/compose/PlatformButtons.kt b/packages/SystemUI/compose/core/src/com/android/compose/PlatformButtons.kt index a18b460cb168..a5f8057b524f 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/PlatformButtons.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/PlatformButtons.kt @@ -21,8 +21,7 @@ import androidx.annotation.DrawableRes import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.RowScope -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.heightIn import androidx.compose.material3.ButtonColors import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Icon @@ -32,7 +31,6 @@ import androidx.compose.material3.IconButtonDefaults import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource -import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.android.compose.theme.LocalAndroidColorScheme @@ -42,11 +40,10 @@ fun PlatformButton( modifier: Modifier = Modifier, enabled: Boolean = true, colors: ButtonColors = filledButtonColors(), - verticalPadding: Dp = DefaultPlatformButtonVerticalPadding, content: @Composable RowScope.() -> Unit, ) { androidx.compose.material3.Button( - modifier = modifier.padding(vertical = verticalPadding).height(36.dp), + modifier = modifier.heightIn(min = 36.dp), colors = colors, contentPadding = ButtonPaddings, onClick = onClick, @@ -63,11 +60,10 @@ fun PlatformOutlinedButton( enabled: Boolean = true, colors: ButtonColors = outlineButtonColors(), border: BorderStroke? = outlineButtonBorder(), - verticalPadding: Dp = DefaultPlatformButtonVerticalPadding, content: @Composable RowScope.() -> Unit, ) { androidx.compose.material3.OutlinedButton( - modifier = modifier.padding(vertical = verticalPadding).height(36.dp), + modifier = modifier.heightIn(min = 36.dp), enabled = enabled, colors = colors, border = border, @@ -118,7 +114,6 @@ fun PlatformIconButton( } } -private val DefaultPlatformButtonVerticalPadding = 6.dp private val ButtonPaddings = PaddingValues(horizontal = 16.dp, vertical = 8.dp) @Composable diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt index f655ac1d207b..d164eab5afeb 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt @@ -95,7 +95,7 @@ import com.android.systemui.bouncer.ui.BouncerDialogFactory import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout import com.android.systemui.bouncer.ui.viewmodel.AuthMethodBouncerViewModel import com.android.systemui.bouncer.ui.viewmodel.BouncerMessageViewModel -import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel +import com.android.systemui.bouncer.ui.viewmodel.BouncerSceneContentViewModel import com.android.systemui.bouncer.ui.viewmodel.MessageViewModel import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel import com.android.systemui.bouncer.ui.viewmodel.PatternBouncerViewModel @@ -114,7 +114,7 @@ import platform.test.motion.compose.values.motionTestValues @Composable fun BouncerContent( - viewModel: BouncerViewModel, + viewModel: BouncerSceneContentViewModel, dialogFactory: BouncerDialogFactory, modifier: Modifier = Modifier, ) { @@ -128,7 +128,7 @@ fun BouncerContent( @VisibleForTesting fun BouncerContent( layout: BouncerSceneLayout, - viewModel: BouncerViewModel, + viewModel: BouncerSceneContentViewModel, dialogFactory: BouncerDialogFactory, modifier: Modifier ) { @@ -173,7 +173,7 @@ fun BouncerContent( */ @Composable private fun StandardLayout( - viewModel: BouncerViewModel, + viewModel: BouncerSceneContentViewModel, modifier: Modifier = Modifier, ) { val isHeightExpanded = @@ -235,7 +235,7 @@ private fun StandardLayout( */ @Composable private fun SplitLayout( - viewModel: BouncerViewModel, + viewModel: BouncerSceneContentViewModel, modifier: Modifier = Modifier, ) { val authMethod by viewModel.authMethodViewModel.collectAsStateWithLifecycle() @@ -326,7 +326,7 @@ private fun SplitLayout( */ @Composable private fun BesideUserSwitcherLayout( - viewModel: BouncerViewModel, + viewModel: BouncerSceneContentViewModel, modifier: Modifier = Modifier, ) { val layoutDirection = LocalLayoutDirection.current @@ -461,7 +461,7 @@ private fun BesideUserSwitcherLayout( /** Arranges the bouncer contents and user switcher contents one on top of the other, vertically. */ @Composable private fun BelowUserSwitcherLayout( - viewModel: BouncerViewModel, + viewModel: BouncerSceneContentViewModel, modifier: Modifier = Modifier, ) { Column( @@ -506,7 +506,7 @@ private fun BelowUserSwitcherLayout( @Composable private fun FoldAware( - viewModel: BouncerViewModel, + viewModel: BouncerSceneContentViewModel, aboveFold: @Composable BoxScope.() -> Unit, belowFold: @Composable BoxScope.() -> Unit, modifier: Modifier = Modifier, @@ -649,7 +649,7 @@ private fun StatusMessage( */ @Composable private fun OutputArea( - viewModel: BouncerViewModel, + viewModel: BouncerSceneContentViewModel, modifier: Modifier = Modifier, ) { val authMethodViewModel: AuthMethodBouncerViewModel? by @@ -677,7 +677,7 @@ private fun OutputArea( */ @Composable private fun InputArea( - viewModel: BouncerViewModel, + viewModel: BouncerSceneContentViewModel, pinButtonRowVerticalSpacing: Dp, centerPatternDotsVertically: Boolean, modifier: Modifier = Modifier, @@ -706,7 +706,7 @@ private fun InputArea( @Composable private fun ActionArea( - viewModel: BouncerViewModel, + viewModel: BouncerSceneContentViewModel, modifier: Modifier = Modifier, ) { val actionButton: BouncerActionButtonModel? by @@ -774,7 +774,7 @@ private fun ActionArea( @Composable private fun Dialog( - bouncerViewModel: BouncerViewModel, + bouncerViewModel: BouncerSceneContentViewModel, dialogFactory: BouncerDialogFactory, ) { val dialogViewModel by bouncerViewModel.dialogViewModel.collectAsStateWithLifecycle() @@ -803,7 +803,7 @@ private fun Dialog( /** Renders the UI of the user switcher that's displayed on large screens next to the bouncer UI. */ @Composable private fun UserSwitcher( - viewModel: BouncerViewModel, + viewModel: BouncerSceneContentViewModel, modifier: Modifier = Modifier, ) { if (!viewModel.isUserSwitcherVisible) { @@ -884,7 +884,7 @@ private fun UserSwitcher( @Composable private fun UserSwitcherDropdownMenu( isExpanded: Boolean, - items: List<BouncerViewModel.UserSwitcherDropdownItemViewModel>, + items: List<BouncerSceneContentViewModel.UserSwitcherDropdownItemViewModel>, onDismissed: () -> Unit, ) { val context = LocalContext.current diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt index 9fd30b499595..3a46882c0ab9 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt @@ -27,9 +27,11 @@ import com.android.compose.animation.scene.SceneScope import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.systemui.bouncer.ui.BouncerDialogFactory -import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel +import com.android.systemui.bouncer.ui.viewmodel.BouncerSceneActionsViewModel +import com.android.systemui.bouncer.ui.viewmodel.BouncerSceneContentViewModel import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.lifecycle.rememberViewModel import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.ui.composable.ComposableScene import javax.inject.Inject @@ -51,23 +53,37 @@ object Bouncer { class BouncerScene @Inject constructor( - private val viewModel: BouncerViewModel, + private val actionsViewModelFactory: BouncerSceneActionsViewModel.Factory, + private val contentViewModelFactory: BouncerSceneContentViewModel.Factory, private val dialogFactory: BouncerDialogFactory, ) : ComposableScene { override val key = Scenes.Bouncer + private val actionsViewModel: BouncerSceneActionsViewModel by lazy { + actionsViewModelFactory.create() + } + override val destinationScenes: Flow<Map<UserAction, UserActionResult>> = - viewModel.destinationScenes + actionsViewModel.actions + + override suspend fun activate() { + actionsViewModel.activate() + } @Composable override fun SceneScope.Content( modifier: Modifier, - ) = BouncerScene(viewModel, dialogFactory, modifier) + ) = + BouncerScene( + viewModel = rememberViewModel { contentViewModelFactory.create() }, + dialogFactory = dialogFactory, + modifier = modifier, + ) } @Composable private fun SceneScope.BouncerScene( - viewModel: BouncerViewModel, + viewModel: BouncerSceneContentViewModel, dialogFactory: BouncerDialogFactory, modifier: Modifier = Modifier, ) { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt index b93b0490816d..7472d818d71a 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 @@ -1410,7 +1410,10 @@ fun AccessibilityContainer(viewModel: BaseCommunalViewModel, content: @Composabl R.string.accessibility_action_label_close_communal_hub ) ) { - viewModel.changeScene(CommunalScenes.Blank) + viewModel.changeScene( + CommunalScenes.Blank, + "closed by accessibility" + ) true }, CustomAccessibilityAction( diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt index eea00c4f2935..fb7c42254caa 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt @@ -29,8 +29,6 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Button -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment @@ -42,6 +40,7 @@ import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.systemui.battery.BatteryMeterViewController import com.android.systemui.brightness.ui.compose.BrightnessSliderContainer +import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.ui.composable.LockscreenContent import com.android.systemui.lifecycle.rememberViewModel @@ -114,7 +113,7 @@ constructor( } @Composable -private fun ShadeBody( +fun ShadeBody( viewModel: QuickSettingsContainerViewModel, ) { val isEditing by viewModel.editModeViewModel.isEditing.collectAsStateWithLifecycle() @@ -131,6 +130,7 @@ private fun ShadeBody( } else { QuickSettingsLayout( viewModel = viewModel, + modifier = Modifier.sysuiResTag("quick_settings_panel") ) } } @@ -158,11 +158,6 @@ private fun QuickSettingsLayout( Modifier.fillMaxWidth().heightIn(max = QuickSettingsShade.Dimensions.GridMaxHeight), viewModel.editModeViewModel::startEditing, ) - Button( - onClick = { viewModel.editModeViewModel.startEditing() }, - ) { - Text("Edit mode") - } } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt index 7920e74eff01..7dc53eaf53ac 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt @@ -56,6 +56,7 @@ import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.pointer.pointerInteropFilter import androidx.compose.ui.layout.Layout import androidx.compose.ui.layout.layoutId +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.colorResource @@ -88,6 +89,8 @@ import com.android.systemui.media.controls.ui.controller.MediaCarouselController import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager import com.android.systemui.media.controls.ui.view.MediaHost import com.android.systemui.media.controls.ui.view.MediaHostState +import com.android.systemui.media.controls.ui.view.MediaHostState.Companion.COLLAPSED +import com.android.systemui.media.controls.ui.view.MediaHostState.Companion.EXPANDED import com.android.systemui.media.dagger.MediaModule.QS_PANEL import com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL import com.android.systemui.notifications.ui.composable.NotificationScrollingStack @@ -110,6 +113,7 @@ import com.android.systemui.statusbar.notification.stack.ui.viewmodel.Notificati import com.android.systemui.statusbar.phone.StatusBarLocation import com.android.systemui.statusbar.phone.ui.StatusBarIconController import com.android.systemui.statusbar.phone.ui.TintedIconManager +import com.android.systemui.util.Utils import dagger.Lazy import javax.inject.Inject import javax.inject.Named @@ -260,6 +264,11 @@ private fun SceneScope.SingleShade( shadeSession: SaveableSession, ) { val cutoutLocation = LocalDisplayCutout.current.location + val isLandscape = LocalWindowSizeClass.current.heightSizeClass == WindowHeightSizeClass.Compact + val usingCollapsedLandscapeMedia = + Utils.useCollapsedMediaInLandscape(LocalContext.current.resources) + val isExpanded = !usingCollapsedLandscapeMedia || !isLandscape + mediaHost.expansion = if (isExpanded) EXPANDED else COLLAPSED val maxNotifScrimTop = remember { mutableStateOf(0f) } val tileSquishiness by @@ -275,9 +284,7 @@ private fun SceneScope.SingleShade( layoutState.isTransitioningBetween(Scenes.Gone, Scenes.Shade) || layoutState.isTransitioningBetween(Scenes.Lockscreen, Scenes.Shade) // Media is visible and we are in landscape on a small height screen - val mediaInRow = - isMediaVisible && - LocalWindowSizeClass.current.heightSizeClass == WindowHeightSizeClass.Compact + val mediaInRow = isMediaVisible && isLandscape val mediaOffset by animateSceneDpAsState(value = InQQS, key = MediaLandscapeTopOffset, canOverflow = false) diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt index 68a6c9836875..920c234174d0 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt @@ -17,7 +17,6 @@ package com.android.compose.animation.scene import com.android.compose.animation.scene.content.state.TransitionState -import kotlin.math.absoluteValue import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job @@ -63,47 +62,30 @@ internal fun CoroutineScope.animateToScene( if (transitionState.toScene == target) { // The user is currently swiping to [target] but didn't release their pointer yet: // animate the progress to `1`. - check(transitionState.fromScene == transitionState.currentScene) - val progress = transitionState.progress - if ((1f - progress).absoluteValue < ProgressVisibilityThreshold) { - // The transition is already finished (progress ~= 1): no need to animate. We - // finish the current transition early to make sure that the current state - // change is committed. - layoutState.finishTransition(transitionState, target) - null - } else { - // The transition is in progress: start the canned animation at the same - // progress as it was in. - animateToScene( - layoutState, - target, - transitionKey, - isInitiatedByUserInput, - replacedTransition = transitionState, - ) - } + + // The transition is in progress: start the canned animation at the same + // progress as it was in. + animateToScene( + layoutState, + target, + transitionKey, + isInitiatedByUserInput, + replacedTransition = transitionState, + ) } else if (transitionState.fromScene == target) { // There is a transition from [target] to another scene: simply animate the same // transition progress to `0`. check(transitionState.toScene == transitionState.currentScene) - val progress = transitionState.progress - if (progress.absoluteValue < ProgressVisibilityThreshold) { - // The transition is at progress ~= 0: no need to animate.We finish the current - // transition early to make sure that the current state change is committed. - layoutState.finishTransition(transitionState, target) - null - } else { - animateToScene( - layoutState, - target, - transitionKey, - isInitiatedByUserInput, - reversed = true, - replacedTransition = transitionState, - ) - } + animateToScene( + layoutState, + target, + transitionKey, + isInitiatedByUserInput, + reversed = true, + replacedTransition = transitionState, + ) } else { // Generic interruption; the current transition is neither from or to [target]. val interruptionResult = @@ -185,7 +167,7 @@ private fun CoroutineScope.animateToScene( oneOffAnimation = oneOffAnimation, targetProgress = targetProgress, startTransition = { layoutState.startTransition(transition, chain) }, - finishTransition = { layoutState.finishTransition(transition, targetScene) }, + finishTransition = { layoutState.finishTransition(transition) }, ) return transition 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 712fe6b2ff50..9c3896b90124 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 @@ -777,7 +777,8 @@ private class SwipeTransition( fun snapToScene(scene: SceneKey) { cancelOffsetAnimation() - layoutState.finishTransition(this, idleScene = scene) + check(currentScene == scene) + layoutState.finishTransition(this) } override fun finish(): Job { diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt index b329534e6e3a..3487730945da 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt @@ -81,6 +81,7 @@ internal fun Modifier.multiPointerDraggable( enabled: () -> Boolean, startDragImmediately: (startedPosition: Offset) -> Boolean, onDragStarted: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController, + onFirstPointerDown: () -> Unit = {}, swipeDetector: SwipeDetector = DefaultSwipeDetector, dispatcher: NestedScrollDispatcher, ): Modifier = @@ -90,6 +91,7 @@ internal fun Modifier.multiPointerDraggable( enabled, startDragImmediately, onDragStarted, + onFirstPointerDown, swipeDetector, dispatcher, ) @@ -101,6 +103,7 @@ private data class MultiPointerDraggableElement( private val startDragImmediately: (startedPosition: Offset) -> Boolean, private val onDragStarted: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController, + private val onFirstPointerDown: () -> Unit, private val swipeDetector: SwipeDetector, private val dispatcher: NestedScrollDispatcher, ) : ModifierNodeElement<MultiPointerDraggableNode>() { @@ -110,6 +113,7 @@ private data class MultiPointerDraggableElement( enabled = enabled, startDragImmediately = startDragImmediately, onDragStarted = onDragStarted, + onFirstPointerDown = onFirstPointerDown, swipeDetector = swipeDetector, dispatcher = dispatcher, ) @@ -119,6 +123,7 @@ private data class MultiPointerDraggableElement( node.enabled = enabled node.startDragImmediately = startDragImmediately node.onDragStarted = onDragStarted + node.onFirstPointerDown = onFirstPointerDown node.swipeDetector = swipeDetector } } @@ -129,6 +134,7 @@ internal class MultiPointerDraggableNode( var startDragImmediately: (startedPosition: Offset) -> Boolean, var onDragStarted: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController, + var onFirstPointerDown: () -> Unit, var swipeDetector: SwipeDetector = DefaultSwipeDetector, private val dispatcher: NestedScrollDispatcher, ) : @@ -225,6 +231,7 @@ internal class MultiPointerDraggableNode( startedPosition = null } else if (startedPosition == null) { startedPosition = pointers.first().position + onFirstPointerDown() } } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt index fe16ef75118b..2fbdf7c1a501 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt @@ -129,7 +129,7 @@ private class PredictiveBackTransition( try { animatable.animateTo(targetProgress) } finally { - state.finishTransition(this@PredictiveBackTransition, scene) + state.finishTransition(this@PredictiveBackTransition) } } .also { animationJob = it } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt index a6c6a80941fc..44f5964feacb 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt @@ -167,8 +167,6 @@ internal class MutableSceneTransitionLayoutStateImpl( override val transitionState: TransitionState get() = transitionStates.last() - private val activeTransitionLinks = mutableMapOf<StateLink, LinkedTransition>() - override val currentTransitions: List<TransitionState.Transition> get() { if (transitionStates.last() is TransitionState.Idle) { @@ -180,12 +178,8 @@ internal class MutableSceneTransitionLayoutStateImpl( } } - /** - * The mapping of transitions that are finished, i.e. for which [finishTransition] was called, - * to their idle scene. - */ - @VisibleForTesting - internal val finishedTransitions = mutableMapOf<TransitionState.Transition, SceneKey>() + /** The transitions that are finished, i.e. for which [finishTransition] was called. */ + @VisibleForTesting internal val finishedTransitions = mutableSetOf<TransitionState.Transition>() internal fun checkThread() { val current = Thread.currentThread() @@ -261,7 +255,7 @@ internal class MutableSceneTransitionLayoutStateImpl( } // Handle transition links. - cancelActiveTransitionLinks() + currentTransition?.let { cancelActiveTransitionLinks(it) } setupTransitionLinks(transition) if (!enableInterruptions) { @@ -289,8 +283,7 @@ internal class MutableSceneTransitionLayoutStateImpl( // Force finish all transitions. while (currentTransitions.isNotEmpty()) { - val transition = transitionStates[0] as TransitionState.Transition - finishTransition(transition, transition.currentScene) + finishTransition(transitionStates[0] as TransitionState.Transition) } // We finished all transitions, so we are now idle. We remove this state so that @@ -328,18 +321,17 @@ internal class MutableSceneTransitionLayoutStateImpl( ) } - private fun cancelActiveTransitionLinks() { - for ((link, linkedTransition) in activeTransitionLinks) { - link.target.finishTransition(linkedTransition, linkedTransition.currentScene) + private fun cancelActiveTransitionLinks(transition: TransitionState.Transition) { + transition.activeTransitionLinks.forEach { (link, linkedTransition) -> + link.target.finishTransition(linkedTransition) } - activeTransitionLinks.clear() + transition.activeTransitionLinks.clear() } - private fun setupTransitionLinks(transitionState: TransitionState) { - if (transitionState !is TransitionState.Transition) return + private fun setupTransitionLinks(transition: TransitionState.Transition) { stateLinks.fastForEach { stateLink -> val matchingLinks = - stateLink.transitionLinks.fastFilter { it.isMatchingLink(transitionState) } + stateLink.transitionLinks.fastFilter { it.isMatchingLink(transition) } if (matchingLinks.isEmpty()) return@fastForEach if (matchingLinks.size > 1) error("More than one link matched.") @@ -350,31 +342,27 @@ internal class MutableSceneTransitionLayoutStateImpl( val linkedTransition = LinkedTransition( - originalTransition = transitionState, + originalTransition = transition, fromScene = targetCurrentScene, toScene = matchingLink.targetTo, key = matchingLink.targetTransitionKey, ) stateLink.target.startTransition(linkedTransition) - activeTransitionLinks[stateLink] = linkedTransition + transition.activeTransitionLinks[stateLink] = linkedTransition } } /** - * Notify that [transition] was finished and that we should settle to [idleScene]. This will do - * nothing if [transition] was interrupted since it was started. + * Notify that [transition] was finished and that it settled to its + * [currentScene][TransitionState.currentScene]. This will do nothing if [transition] was + * interrupted since it was started. */ - internal fun finishTransition(transition: TransitionState.Transition, idleScene: SceneKey) { + internal fun finishTransition(transition: TransitionState.Transition) { checkThread() - val existingIdleScene = finishedTransitions[transition] - if (existingIdleScene != null) { + if (finishedTransitions.contains(transition)) { // This transition was already finished. - check(idleScene == existingIdleScene) { - "Transition $transition was finished multiple times with different " + - "idleScene ($existingIdleScene != $idleScene)" - } return } @@ -386,15 +374,15 @@ internal class MutableSceneTransitionLayoutStateImpl( check(transitionStates.fastAll { it is TransitionState.Transition }) - // Mark this transition as finished and save the scene it is settling at. - finishedTransitions[transition] = idleScene + // Mark this transition as finished. + finishedTransitions.add(transition) // Finish all linked transitions. - finishActiveTransitionLinks(idleScene) + finishActiveTransitionLinks(transition) - // Keep a reference to the idle scene of the last removed transition, in case we remove all - // transitions and should settle to Idle. - var lastRemovedIdleScene: SceneKey? = null + // Keep a reference to the last transition, in case we remove all transitions and should + // settle to Idle. + val lastTransition = transitionStates.last() // Remove all first n finished transitions. var i = 0 @@ -407,14 +395,14 @@ internal class MutableSceneTransitionLayoutStateImpl( } // Remove the transition from the set of finished transitions. - lastRemovedIdleScene = finishedTransitions.remove(t) + finishedTransitions.remove(t) i++ } // If all transitions are finished, we are idle. if (i == nStates) { check(finishedTransitions.isEmpty()) - this.transitionStates = listOf(TransitionState.Idle(checkNotNull(lastRemovedIdleScene))) + this.transitionStates = listOf(TransitionState.Idle(lastTransition.currentScene)) } else if (i > 0) { this.transitionStates = transitionStates.subList(fromIndex = i, toIndex = nStates) } @@ -426,28 +414,18 @@ internal class MutableSceneTransitionLayoutStateImpl( // Force finish all transitions. while (currentTransitions.isNotEmpty()) { val transition = transitionStates[0] as TransitionState.Transition - finishTransition(transition, transition.currentScene) + finishTransition(transition) } check(transitionStates.size == 1) transitionStates = listOf(TransitionState.Idle(scene)) } - private fun finishActiveTransitionLinks(idleScene: SceneKey) { - val previousTransition = this.transitionState as? TransitionState.Transition ?: return - for ((link, linkedTransition) in activeTransitionLinks) { - if (previousTransition.fromScene == idleScene) { - // The transition ended by arriving at the fromScene, move link to Idle(fromScene). - link.target.finishTransition(linkedTransition, linkedTransition.fromScene) - } else if (previousTransition.toScene == idleScene) { - // The transition ended by arriving at the toScene, move link to Idle(toScene). - link.target.finishTransition(linkedTransition, linkedTransition.toScene) - } else { - // The transition was interrupted by something else, we reset to initial state. - link.target.finishTransition(linkedTransition, linkedTransition.fromScene) - } + private fun finishActiveTransitionLinks(transition: TransitionState.Transition) { + for ((link, linkedTransition) in transition.activeTransitionLinks) { + link.target.finishTransition(linkedTransition) } - activeTransitionLinks.clear() + transition.activeTransitionLinks.clear() } /** @@ -465,31 +443,21 @@ internal class MutableSceneTransitionLayoutStateImpl( fun isProgressCloseTo(value: Float) = (progress - value).absoluteValue <= threshold - fun finishAllTransitions(lastTransitionIdleScene: SceneKey) { + fun finishAllTransitions() { // Force finish all transitions. while (currentTransitions.isNotEmpty()) { - val transition = transitionStates[0] as TransitionState.Transition - val idleScene = - if (transitionStates.size == 1) { - lastTransitionIdleScene - } else { - transition.currentScene - } - - finishTransition(transition, idleScene) + finishTransition(transitionStates[0] as TransitionState.Transition) } } - return when { - isProgressCloseTo(0f) -> { - finishAllTransitions(transition.fromScene) - true - } - isProgressCloseTo(1f) -> { - finishAllTransitions(transition.toScene) - true - } - else -> false + val shouldSnap = + (isProgressCloseTo(0f) && transition.currentScene == transition.fromScene) || + (isProgressCloseTo(1f) && transition.currentScene == transition.toScene) + return if (shouldSnap) { + finishAllTransitions() + true + } else { + false } } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt index f06214645144..d1e83bacf40a 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt @@ -67,6 +67,7 @@ private class SwipeToSceneNode( enabled = ::enabled, startDragImmediately = ::startDragImmediately, onDragStarted = draggableHandler::onDragStarted, + onFirstPointerDown = ::onFirstPointerDown, swipeDetector = swipeDetector, dispatcher = dispatcher, ) @@ -101,6 +102,15 @@ private class SwipeToSceneNode( delegate(ScrollBehaviorOwnerNode(draggableHandler.nestedScrollKey, nestedScrollHandlerImpl)) } + private fun onFirstPointerDown() { + // When we drag our finger across the screen, the NestedScrollConnection keeps track of all + // the scroll events until we lift our finger. However, in some cases, the connection might + // not receive the "up" event. This can lead to an incorrect initial state for the gesture. + // To prevent this issue, we can call the reset() method when the first finger touches the + // screen. This ensures that the NestedScrollConnection starts from a correct state. + nestedScrollHandlerImpl.connection.reset() + } + override fun onDetach() { // Make sure we reset the scroll connection when this modifier is removed from composition nestedScrollHandlerImpl.connection.reset() diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/ContentState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/ContentState.kt index add393441794..0bd676bb9858 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/ContentState.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/ContentState.kt @@ -29,6 +29,8 @@ import com.android.compose.animation.scene.SceneTransitionLayoutImpl import com.android.compose.animation.scene.TransformationSpec import com.android.compose.animation.scene.TransformationSpecImpl import com.android.compose.animation.scene.TransitionKey +import com.android.compose.animation.scene.transition.link.LinkedTransition +import com.android.compose.animation.scene.transition.link.StateLink import kotlinx.coroutines.Job import kotlinx.coroutines.launch @@ -110,6 +112,9 @@ sealed interface ContentState<out T : ContentKey> { */ private var interruptionDecay: Animatable<Float, AnimationVector1D>? = null + /** The map of active links that connects this transition to other transitions. */ + internal val activeTransitionLinks = mutableMapOf<StateLink, LinkedTransition>() + init { check(fromContent != toContent) check( @@ -131,7 +136,7 @@ sealed interface ContentState<out T : ContentKey> { * animation is complete or cancel it to snap the animation. Calling [finish] multiple * times will return the same [Job]. */ - abstract fun finish(): Job + internal abstract fun finish(): Job /** * Whether we are transitioning. If [from] or [to] is empty, we will also check that they diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt index 228f7ba48d3e..16fb533bbe06 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt @@ -129,7 +129,11 @@ class PriorityNestedScrollConnection( return onPriorityStop(available) } - /** Method to call before destroying the object or to reset the initial state. */ + /** + * Method to call before destroying the object or to reset the initial state. + * + * TODO(b/303224944) This method should be removed. + */ fun reset() { // Step 3c: To ensure that an onStop is always called for every onStart. onPriorityStop(velocity = Velocity.Zero) diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt index 75f44ff9cfe0..60cefb0c0ac4 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt @@ -1380,8 +1380,8 @@ class ElementTest { // Manually finish the transition. rule.runOnUiThread { - state.finishTransition(aToB, SceneB) - state.finishTransition(bToC, SceneC) + state.finishTransition(aToB) + state.finishTransition(bToC) } rule.waitForIdle() assertThat(state.transitionState).isIdle() @@ -1482,7 +1482,7 @@ class ElementTest { // Manually finish A => B so only B => C is remaining. bToCInterruptionProgress = 0f - rule.runOnUiThread { state.finishTransition(aToB, SceneB) } + rule.runOnUiThread { state.finishTransition(aToB) } rule .onNode(isElement(TestElements.Foo, SceneB)) .assertPositionInRootIsEqualTo(offsetInB.x, offsetInB.y) @@ -2002,6 +2002,7 @@ class ElementTest { transition( from = SceneB, to = SceneC, + current = { SceneB }, progress = { 0f }, interruptionProgress = { interruptionProgress }, onFinish = neverFinish(), @@ -2018,8 +2019,8 @@ class ElementTest { // 100%. We should be at (20dp, 20dp), unless the interruption deltas have not been // correctly cleaned. rule.runOnUiThread { - state.finishTransition(aToB, idleScene = SceneB) - state.finishTransition(bToC, idleScene = SceneB) + state.finishTransition(aToB) + state.finishTransition(bToC) state.startTransition( transition( from = SceneB, diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/NestedScrollToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/NestedScrollToSceneTest.kt index 9ebc42650d45..d8a06f54e74b 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/NestedScrollToSceneTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/NestedScrollToSceneTest.kt @@ -23,6 +23,9 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.platform.LocalViewConfiguration @@ -266,4 +269,38 @@ class NestedScrollToSceneTest { val transition = assertThat(state.transitionState).isTransition() assertThat(transition).hasProgress(0.5f) } + + @Test + fun resetScrollTracking_afterMissingPointerUpEvent() { + var canScroll = true + var hasScrollable by mutableStateOf(true) + val state = setup2ScenesAndScrollTouchSlop { + if (hasScrollable) { + Modifier.scrollable(rememberScrollableState { if (canScroll) it else 0f }, Vertical) + } else { + Modifier + } + } + + // The gesture is consumed by the component in the scene. + scrollUp(percent = 0.2f) + + // STL keeps track of the scroll consumed. The scene remains in Idle. + assertThat(state.transitionState).isIdle() + + // The scrollable component disappears, and does not send the signal (pointer up) to reset + // the consumed amount. + hasScrollable = false + pointerUp() + + // A new scrollable component appears and allows the scene to consume the scroll. + hasScrollable = true + canScroll = false + pointerDownAndScrollTouchSlop() + scrollUp(percent = 0.2f) + + // STL can only start the transition if it has reset the amount of scroll consumed. + val transition = assertThat(state.transitionState).isTransition() + assertThat(transition).hasProgress(0.2f) + } } diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt index 6b417ee5a468..c8ac580bf5ba 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt @@ -18,7 +18,9 @@ package com.android.compose.animation.scene import android.util.Log import androidx.compose.foundation.gestures.Orientation +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue import androidx.compose.ui.test.junit4.createComposeRule import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.compose.animation.scene.TestScenes.SceneA @@ -110,16 +112,6 @@ class SceneTransitionLayoutStateTest { } @Test - fun setTargetScene_transitionToOriginalScene() = runMonotonicClockTest { - val state = MutableSceneTransitionLayoutState(SceneA) - assertThat(state.setTargetScene(SceneB, coroutineScope = this)).isNotNull() - - // Progress is 0f, so we don't animate at all and directly snap back to A. - assertThat(state.setTargetScene(SceneA, coroutineScope = this)).isNull() - assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneA)) - } - - @Test fun setTargetScene_coroutineScopeCancelled() = runMonotonicClockTest { val state = MutableSceneTransitionLayoutState(SceneA) @@ -180,7 +172,7 @@ class SceneTransitionLayoutStateTest { assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue() assertThat(parentState.isTransitioning(SceneC, SceneD)).isTrue() - childState.finishTransition(childTransition, SceneB) + childState.finishTransition(childTransition) assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB)) assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneD)) } @@ -217,7 +209,7 @@ class SceneTransitionLayoutStateTest { assertThat(parentState.isTransitioning(SceneC, SceneD)).isTrue() assertThat(parentParentState.isTransitioning(SceneB, SceneC)).isTrue() - childState.finishTransition(childTransition, SceneB) + childState.finishTransition(childTransition) assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB)) assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneD)) assertThat(parentParentState.transitionState).isEqualTo(TransitionState.Idle(SceneC)) @@ -241,13 +233,13 @@ class SceneTransitionLayoutStateTest { fun linkedTransition_reverseTransitionIsNotLinked() { val (parentState, childState) = setupLinkedStates() - val childTransition = transition(SceneB, SceneA) + val childTransition = transition(SceneB, SceneA, current = { SceneB }) childState.startTransition(childTransition) assertThat(childState.isTransitioning(SceneB, SceneA)).isTrue() assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC)) - childState.finishTransition(childTransition, SceneB) + childState.finishTransition(childTransition) assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB)) assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC)) } @@ -256,27 +248,15 @@ class SceneTransitionLayoutStateTest { fun linkedTransition_startsLinkAndFinishesLinkInFromState() { val (parentState, childState) = setupLinkedStates() - val childTransition = transition(SceneA, SceneB) + val childTransition = transition(SceneA, SceneB, current = { SceneA }) childState.startTransition(childTransition) - childState.finishTransition(childTransition, SceneA) + childState.finishTransition(childTransition) assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneA)) assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC)) } @Test - fun linkedTransition_startsLinkAndFinishesLinkInUnknownState() { - val (parentState, childState) = setupLinkedStates() - - val childTransition = transition(SceneA, SceneB) - childState.startTransition(childTransition) - - childState.finishTransition(childTransition, SceneD) - assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneD)) - assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC)) - } - - @Test fun linkedTransition_startsLinkButLinkedStateIsTakenOver() = runTest { val (parentState, childState) = setupLinkedStates() @@ -295,7 +275,7 @@ class SceneTransitionLayoutStateTest { childState.startTransition(childTransition) parentState.startTransition(parentTransition) - childState.finishTransition(childTransition, SceneB) + childState.finishTransition(childTransition) assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB)) assertThat(parentState.transitionState).isEqualTo(parentTransition) } @@ -341,7 +321,9 @@ class SceneTransitionLayoutStateTest { @Test fun snapToIdleIfClose_snapToStart() = runMonotonicClockTest { val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty) - state.startTransition(transition(from = SceneA, to = SceneB, progress = { 0.2f })) + state.startTransition( + transition(from = SceneA, to = SceneB, current = { SceneA }, progress = { 0.2f }) + ) assertThat(state.isTransitioning()).isTrue() // Ignore the request if the progress is not close to 0 or 1, using the threshold. @@ -399,6 +381,31 @@ class SceneTransitionLayoutStateTest { } @Test + fun snapToIdleIfClose_closeButNotCurrentScene() = runMonotonicClockTest { + val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty) + var progress by mutableStateOf(0f) + var currentScene by mutableStateOf(SceneB) + state.startTransition( + transition( + from = SceneA, + to = SceneB, + current = { currentScene }, + progress = { progress } + ) + ) + assertThat(state.isTransitioning()).isTrue() + + // Ignore the request if we are close to a scene that is not the current scene + assertThat(state.snapToIdleIfClose(threshold = 0.1f)).isFalse() + assertThat(state.isTransitioning()).isTrue() + + progress = 1f + currentScene = SceneA + assertThat(state.snapToIdleIfClose(threshold = 0.1f)).isFalse() + assertThat(state.isTransitioning()).isTrue() + } + + @Test fun linkedTransition_fuzzyLinksAreMatchedAndStarted() { val (parentState, childState) = setupLinkedStates(SceneC, SceneA, null, null, null, SceneD) val childTransition = transition(SceneA, SceneB) @@ -407,7 +414,7 @@ class SceneTransitionLayoutStateTest { assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue() assertThat(parentState.isTransitioning(SceneC, SceneD)).isTrue() - childState.finishTransition(childTransition, SceneB) + childState.finishTransition(childTransition) assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB)) assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneD)) } @@ -417,13 +424,13 @@ class SceneTransitionLayoutStateTest { val (parentState, childState) = setupLinkedStates(SceneC, SceneA, SceneA, null, null, SceneD) - val childTransition = transition(SceneA, SceneB) + val childTransition = transition(SceneA, SceneB, current = { SceneA }) childState.startTransition(childTransition) assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue() assertThat(parentState.isTransitioning(SceneC, SceneD)).isTrue() - childState.finishTransition(childTransition, SceneA) + childState.finishTransition(childTransition) assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneA)) assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC)) } @@ -595,12 +602,12 @@ class SceneTransitionLayoutStateTest { // Mark bToC as finished. The list of current transitions does not change because aToB is // still not marked as finished. - state.finishTransition(bToC, idleScene = bToC.currentScene) - assertThat(state.finishedTransitions).containsExactly(bToC, bToC.currentScene) + state.finishTransition(bToC) + assertThat(state.finishedTransitions).containsExactly(bToC) assertThat(state.currentTransitions).containsExactly(aToB, bToC, cToA).inOrder() // Mark aToB as finished. This will remove both aToB and bToC from the list of transitions. - state.finishTransition(aToB, idleScene = aToB.currentScene) + state.finishTransition(aToB) assertThat(state.finishedTransitions).isEmpty() assertThat(state.currentTransitions).containsExactly(cToA).inOrder() } diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/Transition.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/Transition.kt index 91bd7e1800cd..e4e410828d0a 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/Transition.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/Transition.kt @@ -29,7 +29,7 @@ import kotlinx.coroutines.test.TestScope fun transition( from: SceneKey, to: SceneKey, - current: () -> SceneKey = { from }, + current: () -> SceneKey = { to }, progress: () -> Float = { 0f }, progressVelocity: () -> Float = { 0f }, previewProgress: () -> Float = { 0f }, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt index c9fa671ad34f..deef65218c4b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt @@ -22,14 +22,14 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository import com.android.systemui.authentication.shared.model.AuthenticationMethodModel -import com.android.systemui.bouncer.domain.interactor.bouncerInteractor -import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testScope +import com.android.systemui.lifecycle.activateIn import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.runTest +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -39,17 +39,16 @@ class AuthMethodBouncerViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope - private val bouncerInteractor by lazy { kosmos.bouncerInteractor } - private val underTest by lazy { - PinBouncerViewModel( - applicationContext = context, - viewModelScope = testScope.backgroundScope, - interactor = bouncerInteractor, + private val underTest = + kosmos.pinBouncerViewModelFactory.create( isInputEnabled = MutableStateFlow(true), - simBouncerInteractor = kosmos.simBouncerInteractor, - authenticationMethod = AuthenticationMethodModel.Pin, onIntentionalUserInput = {}, + authenticationMethod = AuthenticationMethodModel.Pin, ) + + @Before + fun setUp() { + underTest.activateIn(testScope) } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt index 4f5d0e58ce01..b83ab7ef0c1b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt @@ -52,6 +52,7 @@ import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthentication import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus import com.android.systemui.kosmos.testScope +import com.android.systemui.lifecycle.activateIn import com.android.systemui.res.R import com.android.systemui.testKosmos import com.android.systemui.user.data.repository.fakeUserRepository @@ -87,6 +88,7 @@ class BouncerMessageViewModelTest : SysuiTestCase() { intArrayOf(ignoreHelpMessageId) ) underTest = kosmos.bouncerMessageViewModel + underTest.activateIn(testScope) overrideResource(R.string.kg_trust_agent_disabled, "Trust agent is unavailable") kosmos.fakeSystemPropertiesHelper.set( DeviceUnlockedInteractor.SYS_BOOT_REASON_PROP, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneActionsViewModelTest.kt new file mode 100644 index 000000000000..a86a0c022c21 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneActionsViewModelTest.kt @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.bouncer.ui.viewmodel + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.Back +import com.android.compose.animation.scene.Swipe +import com.android.compose.animation.scene.SwipeDirection +import com.android.compose.animation.scene.UserActionResult +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.EnableSceneContainer +import com.android.systemui.kosmos.testScope +import com.android.systemui.lifecycle.activateIn +import com.android.systemui.scene.domain.startable.sceneContainerStartable +import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.scene.shared.model.fakeSceneDataSource +import com.android.systemui.testKosmos +import com.android.systemui.truth.containsEntriesExactly +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +@EnableSceneContainer +class BouncerSceneActionsViewModelTest : SysuiTestCase() { + + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + + private lateinit var underTest: BouncerSceneActionsViewModel + + @Before + fun setUp() { + kosmos.sceneContainerStartable.start() + underTest = kosmos.bouncerSceneActionsViewModel + underTest.activateIn(testScope) + } + + @Test + fun actions() = + testScope.runTest { + val actions by collectLastValue(underTest.actions) + kosmos.fakeSceneDataSource.changeScene(Scenes.QuickSettings) + runCurrent() + + kosmos.fakeSceneDataSource.changeScene(Scenes.Bouncer) + runCurrent() + + assertThat(actions) + .containsEntriesExactly( + Back to UserActionResult(Scenes.QuickSettings), + Swipe(SwipeDirection.Down) to UserActionResult(Scenes.QuickSettings), + ) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModelTest.kt index ccddc9c7120f..9bddcd254556 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModelTest.kt @@ -18,10 +18,6 @@ package com.android.systemui.bouncer.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.compose.animation.scene.Back -import com.android.compose.animation.scene.Swipe -import com.android.compose.animation.scene.SwipeDirection -import com.android.compose.animation.scene.UserActionResult import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository @@ -38,11 +34,9 @@ import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.flags.Flags import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.kosmos.testScope +import com.android.systemui.lifecycle.activateIn import com.android.systemui.scene.domain.startable.sceneContainerStartable -import com.android.systemui.scene.shared.model.Scenes -import com.android.systemui.scene.shared.model.fakeSceneDataSource import com.android.systemui.testKosmos -import com.android.systemui.truth.containsEntriesExactly import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -62,17 +56,18 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) @EnableSceneContainer -class BouncerViewModelTest : SysuiTestCase() { +class BouncerSceneContentViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope - private lateinit var underTest: BouncerViewModel + private lateinit var underTest: BouncerSceneContentViewModel @Before fun setUp() { kosmos.sceneContainerStartable.start() - underTest = kosmos.bouncerViewModel + underTest = kosmos.bouncerSceneContentViewModel + underTest.activateIn(testScope) } @Test @@ -201,23 +196,6 @@ class BouncerViewModelTest : SysuiTestCase() { assertThat(isFoldSplitRequired).isTrue() } - @Test - fun destinationScenes() = - testScope.runTest { - val destinationScenes by collectLastValue(underTest.destinationScenes) - kosmos.fakeSceneDataSource.changeScene(Scenes.QuickSettings) - runCurrent() - - kosmos.fakeSceneDataSource.changeScene(Scenes.Bouncer) - runCurrent() - - assertThat(destinationScenes) - .containsEntriesExactly( - Back to UserActionResult(Scenes.QuickSettings), - Swipe(SwipeDirection.Down) to UserActionResult(Scenes.QuickSettings), - ) - } - private fun authMethodsToTest(): List<AuthenticationMethodModel> { return listOf(None, Pin, Password, Pattern, Sim) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt index a09189efa41b..492543f215b7 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt @@ -31,6 +31,7 @@ import com.android.systemui.inputmethod.data.model.InputMethodModel import com.android.systemui.inputmethod.data.repository.fakeInputMethodRepository import com.android.systemui.inputmethod.domain.interactor.inputMethodInteractor import com.android.systemui.kosmos.testScope +import com.android.systemui.lifecycle.activateIn import com.android.systemui.res.R import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.Scenes @@ -44,7 +45,6 @@ import java.util.UUID import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.advanceTimeBy import kotlinx.coroutines.test.runCurrent @@ -68,12 +68,8 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { private val isInputEnabled = MutableStateFlow(true) private val underTest = - PasswordBouncerViewModel( - viewModelScope = testScope.backgroundScope, - isInputEnabled = isInputEnabled.asStateFlow(), - interactor = bouncerInteractor, - inputMethodInteractor = inputMethodInteractor, - selectedUserInteractor = selectedUserInteractor, + kosmos.passwordBouncerViewModelFactory.create( + isInputEnabled = isInputEnabled, onIntentionalUserInput = {}, ) @@ -81,6 +77,7 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { fun setUp() { overrideResource(R.string.keyguard_enter_your_password, ENTER_YOUR_PASSWORD) overrideResource(R.string.kg_wrong_password, WRONG_PASSWORD) + underTest.activateIn(testScope) } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt index 14d36343041d..7c773a902367 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt @@ -26,9 +26,9 @@ import com.android.systemui.authentication.data.repository.fakeAuthenticationRep import com.android.systemui.authentication.domain.interactor.authenticationInteractor import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate as Point -import com.android.systemui.bouncer.domain.interactor.bouncerInteractor import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testScope +import com.android.systemui.lifecycle.activateIn import com.android.systemui.res.R import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.Scenes @@ -54,17 +54,12 @@ class PatternBouncerViewModelTest : SysuiTestCase() { private val testScope = kosmos.testScope private val authenticationInteractor by lazy { kosmos.authenticationInteractor } private val sceneInteractor by lazy { kosmos.sceneInteractor } - private val bouncerInteractor by lazy { kosmos.bouncerInteractor } - private val bouncerViewModel by lazy { kosmos.bouncerViewModel } - private val underTest by lazy { - PatternBouncerViewModel( - applicationContext = context, - viewModelScope = testScope.backgroundScope, - interactor = bouncerInteractor, + private val bouncerViewModel by lazy { kosmos.bouncerSceneContentViewModel } + private val underTest = + kosmos.patternBouncerViewModelFactory.create( isInputEnabled = MutableStateFlow(true).asStateFlow(), onIntentionalUserInput = {}, ) - } private val containerSize = 90 // px private val dotSize = 30 // px @@ -73,6 +68,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { fun setUp() { overrideResource(R.string.keyguard_enter_your_pattern, ENTER_YOUR_PATTERN) overrideResource(R.string.kg_wrong_pattern, WRONG_PATTERN) + underTest.activateIn(testScope) } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt index 89bafb952211..8d82e972bdaa 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt @@ -31,10 +31,9 @@ import com.android.systemui.authentication.data.repository.fakeAuthenticationRep import com.android.systemui.authentication.domain.interactor.authenticationInteractor import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.bouncer.data.repository.fakeSimBouncerRepository -import com.android.systemui.bouncer.domain.interactor.bouncerInteractor -import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testScope +import com.android.systemui.lifecycle.activateIn import com.android.systemui.res.R import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.Scenes @@ -44,7 +43,6 @@ import kotlin.random.Random import kotlin.random.nextInt import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.map import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent @@ -62,24 +60,18 @@ class PinBouncerViewModelTest : SysuiTestCase() { private val testScope = kosmos.testScope private val sceneInteractor by lazy { kosmos.sceneInteractor } private val authenticationInteractor by lazy { kosmos.authenticationInteractor } - private val bouncerInteractor by lazy { kosmos.bouncerInteractor } - private lateinit var underTest: PinBouncerViewModel + private val underTest = + kosmos.pinBouncerViewModelFactory.create( + isInputEnabled = MutableStateFlow(true), + onIntentionalUserInput = {}, + authenticationMethod = AuthenticationMethodModel.Pin, + ) @Before fun setUp() { - underTest = - PinBouncerViewModel( - applicationContext = context, - viewModelScope = testScope.backgroundScope, - interactor = bouncerInteractor, - isInputEnabled = MutableStateFlow(true).asStateFlow(), - simBouncerInteractor = kosmos.simBouncerInteractor, - authenticationMethod = AuthenticationMethodModel.Pin, - onIntentionalUserInput = {}, - ) - overrideResource(R.string.keyguard_enter_your_pin, ENTER_YOUR_PIN) overrideResource(R.string.kg_wrong_pin, WRONG_PIN) + underTest.activateIn(testScope) } @Test @@ -96,14 +88,10 @@ class PinBouncerViewModelTest : SysuiTestCase() { fun simBouncerViewModel_simAreaIsVisible() = testScope.runTest { val underTest = - PinBouncerViewModel( - applicationContext = context, - viewModelScope = testScope.backgroundScope, - interactor = bouncerInteractor, - isInputEnabled = MutableStateFlow(true).asStateFlow(), - simBouncerInteractor = kosmos.simBouncerInteractor, - authenticationMethod = AuthenticationMethodModel.Sim, + kosmos.pinBouncerViewModelFactory.create( + isInputEnabled = MutableStateFlow(true), onIntentionalUserInput = {}, + authenticationMethod = AuthenticationMethodModel.Sim, ) assertThat(underTest.isSimAreaVisible).isTrue() @@ -125,14 +113,10 @@ class PinBouncerViewModelTest : SysuiTestCase() { fun simBouncerViewModel_autoConfirmEnabled_hintedPinLengthIsNull() = testScope.runTest { val underTest = - PinBouncerViewModel( - applicationContext = context, - viewModelScope = testScope.backgroundScope, - interactor = bouncerInteractor, - isInputEnabled = MutableStateFlow(true).asStateFlow(), - simBouncerInteractor = kosmos.simBouncerInteractor, - authenticationMethod = AuthenticationMethodModel.Sim, + kosmos.pinBouncerViewModelFactory.create( + isInputEnabled = MutableStateFlow(true), onIntentionalUserInput = {}, + authenticationMethod = AuthenticationMethodModel.Pin, ) kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true) val hintedPinLength by collectLastValue(underTest.hintedPinLength) @@ -355,6 +339,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { AuthenticationMethodModel.Pin ) kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true) + runCurrent() underTest.onPinButtonClicked(1) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt index 9ccf99b301b1..70529cc762e0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt @@ -112,7 +112,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { testScope.runTest { val scene by collectLastValue(communalSceneInteractor.currentScene) - communalSceneInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") assertThat(scene).isEqualTo(CommunalScenes.Communal) communalSceneInteractor.setEditModeState(EditModeState.STARTING) @@ -133,7 +133,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { testScope.runTest { val scene by collectLastValue(communalSceneInteractor.currentScene) - communalSceneInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") assertThat(scene).isEqualTo(CommunalScenes.Communal) communalSceneInteractor.setIsLaunchingWidget(true) @@ -154,7 +154,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { testScope.runTest { val scene by collectLastValue(communalSceneInteractor.currentScene) - communalSceneInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") assertThat(scene).isEqualTo(CommunalScenes.Communal) communalSceneInteractor.setIsLaunchingWidget(false) @@ -174,7 +174,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { with(kosmos) { testScope.runTest { val scene by collectLastValue(communalSceneInteractor.currentScene) - communalSceneInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") assertThat(scene).isEqualTo(CommunalScenes.Communal) communalInteractor.setEditModeOpen(true) @@ -213,7 +213,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { testScope.runTest { whenever(centralSurfaces.isLaunchingActivityOverLockscreen).thenReturn(false) val scene by collectLastValue(communalSceneInteractor.currentScene) - communalSceneInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") assertThat(scene).isEqualTo(CommunalScenes.Communal) updateDocked(true) @@ -233,7 +233,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { testScope.runTest { whenever(centralSurfaces.isLaunchingActivityOverLockscreen).thenReturn(true) val scene by collectLastValue(communalSceneInteractor.currentScene) - communalSceneInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") assertThat(scene).isEqualTo(CommunalScenes.Communal) updateDocked(true) @@ -270,7 +270,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { with(kosmos) { testScope.runTest { val scene by collectLastValue(communalSceneInteractor.currentScene) - communalSceneInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") assertThat(scene).isEqualTo(CommunalScenes.Communal) fakeKeyguardTransitionRepository.sendTransitionSteps( @@ -292,7 +292,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { with(kosmos) { testScope.runTest { val scene by collectLastValue(communalSceneInteractor.currentScene) - communalSceneInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") assertThat(scene).isEqualTo(CommunalScenes.Communal) fakeKeyguardTransitionRepository.sendTransitionSteps( @@ -320,7 +320,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { fun dockingOnLockscreen_forcesCommunal() = with(kosmos) { testScope.runTest { - communalSceneInteractor.changeScene(CommunalScenes.Blank) + communalSceneInteractor.changeScene(CommunalScenes.Blank, "test") val scene by collectLastValue(communalSceneInteractor.currentScene) // device is docked while on the lockscreen @@ -342,7 +342,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { fun dockingOnLockscreen_doesNotForceCommunalIfDreamStarts() = with(kosmos) { testScope.runTest { - communalSceneInteractor.changeScene(CommunalScenes.Blank) + communalSceneInteractor.changeScene(CommunalScenes.Blank, "test") val scene by collectLastValue(communalSceneInteractor.currentScene) // device is docked while on the lockscreen @@ -374,7 +374,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { testScope.runTest { // Device is dreaming and on communal. updateDreaming(true) - communalSceneInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") val scene by collectLastValue(communalSceneInteractor.currentScene) assertThat(scene).isEqualTo(CommunalScenes.Communal) @@ -391,7 +391,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { testScope.runTest { // Device is not dreaming and on communal. updateDreaming(false) - communalSceneInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") // Scene stays as Communal advanceTimeBy(SCREEN_TIMEOUT.milliseconds) @@ -406,7 +406,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { testScope.runTest { // Device is dreaming and on communal. updateDreaming(true) - communalSceneInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") val scene by collectLastValue(communalSceneInteractor.currentScene) assertThat(scene).isEqualTo(CommunalScenes.Communal) @@ -429,7 +429,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { testScope.runTest { // Device is on communal, but not dreaming. updateDreaming(false) - communalSceneInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") val scene by collectLastValue(communalSceneInteractor.currentScene) assertThat(scene).isEqualTo(CommunalScenes.Communal) @@ -450,7 +450,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { with(kosmos) { testScope.runTest { // Device is on communal. - communalSceneInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") // Device stays on the hub after the timeout since we're not dreaming. advanceTimeBy(SCREEN_TIMEOUT.milliseconds * 2) @@ -471,7 +471,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { testScope.runTest { // Device is dreaming and on communal. updateDreaming(true) - communalSceneInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") val scene by collectLastValue(communalSceneInteractor.currentScene) assertThat(scene).isEqualTo(CommunalScenes.Communal) @@ -500,7 +500,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { // Device is dreaming and on communal. updateDreaming(true) - communalSceneInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") val scene by collectLastValue(communalSceneInteractor.currentScene) assertThat(scene).isEqualTo(CommunalScenes.Communal) @@ -520,7 +520,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { with(kosmos) { testScope.runTest { val scene by collectLastValue(communalSceneInteractor.currentScene) - communalSceneInteractor.changeScene(CommunalScenes.Blank) + communalSceneInteractor.changeScene(CommunalScenes.Blank, "test") assertThat(scene).isEqualTo(CommunalScenes.Blank) fakeKeyguardTransitionRepository.sendTransitionSteps( diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt index e57a4cbc230e..864795b062be 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt @@ -482,7 +482,7 @@ class CommunalInteractorTest : SysuiTestCase() { testScope.runTest { val targetScene = CommunalScenes.Communal - underTest.changeScene(targetScene) + underTest.changeScene(targetScene, "test") val desiredScene = collectLastValue(communalRepository.currentScene) runCurrent() @@ -635,7 +635,7 @@ class CommunalInteractorTest : SysuiTestCase() { runCurrent() assertThat(isCommunalShowing()).isEqualTo(false) - underTest.changeScene(CommunalScenes.Communal) + underTest.changeScene(CommunalScenes.Communal, "test") isCommunalShowing = collectLastValue(underTest.isCommunalShowing) runCurrent() @@ -659,12 +659,12 @@ class CommunalInteractorTest : SysuiTestCase() { assertThat(isCommunalShowing).isFalse() // Verify scene changes (without the flag) to communal sets the value to true - underTest.changeScene(CommunalScenes.Communal) + underTest.changeScene(CommunalScenes.Communal, "test") runCurrent() assertThat(isCommunalShowing).isTrue() // Verify scene changes (without the flag) to blank sets the value back to false - underTest.changeScene(CommunalScenes.Blank) + underTest.changeScene(CommunalScenes.Blank, "test") runCurrent() assertThat(isCommunalShowing).isFalse() } @@ -679,7 +679,7 @@ class CommunalInteractorTest : SysuiTestCase() { assertThat(isCommunalShowing).isFalse() // Verify scene changes without the flag doesn't have any impact - underTest.changeScene(CommunalScenes.Communal) + underTest.changeScene(CommunalScenes.Communal, "test") runCurrent() assertThat(isCommunalShowing).isFalse() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt index 43293c7a50ca..ed7e9107240e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt @@ -53,7 +53,7 @@ class CommunalSceneInteractorTest : SysuiTestCase() { val currentScene by collectLastValue(underTest.currentScene) assertThat(currentScene).isEqualTo(CommunalScenes.Blank) - underTest.changeScene(CommunalScenes.Communal) + underTest.changeScene(CommunalScenes.Communal, "test") assertThat(currentScene).isEqualTo(CommunalScenes.Communal) } @@ -63,7 +63,7 @@ class CommunalSceneInteractorTest : SysuiTestCase() { val currentScene by collectLastValue(underTest.currentScene) assertThat(currentScene).isEqualTo(CommunalScenes.Blank) - underTest.snapToScene(CommunalScenes.Communal) + underTest.snapToScene(CommunalScenes.Communal, "test") assertThat(currentScene).isEqualTo(CommunalScenes.Communal) } @@ -75,6 +75,7 @@ class CommunalSceneInteractorTest : SysuiTestCase() { assertThat(currentScene).isEqualTo(CommunalScenes.Blank) underTest.snapToScene( CommunalScenes.Communal, + "test", ActivityTransitionAnimator.TIMINGS.totalDuration ) assertThat(currentScene).isEqualTo(CommunalScenes.Blank) @@ -86,7 +87,7 @@ class CommunalSceneInteractorTest : SysuiTestCase() { fun changeSceneForActivityStartOnDismissKeyguard() = testScope.runTest { val currentScene by collectLastValue(underTest.currentScene) - underTest.snapToScene(CommunalScenes.Communal) + underTest.snapToScene(CommunalScenes.Communal, "test") assertThat(currentScene).isEqualTo(CommunalScenes.Communal) underTest.changeSceneForActivityStartOnDismissKeyguard() @@ -97,7 +98,7 @@ class CommunalSceneInteractorTest : SysuiTestCase() { fun changeSceneForActivityStartOnDismissKeyguard_willNotChangeScene_forEditModeActivity() = testScope.runTest { val currentScene by collectLastValue(underTest.currentScene) - underTest.snapToScene(CommunalScenes.Communal) + underTest.snapToScene(CommunalScenes.Communal, "test") assertThat(currentScene).isEqualTo(CommunalScenes.Communal) underTest.setEditModeState(EditModeState.STARTING) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt index 3a23e14e2777..7e28e19d0ee0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt @@ -158,7 +158,7 @@ class CommunalTutorialInteractorTest : SysuiTestCase() { kosmos.setCommunalAvailable(true) communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED) - communalInteractor.changeScene(CommunalScenes.Blank) + communalInteractor.changeScene(CommunalScenes.Blank, "test") assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_NOT_STARTED) } @@ -171,7 +171,7 @@ class CommunalTutorialInteractorTest : SysuiTestCase() { goToCommunal() communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_STARTED) - communalInteractor.changeScene(CommunalScenes.Blank) + communalInteractor.changeScene(CommunalScenes.Blank, "test") assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_COMPLETED) } @@ -184,13 +184,13 @@ class CommunalTutorialInteractorTest : SysuiTestCase() { goToCommunal() communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) - communalInteractor.changeScene(CommunalScenes.Blank) + communalInteractor.changeScene(CommunalScenes.Blank, "test") assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_COMPLETED) } private suspend fun goToCommunal() { kosmos.setCommunalAvailable(true) - communalInteractor.changeScene(CommunalScenes.Communal) + communalInteractor.changeScene(CommunalScenes.Communal, "test") } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorTest.kt new file mode 100644 index 000000000000..b3ffc7159f7c --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorTest.kt @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.domain.interactor + +import android.app.ActivityManager.RunningTaskInfo +import android.app.usage.UsageEvents +import android.content.pm.UserInfo +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.common.usagestats.data.repository.fakeUsageStatsRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.kosmos.testScope +import com.android.systemui.plugins.activityStarter +import com.android.systemui.settings.fakeUserTracker +import com.android.systemui.shared.system.taskStackChangeListeners +import com.android.systemui.testKosmos +import com.android.systemui.util.time.fakeSystemClock +import kotlin.time.Duration +import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.Duration.Companion.seconds +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.advanceTimeBy +import kotlinx.coroutines.test.currentTime +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.any +import org.mockito.kotlin.anyOrNull +import org.mockito.kotlin.mock +import org.mockito.kotlin.never +import org.mockito.kotlin.times +import org.mockito.kotlin.verify + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +class WidgetTrampolineInteractorTest : SysuiTestCase() { + + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + + private val activityStarter = kosmos.activityStarter + private val usageStatsRepository = kosmos.fakeUsageStatsRepository + private val taskStackChangeListeners = kosmos.taskStackChangeListeners + private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository + private val userTracker = kosmos.fakeUserTracker + private val systemClock = kosmos.fakeSystemClock + + private val underTest = kosmos.widgetTrampolineInteractor + + @Before + fun setUp() { + userTracker.set(listOf(MAIN_USER), 0) + systemClock.setCurrentTimeMillis(testScope.currentTime) + } + + @Test + fun testNewTaskStartsWhileOnHub_triggersUnlock() = + testScope.runTest { + transition(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GLANCEABLE_HUB) + backgroundScope.launch { underTest.waitForActivityStartAndDismissKeyguard() } + runCurrent() + + verify(activityStarter, never()).dismissKeyguardThenExecute(any(), anyOrNull(), any()) + moveTaskToFront() + + verify(activityStarter).dismissKeyguardThenExecute(any(), anyOrNull(), any()) + } + + @Test + fun testNewTaskStartsAfterExitingHub_doesNotTriggerUnlock() = + testScope.runTest { + transition(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GLANCEABLE_HUB) + backgroundScope.launch { underTest.waitForActivityStartAndDismissKeyguard() } + runCurrent() + + transition(from = KeyguardState.GLANCEABLE_HUB, to = KeyguardState.LOCKSCREEN) + moveTaskToFront() + + verify(activityStarter, never()).dismissKeyguardThenExecute(any(), anyOrNull(), any()) + } + + @Test + fun testNewTaskStartsAfterTimeout_doesNotTriggerUnlock() = + testScope.runTest { + transition(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GLANCEABLE_HUB) + backgroundScope.launch { underTest.waitForActivityStartAndDismissKeyguard() } + runCurrent() + + advanceTime(2.seconds) + moveTaskToFront() + + verify(activityStarter, never()).dismissKeyguardThenExecute(any(), anyOrNull(), any()) + } + + @Test + fun testActivityResumedWhileOnHub_triggersUnlock() = + testScope.runTest { + transition(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GLANCEABLE_HUB) + backgroundScope.launch { underTest.waitForActivityStartAndDismissKeyguard() } + runCurrent() + + addActivityEvent(UsageEvents.Event.ACTIVITY_RESUMED) + advanceTime(1.seconds) + + verify(activityStarter).dismissKeyguardThenExecute(any(), anyOrNull(), any()) + } + + @Test + fun testActivityResumedAfterExitingHub_doesNotTriggerUnlock() = + testScope.runTest { + transition(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GLANCEABLE_HUB) + backgroundScope.launch { underTest.waitForActivityStartAndDismissKeyguard() } + runCurrent() + + transition(from = KeyguardState.GLANCEABLE_HUB, to = KeyguardState.LOCKSCREEN) + addActivityEvent(UsageEvents.Event.ACTIVITY_RESUMED) + advanceTime(1.seconds) + + verify(activityStarter, never()).dismissKeyguardThenExecute(any(), anyOrNull(), any()) + } + + @Test + fun testActivityDestroyed_doesNotTriggerUnlock() = + testScope.runTest { + transition(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GLANCEABLE_HUB) + backgroundScope.launch { underTest.waitForActivityStartAndDismissKeyguard() } + runCurrent() + + addActivityEvent(UsageEvents.Event.ACTIVITY_DESTROYED) + advanceTime(1.seconds) + + verify(activityStarter, never()).dismissKeyguardThenExecute(any(), anyOrNull(), any()) + } + + @Test + fun testMultipleActivityEvents_triggersUnlockOnlyOnce() = + testScope.runTest { + transition(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GLANCEABLE_HUB) + backgroundScope.launch { underTest.waitForActivityStartAndDismissKeyguard() } + runCurrent() + + addActivityEvent(UsageEvents.Event.ACTIVITY_RESUMED) + advanceTime(10.milliseconds) + addActivityEvent(UsageEvents.Event.ACTIVITY_RESUMED) + advanceTime(1.seconds) + + verify(activityStarter, times(1)).dismissKeyguardThenExecute(any(), anyOrNull(), any()) + } + + private fun TestScope.advanceTime(duration: Duration) { + systemClock.advanceTime(duration.inWholeMilliseconds) + advanceTimeBy(duration) + } + + private fun TestScope.addActivityEvent(type: Int) { + usageStatsRepository.addEvent( + instanceId = 1, + user = MAIN_USER.userHandle, + packageName = "pkg.test", + timestamp = systemClock.currentTimeMillis(), + type = type, + ) + runCurrent() + } + + private fun TestScope.moveTaskToFront() { + taskStackChangeListeners.listenerImpl.onTaskMovedToFront(mock<RunningTaskInfo>()) + runCurrent() + } + + private suspend fun TestScope.transition(from: KeyguardState, to: KeyguardState) { + keyguardTransitionRepository.sendTransitionSteps( + listOf( + TransitionStep( + from = from, + to = to, + value = 0.1f, + transitionState = TransitionState.STARTED, + ownerName = "test", + ), + TransitionStep( + from = from, + to = to, + value = 1f, + transitionState = TransitionState.FINISHED, + ownerName = "test", + ), + ), + testScope + ) + runCurrent() + } + + private companion object { + val MAIN_USER: UserInfo = UserInfo(0, "primary", UserInfo.FLAG_MAIN) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorControllerTest.kt index e36fd75445e2..a052b078167d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorControllerTest.kt @@ -76,7 +76,7 @@ class CommunalTransitionAnimatorControllerTest : SysuiTestCase() { val launching by collectLastValue(communalSceneInteractor.isLaunchingWidget) val scene by collectLastValue(communalSceneInteractor.currentScene) - communalSceneInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") Truth.assertThat(scene).isEqualTo(CommunalScenes.Communal) communalSceneInteractor.setIsLaunchingWidget(true) assertTrue(launching!!) @@ -103,7 +103,7 @@ class CommunalTransitionAnimatorControllerTest : SysuiTestCase() { val launching by collectLastValue(communalSceneInteractor.isLaunchingWidget) val scene by collectLastValue(communalSceneInteractor.currentScene) - communalSceneInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") Truth.assertThat(scene).isEqualTo(CommunalScenes.Communal) communalSceneInteractor.setIsLaunchingWidget(true) assertTrue(launching!!) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt index 023de52b2460..400f736ba882 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt @@ -27,7 +27,9 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.communal.domain.interactor.communalSceneInteractor +import com.android.systemui.communal.domain.interactor.widgetTrampolineInteractor import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testScope import com.android.systemui.log.logcatLogBuffer import com.android.systemui.plugins.ActivityStarter @@ -67,9 +69,11 @@ class WidgetInteractionHandlerTest : SysuiTestCase() { with(kosmos) { underTest = WidgetInteractionHandler( + applicationScope = applicationCoroutineScope, activityStarter = activityStarter, communalSceneInteractor = communalSceneInteractor, logBuffer = logcatLogBuffer(), + widgetTrampolineInteractor = widgetTrampolineInteractor, ) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt index 7a86e57779b9..da82b5f4a1f7 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt @@ -68,7 +68,6 @@ import com.android.systemui.navigationbar.gestural.domain.GestureInteractor import com.android.systemui.testKosmos import com.android.systemui.touch.TouchInsetManager import com.android.systemui.util.concurrency.FakeExecutor -import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat @@ -87,6 +86,7 @@ import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.isNull import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations +import org.mockito.kotlin.any import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.eq import org.mockito.kotlin.spy @@ -669,7 +669,7 @@ class DreamOverlayServiceTest : SysuiTestCase() { runCurrent() verify(mDreamOverlayCallback).onRedirectWake(true) client.onWakeRequested() - verify(mCommunalInteractor).changeScene(eq(CommunalScenes.Communal), isNull()) + verify(mCommunalInteractor).changeScene(eq(CommunalScenes.Communal), any(), isNull()) verify(mUiEventLogger).log(CommunalUiEvent.DREAM_TO_COMMUNAL_HUB_DREAM_AWAKE_START) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt index 8c1e8de315b1..9792c28d5023 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt @@ -845,7 +845,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.GONE) // WHEN the glanceable hub is shown - communalSceneInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") runCurrent() assertThat(transitionRepository) @@ -1004,7 +1004,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest fun alternateBouncerToGlanceableHub() = testScope.runTest { // GIVEN the device is idle on the glanceable hub - communalSceneInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") runCurrent() clearInvocations(transitionRepository) @@ -1123,7 +1123,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest fun primaryBouncerToGlanceableHub() = testScope.runTest { // GIVEN the device is idle on the glanceable hub - communalSceneInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") runCurrent() // GIVEN a prior transition has run to PRIMARY_BOUNCER @@ -1157,7 +1157,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest advanceTimeBy(600L) // GIVEN the device is idle on the glanceable hub - communalSceneInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") runCurrent() // GIVEN a prior transition has run to PRIMARY_BOUNCER @@ -1971,7 +1971,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest fun glanceableHubToLockscreen_communalKtfRefactor() = testScope.runTest { // GIVEN a prior transition has run to GLANCEABLE_HUB - communalSceneInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") runCurrent() clearInvocations(transitionRepository) @@ -2035,7 +2035,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest fun glanceableHubToDozing_communalKtfRefactor() = testScope.runTest { // GIVEN a prior transition has run to GLANCEABLE_HUB - communalSceneInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") runCurrent() clearInvocations(transitionRepository) @@ -2136,7 +2136,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest fun glanceableHubToOccluded_communalKtfRefactor() = testScope.runTest { // GIVEN a prior transition has run to GLANCEABLE_HUB - communalSceneInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") runCurrent() clearInvocations(transitionRepository) @@ -2184,7 +2184,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest fun glanceableHubToGone_communalKtfRefactor() = testScope.runTest { // GIVEN a prior transition has run to GLANCEABLE_HUB - communalSceneInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") runCurrent() clearInvocations(transitionRepository) @@ -2265,7 +2265,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest advanceTimeBy(600L) // GIVEN a prior transition has run to GLANCEABLE_HUB - communalSceneInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") runCurrent() clearInvocations(transitionRepository) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt index 409c55144c6a..5ec566bab6d5 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt @@ -28,6 +28,7 @@ import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos +import com.google.common.collect.Range import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runCurrent @@ -49,6 +50,24 @@ class PrimaryBouncerToLockscreenTransitionViewModelTest : SysuiTestCase() { val underTest = kosmos.primaryBouncerToLockscreenTransitionViewModel @Test + fun lockscreenAlphaStartsFromViewStateAccessorAlpha() = + testScope.runTest { + val viewState = ViewStateAccessor(alpha = { 0.5f }) + val alpha by collectLastValue(underTest.lockscreenAlpha(viewState)) + + keyguardTransitionRepository.sendTransitionStep(step(0f, TransitionState.STARTED)) + + keyguardTransitionRepository.sendTransitionStep(step(0f)) + assertThat(alpha).isEqualTo(0.5f) + + keyguardTransitionRepository.sendTransitionStep(step(0.5f)) + assertThat(alpha).isIn(Range.open(0.5f, 1f)) + + keyguardTransitionRepository.sendTransitionStep(step(1f)) + assertThat(alpha).isEqualTo(1f) + } + + @Test fun deviceEntryParentViewAlpha() = testScope.runTest { val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt new file mode 100644 index 000000000000..59992650cfc7 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.composefragment.viewmodel + +import android.content.testableContext +import android.testing.TestableLooper.RunWithLooper +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.testing.TestLifecycleOwner +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.kosmos.testScope +import com.android.systemui.qs.fgsManagerController +import com.android.systemui.res.R +import com.android.systemui.shade.largeScreenHeaderHelper +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestResult +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.resetMain +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.test.setMain +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +@RunWithLooper +@OptIn(ExperimentalCoroutinesApi::class) +class QSFragmentComposeViewModelTest : SysuiTestCase() { + private val kosmos = testKosmos() + + private val lifecycleOwner = + TestLifecycleOwner( + initialState = Lifecycle.State.CREATED, + coroutineDispatcher = kosmos.testDispatcher, + ) + + private val underTest by lazy { + kosmos.qsFragmentComposeViewModelFactory.create(lifecycleOwner.lifecycleScope) + } + + @Before + fun setUp() { + Dispatchers.setMain(kosmos.testDispatcher) + } + + @After + fun teardown() { + Dispatchers.resetMain() + } + + // For now the state changes at 0.5f expansion. This will change once we implement animation + // (and this test will fail) + @Test + fun qsExpansionValueChanges_correctExpansionState() = + with(kosmos) { + testScope.testWithinLifecycle { + val expansionState by collectLastValue(underTest.expansionState) + + underTest.qsExpansionValue = 0f + assertThat(expansionState) + .isEqualTo(QSFragmentComposeViewModel.QSExpansionState.QQS) + + underTest.qsExpansionValue = 0.3f + assertThat(expansionState) + .isEqualTo(QSFragmentComposeViewModel.QSExpansionState.QQS) + + underTest.qsExpansionValue = 0.7f + assertThat(expansionState).isEqualTo(QSFragmentComposeViewModel.QSExpansionState.QS) + + underTest.qsExpansionValue = 1f + assertThat(expansionState).isEqualTo(QSFragmentComposeViewModel.QSExpansionState.QS) + } + } + + @Test + fun qqsHeaderHeight_largeScreenHeader_0() = + with(kosmos) { + testScope.testWithinLifecycle { + val qqsHeaderHeight by collectLastValue(underTest.qqsHeaderHeight) + + testableContext.orCreateTestableResources.addOverride( + R.bool.config_use_large_screen_shade_header, + true + ) + fakeConfigurationRepository.onConfigurationChange() + + assertThat(qqsHeaderHeight).isEqualTo(0) + } + } + + @Test + fun qqsHeaderHeight_noLargeScreenHeader_providedByHelper() = + with(kosmos) { + testScope.testWithinLifecycle { + val qqsHeaderHeight by collectLastValue(underTest.qqsHeaderHeight) + + testableContext.orCreateTestableResources.addOverride( + R.bool.config_use_large_screen_shade_header, + false + ) + fakeConfigurationRepository.onConfigurationChange() + + assertThat(qqsHeaderHeight) + .isEqualTo(largeScreenHeaderHelper.getLargeScreenHeaderHeight()) + } + } + + @Test + fun footerActionsControllerInit() = + with(kosmos) { + testScope.testWithinLifecycle { + underTest + runCurrent() + assertThat(fgsManagerController.initialized).isTrue() + } + } + + private inline fun TestScope.testWithinLifecycle( + crossinline block: suspend TestScope.() -> TestResult + ): TestResult { + return runTest { + lifecycleOwner.setCurrentState(Lifecycle.State.RESUMED) + block().also { lifecycleOwner.setCurrentState(Lifecycle.State.DESTROYED) } + } + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt index 314631823e96..8995f46e7a72 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt @@ -24,7 +24,6 @@ import android.os.UserHandle import android.service.quicksettings.Tile import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.systemui.Flags.FLAG_QS_NEW_PIPELINE import com.android.systemui.Flags.FLAG_QS_NEW_TILES import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue @@ -107,7 +106,6 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { fun setup() { MockitoAnnotations.initMocks(this) - mSetFlagsRule.enableFlags(FLAG_QS_NEW_PIPELINE) mSetFlagsRule.enableFlags(FLAG_QS_NEW_TILES) userRepository.setUserInfos(listOf(USER_INFO_0, USER_INFO_1)) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/NoLowNumberOfTilesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/NoLowNumberOfTilesTest.kt index e8ad038f8fbc..00c720475fb1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/NoLowNumberOfTilesTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/NoLowNumberOfTilesTest.kt @@ -20,7 +20,6 @@ import android.content.ComponentName import android.content.pm.UserInfo import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.MediumTest -import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.Kosmos @@ -98,8 +97,6 @@ class NoLowNumberOfTilesTest : SysuiTestCase() { @Before fun setUp() { - mSetFlagsRule.enableFlags(Flags.FLAG_QS_NEW_PIPELINE) - with(kosmos) { restoreReconciliationInteractor.start() autoAddInteractor.init(kosmos.currentTilesInteractor) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/WorkProfileAutoAddedAfterRestoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/WorkProfileAutoAddedAfterRestoreTest.kt index dffd0d72969c..6bcaea47cab1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/WorkProfileAutoAddedAfterRestoreTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/WorkProfileAutoAddedAfterRestoreTest.kt @@ -20,7 +20,6 @@ import android.content.pm.UserInfo import android.os.UserManager import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.MediumTest -import com.android.systemui.Flags.FLAG_QS_NEW_PIPELINE import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.Kosmos @@ -59,6 +58,7 @@ class WorkProfileAutoAddedAfterRestoreTest : SysuiTestCase() { // Getter here so it can change when there is a managed profile. private val workTileAvailable: Boolean get() = hasManagedProfile() + private val currentUser: Int get() = kosmos.userTracker.userId @@ -67,8 +67,6 @@ class WorkProfileAutoAddedAfterRestoreTest : SysuiTestCase() { @Before fun setUp() { - mSetFlagsRule.enableFlags(FLAG_QS_NEW_PIPELINE) - kosmos.qsTileFactory = FakeQSFactory(::tileCreator) kosmos.restoreReconciliationInteractor.start() kosmos.autoAddInteractor.init(kosmos.currentTilesInteractor) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModelTest.kt index 956353845506..1118a6150fcc 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModelTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.qs.ui.viewmodel import android.testing.TestableLooper.RunWithLooper +import androidx.lifecycle.LifecycleOwner import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -59,7 +60,7 @@ class QuickSettingsSceneContentViewModelTest : SysuiTestCase() { private val footerActionsViewModel = mock<FooterActionsViewModel>() private val footerActionsViewModelFactory = mock<FooterActionsViewModel.Factory> { - whenever(create(any())).thenReturn(footerActionsViewModel) + whenever(create(any<LifecycleOwner>())).thenReturn(footerActionsViewModel) } private val footerActionsController = mock<FooterActionsController>() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt index 66e45ab8ccbe..aee3ce052d78 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt @@ -36,10 +36,10 @@ import com.android.systemui.authentication.domain.interactor.authenticationInter import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor import com.android.systemui.bouncer.domain.interactor.bouncerActionButtonInteractor -import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel +import com.android.systemui.bouncer.ui.viewmodel.BouncerSceneContentViewModel import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel -import com.android.systemui.bouncer.ui.viewmodel.bouncerViewModel +import com.android.systemui.bouncer.ui.viewmodel.bouncerSceneContentViewModel import com.android.systemui.classifier.domain.interactor.falsingInteractor import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.coroutines.collectLastValue @@ -133,13 +133,14 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { sceneInteractor = sceneInteractor, falsingInteractor = kosmos.falsingInteractor, powerInteractor = kosmos.powerInteractor, + motionEventHandlerReceiver = {}, ) .apply { setTransitionState(transitionState) } } private lateinit var mobileConnectionsRepository: FakeMobileConnectionsRepository private lateinit var bouncerActionButtonInteractor: BouncerActionButtonInteractor - private lateinit var bouncerViewModel: BouncerViewModel + private lateinit var bouncerSceneContentViewModel: BouncerSceneContentViewModel private val lockscreenSceneActionsViewModel by lazy { LockscreenSceneActionsViewModel( @@ -187,7 +188,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { } bouncerActionButtonInteractor = kosmos.bouncerActionButtonInteractor - bouncerViewModel = kosmos.bouncerViewModel + bouncerSceneContentViewModel = kosmos.bouncerSceneContentViewModel shadeSceneContentViewModel = kosmos.shadeSceneContentViewModel shadeSceneActionsViewModel = kosmos.shadeSceneActionsViewModel @@ -198,6 +199,8 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { lockscreenSceneActionsViewModel.activateIn(testScope) shadeSceneContentViewModel.activateIn(testScope) shadeSceneActionsViewModel.activateIn(testScope) + bouncerSceneContentViewModel.activateIn(testScope) + sceneContainerViewModel.activateIn(testScope) assertWithMessage("Initial scene key mismatch!") .that(sceneContainerViewModel.currentScene.value) @@ -397,7 +400,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer) emulateUserDrivenTransition(to = upDestinationSceneKey) - val bouncerActionButton by collectLastValue(bouncerViewModel.actionButton) + val bouncerActionButton by collectLastValue(bouncerSceneContentViewModel.actionButton) assertWithMessage("Bouncer action button not visible") .that(bouncerActionButton) .isNotNull() @@ -417,7 +420,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer) emulateUserDrivenTransition(to = upDestinationSceneKey) - val bouncerActionButton by collectLastValue(bouncerViewModel.actionButton) + val bouncerActionButton by collectLastValue(bouncerSceneContentViewModel.actionButton) assertWithMessage("Bouncer action button not visible during call") .that(bouncerActionButton) .isNotNull() @@ -568,7 +571,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { bouncerSceneJob = if (to == Scenes.Bouncer) { testScope.backgroundScope.launch { - bouncerViewModel.authMethodViewModel.collect { + bouncerSceneContentViewModel.authMethodViewModel.collect { // Do nothing. Need this to turn this otherwise cold flow, hot. } } @@ -644,7 +647,8 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { assertWithMessage("Cannot enter PIN when not on the Bouncer scene!") .that(getCurrentSceneInUi()) .isEqualTo(Scenes.Bouncer) - val authMethodViewModel by collectLastValue(bouncerViewModel.authMethodViewModel) + val authMethodViewModel by + collectLastValue(bouncerSceneContentViewModel.authMethodViewModel) assertWithMessage("Cannot enter PIN when not using a PIN authentication method!") .that(authMethodViewModel) .isInstanceOf(PinBouncerViewModel::class.java) @@ -672,7 +676,8 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { assertWithMessage("Cannot enter PIN when not on the Bouncer scene!") .that(getCurrentSceneInUi()) .isEqualTo(Scenes.Bouncer) - val authMethodViewModel by collectLastValue(bouncerViewModel.authMethodViewModel) + val authMethodViewModel by + collectLastValue(bouncerSceneContentViewModel.authMethodViewModel) assertWithMessage("Cannot enter PIN when not using a PIN authentication method!") .that(authMethodViewModel) .isInstanceOf(PinBouncerViewModel::class.java) @@ -719,7 +724,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { /** Emulates the dismissal of the IME (soft keyboard). */ private fun TestScope.dismissIme() { - (bouncerViewModel.authMethodViewModel.value as? PasswordBouncerViewModel)?.let { + (bouncerSceneContentViewModel.authMethodViewModel.value as? PasswordBouncerViewModel)?.let { it.onImeDismissed() runCurrent() } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt index ea95aab4a1c4..f85823ad7c33 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt @@ -14,6 +14,8 @@ * limitations under the License. */ +@file:OptIn(ExperimentalCoroutinesApi::class) + package com.android.systemui.scene.ui.viewmodel import android.view.MotionEvent @@ -25,6 +27,7 @@ import com.android.systemui.classifier.fakeFalsingManager import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.kosmos.testScope +import com.android.systemui.lifecycle.activateIn import com.android.systemui.power.data.repository.fakePowerRepository import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.scene.domain.interactor.sceneInteractor @@ -37,6 +40,8 @@ import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.Job import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before @@ -57,6 +62,9 @@ class SceneContainerViewModelTest : SysuiTestCase() { private lateinit var underTest: SceneContainerViewModel + private lateinit var activationJob: Job + private var motionEventHandler: SceneContainerViewModel.MotionEventHandler? = null + @Before fun setUp() { underTest = @@ -64,10 +72,28 @@ class SceneContainerViewModelTest : SysuiTestCase() { sceneInteractor = sceneInteractor, falsingInteractor = kosmos.falsingInteractor, powerInteractor = kosmos.powerInteractor, + motionEventHandlerReceiver = { motionEventHandler -> + this@SceneContainerViewModelTest.motionEventHandler = motionEventHandler + }, ) + activationJob = Job() + underTest.activateIn(testScope, activationJob) } @Test + fun activate_setsMotionEventHandler() = + testScope.runTest { assertThat(motionEventHandler).isNotNull() } + + @Test + fun deactivate_clearsMotionEventHandler() = + testScope.runTest { + activationJob.cancel() + runCurrent() + + assertThat(motionEventHandler).isNull() + } + + @Test fun isVisible() = testScope.runTest { val isVisible by collectLastValue(underTest.isVisible) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java index 9005ae3c3d41..89aa670e5f0c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java @@ -241,7 +241,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase { alm.showNotification(entry); final boolean removedImmediately = alm.removeNotification( - entry.getKey(), /* releaseImmediately = */ false); + entry.getKey(), /* releaseImmediately = */ false, "removeDeferred"); assertFalse(removedImmediately); assertTrue(alm.isHeadsUpEntry(entry.getKey())); } @@ -254,7 +254,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase { alm.showNotification(entry); final boolean removedImmediately = alm.removeNotification( - entry.getKey(), /* releaseImmediately = */ true); + entry.getKey(), /* releaseImmediately = */ true, "forceRemove"); assertTrue(removedImmediately); assertFalse(alm.isHeadsUpEntry(entry.getKey())); } @@ -430,7 +430,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase { hum.showNotification(entry); final boolean removedImmediately = hum.removeNotification( - entry.getKey(), /* releaseImmediately = */ false); + entry.getKey(), /* releaseImmediately = */ false, "beforeMinimumDisplayTime"); assertFalse(removedImmediately); assertTrue(hum.isHeadsUpEntry(entry.getKey())); @@ -452,7 +452,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase { assertTrue(hum.isHeadsUpEntry(entry.getKey())); final boolean removedImmediately = hum.removeNotification( - entry.getKey(), /* releaseImmediately = */ false); + entry.getKey(), /* releaseImmediately = */ false, "afterMinimumDisplayTime"); assertTrue(removedImmediately); assertFalse(hum.isHeadsUpEntry(entry.getKey())); } @@ -466,7 +466,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase { hum.showNotification(entry); final boolean removedImmediately = hum.removeNotification( - entry.getKey(), /* releaseImmediately = */ true); + entry.getKey(), /* releaseImmediately = */ true, "afterMinimumDisplayTime"); assertTrue(removedImmediately); assertFalse(hum.isHeadsUpEntry(entry.getKey())); } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt index 7a6838a6c9ac..ca106fa01f9a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt @@ -179,8 +179,8 @@ class HeadsUpManagerPhoneTest(flags: FlagsParameterization) : BaseHeadsUpManager mContext .getOrCreateTestableResources() .addOverride(R.integer.ambient_notification_extension_time, 500) - mAvalancheController = AvalancheController(dumpManager, mUiEventLogger, - mHeadsUpManagerLogger, mBgHandler) + mAvalancheController = + AvalancheController(dumpManager, mUiEventLogger, mHeadsUpManagerLogger, mBgHandler) } @Test @@ -200,7 +200,12 @@ class HeadsUpManagerPhoneTest(flags: FlagsParameterization) : BaseHeadsUpManager hmp.addSwipedOutNotification(entry.key) // Remove should succeed because the notification is swiped out - val removedImmediately = hmp.removeNotification(entry.key, /* releaseImmediately= */ false) + val removedImmediately = + hmp.removeNotification( + entry.key, + /* releaseImmediately= */ false, + /* reason= */ "swipe out" + ) Assert.assertTrue(removedImmediately) Assert.assertFalse(hmp.isHeadsUpEntry(entry.key)) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java index 69207ba07e6e..3efabd743141 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java @@ -100,7 +100,7 @@ class TestableHeadsUpManager extends BaseHeadsUpManager { @Override public boolean removeNotification(@NonNull String key, boolean releaseImmediately, - boolean animate) { + boolean animate, @NonNull String reason) { throw new UnsupportedOperationException(); } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt index bcad7e7bc31c..54b7d25325c5 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt @@ -23,6 +23,7 @@ import android.provider.Settings import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.settingslib.notification.modes.TestModeBuilder +import com.android.settingslib.notification.modes.ZenMode import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testDispatcher @@ -30,6 +31,7 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.statusbar.policy.data.repository.fakeZenModeRepository import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor import com.android.systemui.statusbar.policy.ui.dialog.mockModesDialogDelegate +import com.android.systemui.statusbar.policy.ui.dialog.mockModesDialogEventLogger import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -40,6 +42,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.clearInvocations import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.times import org.mockito.kotlin.verify @SmallTest @@ -50,9 +53,16 @@ class ModesDialogViewModelTest : SysuiTestCase() { private val repository = kosmos.fakeZenModeRepository private val interactor = kosmos.zenModeInteractor private val mockDialogDelegate = kosmos.mockModesDialogDelegate + private val mockDialogEventLogger = kosmos.mockModesDialogEventLogger private val underTest = - ModesDialogViewModel(context, interactor, kosmos.testDispatcher, mockDialogDelegate) + ModesDialogViewModel( + context, + interactor, + kosmos.testDispatcher, + mockDialogDelegate, + mockDialogEventLogger + ) @Test fun tiles_filtersOutUserDisabledModes() = @@ -432,4 +442,84 @@ class ModesDialogViewModelTest : SysuiTestCase() { assertThat(intent.extras?.getString(Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID)) .isEqualTo("B") } + + @Test + fun onClick_logsOnOffEvents() = + testScope.runTest { + val tiles by collectLastValue(underTest.tiles) + + repository.addModes( + listOf( + TestModeBuilder.MANUAL_DND_ACTIVE, + TestModeBuilder() + .setId("id1") + .setName("Inactive Mode One") + .setActive(false) + .setManualInvocationAllowed(true) + .build(), + TestModeBuilder() + .setId("id2") + .setName("Active Non-Invokable Mode Two") // but can be turned off by tile + .setActive(true) + .setManualInvocationAllowed(false) + .build(), + ) + ) + runCurrent() + + assertThat(tiles?.size).isEqualTo(3) + + // Trigger onClick for each tile in sequence + tiles?.forEach { it.onClick.invoke() } + runCurrent() + + val onModeCaptor = argumentCaptor<ZenMode>() + val offModeCaptor = argumentCaptor<ZenMode>() + + // manual mode and mode 2 should have turned off + verify(mockDialogEventLogger, times(2)).logModeOff(offModeCaptor.capture()) + val off0 = offModeCaptor.firstValue + assertThat(off0.isManualDnd).isTrue() + + val off1 = offModeCaptor.secondValue + assertThat(off1.id).isEqualTo("id2") + + // should also have logged turning mode 1 on + verify(mockDialogEventLogger).logModeOn(onModeCaptor.capture()) + val on = onModeCaptor.lastValue + assertThat(on.id).isEqualTo("id1") + } + + @Test + fun onLongClick_logsSettingsEvents() = + testScope.runTest { + val tiles by collectLastValue(underTest.tiles) + + repository.addModes( + listOf( + TestModeBuilder.MANUAL_DND_ACTIVE, + TestModeBuilder() + .setId("id1") + .setName("Inactive Mode One") + .setActive(false) + .setManualInvocationAllowed(true) + .build(), + ) + ) + runCurrent() + + assertThat(tiles?.size).isEqualTo(2) + val modeCaptor = argumentCaptor<ZenMode>() + + // long click manual DND and then automatic mode + tiles?.forEach { it.onLongClick.invoke() } + runCurrent() + + verify(mockDialogEventLogger, times(2)).logModeSettings(modeCaptor.capture()) + val manualMode = modeCaptor.firstValue + assertThat(manualMode.isManualDnd).isTrue() + + val automaticMode = modeCaptor.lastValue + assertThat(automaticMode.id).isEqualTo("id1") + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorTest.kt index 7385a4731401..7c55f7a91a76 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorTest.kt @@ -32,7 +32,6 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos import com.android.systemui.volume.data.repository.TestAudioDevicesFactory import com.android.systemui.volume.data.repository.audioRepository -import com.android.systemui.volume.data.repository.audioSharingRepository import com.android.systemui.volume.domain.model.AudioOutputDevice import com.android.systemui.volume.localMediaController import com.android.systemui.volume.localMediaRepository @@ -222,32 +221,4 @@ class AudioOutputInteractorTest : SysuiTestCase() { val testIcon = TestStubDrawable() } - - @Test - fun inAudioSharing_returnTrue() { - with(kosmos) { - testScope.runTest { - audioSharingRepository.setInAudioSharing(true) - - val inAudioSharing by collectLastValue(underTest.isInAudioSharing) - runCurrent() - - assertThat(inAudioSharing).isTrue() - } - } - } - - @Test - fun notInAudioSharing_returnFalse() { - with(kosmos) { - testScope.runTest { - audioSharingRepository.setInAudioSharing(false) - - val inAudioSharing by collectLastValue(underTest.isInAudioSharing) - runCurrent() - - assertThat(inAudioSharing).isFalse() - } - } - } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorTest.kt index a1fcfcd0f749..c9d147b6c81c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorTest.kt @@ -49,6 +49,24 @@ class AudioSharingInteractorTest : SysuiTestCase() { } @Test + fun handleInAudioSharingChange() { + with(kosmos) { + testScope.runTest { + with(audioSharingRepository) { setInAudioSharing(true) } + val inAudioSharing by collectLastValue(underTest.isInAudioSharing) + runCurrent() + + Truth.assertThat(inAudioSharing).isEqualTo(true) + + with(audioSharingRepository) { setInAudioSharing(false) } + runCurrent() + + Truth.assertThat(inAudioSharing).isEqualTo(false) + } + } + } + + @Test fun handlePrimaryGroupChange_nullVolume() { with(kosmos) { testScope.runTest { diff --git a/packages/SystemUI/res-keyguard/values-ar/strings.xml b/packages/SystemUI/res-keyguard/values-ar/strings.xml index 57fd9ea8319f..d069c0181218 100644 --- a/packages/SystemUI/res-keyguard/values-ar/strings.xml +++ b/packages/SystemUI/res-keyguard/values-ar/strings.xml @@ -71,7 +71,7 @@ <string name="kg_prompt_after_dpm_lock" msgid="6002804765868345917">"لمزيد من الأمان، تم قفل الجهاز وفقًا لسياسة العمل."</string> <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"يجب إدخال رقم التعريف الشخصي بعد إلغاء الفتح الذكي."</string> <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"يجب إدخال كلمة المرور بعد إلغاء الفتح الذكي."</string> - <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"يجب رسم النقش بعد إلغاء التأمين."</string> + <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"يجب رسم النقش بعد إلغاء الفتح الذكي."</string> <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"سيتم تثبيت التحديث عندما لا يكون الجهاز قيد الاستخدام."</string> <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"يجب تعزيز الأمان. لم يُستخدَم رقم PIN لبعض الوقت."</string> <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"يجب تعزيز الأمان. لم تستخدَم كلمة المرور لبعض الوقت."</string> diff --git a/packages/SystemUI/res-keyguard/values-ru/strings.xml b/packages/SystemUI/res-keyguard/values-ru/strings.xml index fd90d08caf9e..cf2057cafb8b 100644 --- a/packages/SystemUI/res-keyguard/values-ru/strings.xml +++ b/packages/SystemUI/res-keyguard/values-ru/strings.xml @@ -57,7 +57,7 @@ <string name="kg_wrong_pin" msgid="4160978845968732624">"Неверный PIN-код"</string> <string name="kg_wrong_pin_try_again" msgid="3129729383303430190">"Неверный PIN-код."</string> <string name="kg_wrong_input_try_fp_suggestion" msgid="3143861542242024833">"Повторите попытку или используйте отпечаток пальца."</string> - <string name="kg_fp_not_recognized" msgid="5183108260932029241">"Отпечаток не распознан."</string> + <string name="kg_fp_not_recognized" msgid="5183108260932029241">"Отпечаток не распознан"</string> <string name="bouncer_face_not_recognized" msgid="1666128054475597485">"Лицо не распознано."</string> <string name="kg_bio_try_again_or_pin" msgid="4752168242723808390">"Повторите попытку или введите PIN-код."</string> <string name="kg_bio_try_again_or_password" msgid="1473132729225398039">"Повторите попытку или введите пароль."</string> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index f0c8894d7d36..823ff9f54be3 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -1053,4 +1053,15 @@ <!-- List of packages for which we want to use activity info (instead of application info) for biometric prompt logo. Empty for AOSP. [DO NOT TRANSLATE] --> <string-array name="config_useActivityLogoForBiometricPrompt" translatable="false"/> + + <!-- + Whether to enable the desktop specific feature set. + + Refrain from using this from code that needs to make decisions + regarding the size or density of the display. + + Variant owners and OEMs should override this to true when they want to + enable the desktop specific features. + --> + <bool name="config_enableDesktopFeatureSet">false</bool> </resources> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 0da252da5cc9..60fff282d041 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -104,6 +104,7 @@ import android.util.SparseBooleanArray; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.compose.animation.scene.ObservableTransitionState; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.foldables.FoldGracePeriodProvider; import com.android.internal.jank.InteractionJankMonitor; @@ -119,6 +120,7 @@ import com.android.systemui.CoreStartable; import com.android.systemui.Dumpable; import com.android.systemui.biometrics.AuthController; import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider; +import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; @@ -140,6 +142,9 @@ import com.android.systemui.log.SessionTracker; import com.android.systemui.plugins.clocks.WeatherData; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.res.R; +import com.android.systemui.scene.domain.interactor.SceneInteractor; +import com.android.systemui.scene.shared.flag.SceneContainerFlag; +import com.android.systemui.scene.shared.model.Scenes; import com.android.systemui.settings.UserTracker; import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.shared.system.TaskStackChangeListeners; @@ -150,6 +155,7 @@ import com.android.systemui.statusbar.policy.DevicePostureController.DevicePostu import com.android.systemui.telephony.TelephonyListenerManager; import com.android.systemui.user.domain.interactor.SelectedUserInteractor; import com.android.systemui.util.Assert; +import com.android.systemui.util.kotlin.JavaAdapter; import dalvik.annotation.optimization.NeverCompile; @@ -279,6 +285,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private final UserTracker mUserTracker; private final KeyguardUpdateMonitorLogger mLogger; private final boolean mIsSystemUser; + private final Provider<JavaAdapter> mJavaAdapter; + private final Provider<SceneInteractor> mSceneInteractor; + private final Provider<AlternateBouncerInteractor> mAlternateBouncerInteractor; private final AuthController mAuthController; private final UiEventLogger mUiEventLogger; private final Set<String> mAllowFingerprintOnOccludingActivitiesFromPackage; @@ -563,7 +572,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab */ private boolean shouldDismissKeyguardOnTrustGrantedWithCurrentUser(TrustGrantFlags flags) { final boolean isBouncerShowing = - mPrimaryBouncerIsOrWillBeShowing || mAlternateBouncerShowing; + isPrimaryBouncerShowingOrWillBeShowing() || isAlternateBouncerShowing(); return (flags.isInitiatedByUser() || flags.dismissKeyguardRequested()) && (mDeviceInteractive || flags.temporaryAndRenewable()) && (isBouncerShowing || flags.dismissKeyguardRequested()); @@ -1170,8 +1179,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab Assert.isMainThread(); String reason = mKeyguardBypassController.canBypass() ? "bypass" - : mAlternateBouncerShowing ? "alternateBouncer" - : mPrimaryBouncerFullyShown ? "bouncer" + : isAlternateBouncerShowing() ? "alternateBouncer" + : isPrimaryBouncerFullyShown() ? "bouncer" : "udfpsFpDown"; requestActiveUnlock( ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL, @@ -2169,7 +2178,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab Optional<FingerprintInteractiveToAuthProvider> interactiveToAuthProvider, TaskStackChangeListeners taskStackChangeListeners, SelectedUserInteractor selectedUserInteractor, - IActivityTaskManager activityTaskManagerService) { + IActivityTaskManager activityTaskManagerService, + Provider<AlternateBouncerInteractor> alternateBouncerInteractor, + Provider<JavaAdapter> javaAdapter, + Provider<SceneInteractor> sceneInteractor) { mContext = context; mSubscriptionManager = subscriptionManager; mUserTracker = userTracker; @@ -2214,6 +2226,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mFingerprintInteractiveToAuthProvider = interactiveToAuthProvider.orElse(null); mIsSystemUser = mUserManager.isSystemUser(); + mAlternateBouncerInteractor = alternateBouncerInteractor; + mJavaAdapter = javaAdapter; + mSceneInteractor = sceneInteractor; mHandler = new Handler(mainLooper) { @Override @@ -2470,6 +2485,30 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mContext.getContentResolver().registerContentObserver( Settings.System.getUriFor(Settings.System.TIME_12_24), false, mTimeFormatChangeObserver, UserHandle.USER_ALL); + + if (SceneContainerFlag.isEnabled()) { + mJavaAdapter.get().alwaysCollectFlow( + mAlternateBouncerInteractor.get().isVisible(), + this::onAlternateBouncerVisibilityChange); + mJavaAdapter.get().alwaysCollectFlow( + mSceneInteractor.get().getTransitionState(), + this::onTransitionStateChanged + ); + } + } + + @VisibleForTesting + void onAlternateBouncerVisibilityChange(boolean isAlternateBouncerVisible) { + setAlternateBouncerShowing(isAlternateBouncerVisible); + } + + + @VisibleForTesting + void onTransitionStateChanged(ObservableTransitionState transitionState) { + int primaryBouncerFullyShown = isPrimaryBouncerFullyShown(transitionState) ? 1 : 0; + int primaryBouncerIsOrWillBeShowing = + isPrimaryBouncerShowingOrWillBeShowing(transitionState) ? 1 : 0; + handlePrimaryBouncerChanged(primaryBouncerIsOrWillBeShowing, primaryBouncerFullyShown); } private void initializeSimState() { @@ -2717,8 +2756,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab requestActiveUnlock( requestOrigin, extraReason, canFaceBypass - || mAlternateBouncerShowing - || mPrimaryBouncerFullyShown + || isAlternateBouncerShowing() + || isPrimaryBouncerFullyShown() || mAuthController.isUdfpsFingerDown()); } @@ -2739,7 +2778,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab */ public void setAlternateBouncerShowing(boolean showing) { mAlternateBouncerShowing = showing; - if (mAlternateBouncerShowing) { + if (isAlternateBouncerShowing()) { requestActiveUnlock( ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT, "alternateBouncer"); @@ -2747,6 +2786,45 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE); } + private boolean isAlternateBouncerShowing() { + if (SceneContainerFlag.isEnabled()) { + return mAlternateBouncerInteractor.get().isVisibleState(); + } else { + return mAlternateBouncerShowing; + } + } + + private boolean isPrimaryBouncerShowingOrWillBeShowing() { + if (SceneContainerFlag.isEnabled()) { + return isPrimaryBouncerShowingOrWillBeShowing( + mSceneInteractor.get().getTransitionState().getValue()); + } else { + return mPrimaryBouncerIsOrWillBeShowing; + } + } + + private boolean isPrimaryBouncerFullyShown() { + if (SceneContainerFlag.isEnabled()) { + return isPrimaryBouncerFullyShown( + mSceneInteractor.get().getTransitionState().getValue()); + } else { + return mPrimaryBouncerFullyShown; + } + } + + private boolean isPrimaryBouncerShowingOrWillBeShowing( + ObservableTransitionState transitionState + ) { + SceneContainerFlag.assertInNewMode(); + return isPrimaryBouncerFullyShown(transitionState) + || transitionState.isTransitioning(null, Scenes.Bouncer); + } + + private boolean isPrimaryBouncerFullyShown(ObservableTransitionState transitionState) { + SceneContainerFlag.assertInNewMode(); + return transitionState.isIdle(Scenes.Bouncer); + } + /** * If the current state of the device allows for triggering active unlock. This does not * include active unlock availability. @@ -2762,7 +2840,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private boolean shouldTriggerActiveUnlock(boolean shouldLog) { // Triggers: final boolean triggerActiveUnlockForAssistant = shouldTriggerActiveUnlockForAssistant(); - final boolean awakeKeyguard = mPrimaryBouncerFullyShown || mAlternateBouncerShowing + final boolean awakeKeyguard = isPrimaryBouncerFullyShown() || isAlternateBouncerShowing() || (isKeyguardVisible() && !mGoingToSleep && mStatusBarState != StatusBarState.SHADE_LOCKED); @@ -2830,14 +2908,14 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab final boolean shouldListenKeyguardState = isKeyguardVisible() || !mDeviceInteractive - || (mPrimaryBouncerIsOrWillBeShowing && !mKeyguardGoingAway) + || (isPrimaryBouncerShowingOrWillBeShowing() && !mKeyguardGoingAway) || mGoingToSleep || shouldListenForFingerprintAssistant || (mKeyguardOccluded && mIsDreaming) || (mKeyguardOccluded && userDoesNotHaveTrust && mKeyguardShowing && (mOccludingAppRequestingFp || isUdfps - || mAlternateBouncerShowing + || isAlternateBouncerShowing() || mAllowFingerprintOnCurrentOccludingActivity ) ); @@ -2856,7 +2934,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab && !isUserInLockdown(user); final boolean strongerAuthRequired = !isUnlockingWithFingerprintAllowed(); final boolean shouldListenBouncerState = - !strongerAuthRequired || !mPrimaryBouncerIsOrWillBeShowing; + !strongerAuthRequired || !isPrimaryBouncerShowingOrWillBeShowing(); final boolean shouldListenUdfpsState = !isUdfps || (!userCanSkipBouncer @@ -2872,10 +2950,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab user, shouldListen, mAllowFingerprintOnCurrentOccludingActivity, - mAlternateBouncerShowing, + isAlternateBouncerShowing(), biometricEnabledForUser, mBiometricPromptShowing, - mPrimaryBouncerIsOrWillBeShowing, + isPrimaryBouncerShowingOrWillBeShowing(), userCanSkipBouncer, mCredentialAttempted, mDeviceInteractive, @@ -3614,6 +3692,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab */ public void sendPrimaryBouncerChanged(boolean primaryBouncerIsOrWillBeShowing, boolean primaryBouncerFullyShown) { + SceneContainerFlag.assertInLegacyMode(); mLogger.logSendPrimaryBouncerChanged(primaryBouncerIsOrWillBeShowing, primaryBouncerFullyShown); Message message = mHandler.obtainMessage(MSG_KEYGUARD_BOUNCER_CHANGED); @@ -4031,10 +4110,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab if (isUdfpsSupported()) { pw.println(" udfpsEnrolled=" + isUdfpsEnrolled()); pw.println(" shouldListenForUdfps=" + shouldListenForFingerprint(true)); - pw.println(" mPrimaryBouncerIsOrWillBeShowing=" - + mPrimaryBouncerIsOrWillBeShowing); + pw.println(" isPrimaryBouncerShowingOrWillBeShowing=" + + isPrimaryBouncerShowingOrWillBeShowing()); pw.println(" mStatusBarState=" + StatusBarState.toString(mStatusBarState)); - pw.println(" mAlternateBouncerShowing=" + mAlternateBouncerShowing); + pw.println(" isAlternateBouncerShowing=" + isAlternateBouncerShowing()); } else if (isSfpsSupported()) { pw.println(" sfpsEnrolled=" + isSfpsEnrolled()); pw.println(" shouldListenForSfps=" + shouldListenForFingerprint(false)); diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index baf8f5aeba29..a30115568842 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -49,7 +49,6 @@ import com.android.systemui.statusbar.notification.stack.AmbientState; import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager; import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.statusbar.phone.ScreenOffAnimationController; -import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider; import com.android.systemui.statusbar.phone.SystemUIDialogManager; import com.android.systemui.statusbar.policy.BluetoothController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; @@ -140,7 +139,6 @@ public class Dependency { @Inject Lazy<SysUiState> mSysUiStateFlagsContainer; @Inject Lazy<CommandQueue> mCommandQueue; @Inject Lazy<UiEventLogger> mUiEventLogger; - @Inject Lazy<StatusBarContentInsetsProvider> mContentInsetsProviderLazy; @Inject Lazy<FeatureFlags> mFeatureFlagsLazy; @Inject Lazy<NotificationSectionsManager> mNotificationSectionsManagerLazy; @Inject Lazy<ScreenOffAnimationController> mScreenOffAnimationController; @@ -186,7 +184,6 @@ public class Dependency { mProviders.put(CommandQueue.class, mCommandQueue::get); mProviders.put(UiEventLogger.class, mUiEventLogger::get); mProviders.put(FeatureFlags.class, mFeatureFlagsLazy::get); - mProviders.put(StatusBarContentInsetsProvider.class, mContentInsetsProviderLazy::get); mProviders.put(NotificationSectionsManager.class, mNotificationSectionsManagerLazy::get); mProviders.put(ScreenOffAnimationController.class, mScreenOffAnimationController::get); mProviders.put(AmbientState.class, mAmbientStateLazy::get); diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java index 63ad41a808dc..13cd2c538f3f 100644 --- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java @@ -53,6 +53,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.res.R; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.shared.NotificationContentAlphaOptimization; import com.android.wm.shell.animation.FlingAnimationUtils; import com.android.wm.shell.shared.animation.PhysicsAnimator; import com.android.wm.shell.shared.animation.PhysicsAnimator.SpringConfig; @@ -63,13 +64,6 @@ import java.util.function.Consumer; public class SwipeHelper implements Gefingerpoken, Dumpable { static final String TAG = "com.android.systemui.SwipeHelper"; private static final boolean DEBUG_INVALIDATE = false; - private static final boolean CONSTRAIN_SWIPE = true; - private static final boolean FADE_OUT_DURING_SWIPE = true; - private static final boolean DISMISS_IF_SWIPED_FAR_ENOUGH = true; - - public static final int X = 0; - public static final int Y = 1; - private static final float SWIPE_ESCAPE_VELOCITY = 500f; // dp/sec private static final int DEFAULT_ESCAPE_ANIMATION_DURATION = 200; // ms private static final int MAX_ESCAPE_ANIMATION_DURATION = 400; // ms @@ -171,10 +165,6 @@ public class SwipeHelper implements Gefingerpoken, Dumpable { mPagingTouchSlop = pagingTouchSlop; } - public void setDisableHardwareLayers(boolean disableHwLayers) { - mDisableHwLayers = disableHwLayers; - } - private float getPos(MotionEvent ev) { return ev.getX(); } @@ -253,13 +243,14 @@ public class SwipeHelper implements Gefingerpoken, Dumpable { float translation) { float swipeProgress = getSwipeProgressForOffset(animView, translation); if (!mCallback.updateSwipeProgress(animView, dismissable, swipeProgress)) { - if (FADE_OUT_DURING_SWIPE && dismissable) { - if (!mDisableHwLayers) { - if (swipeProgress != 0f && swipeProgress != 1f) { - animView.setLayerType(View.LAYER_TYPE_HARDWARE, null); - } else { - animView.setLayerType(View.LAYER_TYPE_NONE, null); - } + if (dismissable + || (NotificationContentAlphaOptimization.isEnabled() && translation == 0)) { + // We need to reset the content alpha even when the view is not dismissible (eg. + // when Guts is visible) + if (swipeProgress != 0f && swipeProgress != 1f) { + animView.setLayerType(View.LAYER_TYPE_HARDWARE, null); + } else { + animView.setLayerType(View.LAYER_TYPE_NONE, null); } updateSwipeProgressAlpha(animView, getSwipeAlpha(swipeProgress)); } @@ -436,9 +427,7 @@ public class SwipeHelper implements Gefingerpoken, Dumpable { duration = fixedDuration; } - if (!mDisableHwLayers) { - animView.setLayerType(View.LAYER_TYPE_HARDWARE, null); - } + animView.setLayerType(View.LAYER_TYPE_HARDWARE, null); AnimatorUpdateListener updateListener = new AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { @@ -493,9 +482,7 @@ public class SwipeHelper implements Gefingerpoken, Dumpable { if (endAction != null) { endAction.accept(mCancelled); } - if (!mDisableHwLayers) { - animView.setLayerType(View.LAYER_TYPE_NONE, null); - } + animView.setLayerType(View.LAYER_TYPE_NONE, null); onDismissChildWithAnimationFinished(); } }); @@ -612,7 +599,11 @@ public class SwipeHelper implements Gefingerpoken, Dumpable { * view is being animated to dismiss or snap. */ public void onTranslationUpdate(View animView, float value, boolean canBeDismissed) { - updateSwipeProgressFromOffset(animView, canBeDismissed, value); + updateSwipeProgressFromOffset( + animView, + /* dismissable= */ canBeDismissed, + /* translation= */ value + ); } private void snapChildInstantly(final View view) { @@ -689,7 +680,7 @@ public class SwipeHelper implements Gefingerpoken, Dumpable { } else { // don't let items that can't be dismissed be dragged more than // maxScrollDistance - if (CONSTRAIN_SWIPE && !mCallback.canChildBeDismissedInDirection( + if (!mCallback.canChildBeDismissedInDirection( mTouchedView, delta > 0)) { float size = getSize(mTouchedView); @@ -761,8 +752,7 @@ public class SwipeHelper implements Gefingerpoken, Dumpable { protected boolean swipedFarEnough() { float translation = getTranslation(mTouchedView); - return DISMISS_IF_SWIPED_FAR_ENOUGH - && Math.abs(translation) > SWIPED_FAR_ENOUGH_SIZE_FRACTION * getSize( + return Math.abs(translation) > SWIPED_FAR_ENOUGH_SIZE_FRACTION * getSize( mTouchedView); } @@ -822,9 +812,18 @@ public class SwipeHelper implements Gefingerpoken, Dumpable { } public void forceResetSwipeState(@NonNull View view) { - if (view.getTranslationX() == 0) return; + if (view.getTranslationX() == 0 + && (!NotificationContentAlphaOptimization.isEnabled() || view.getAlpha() == 1f) + ) { + // Don't do anything when translation is 0 and alpha is 1 + return; + } setTranslation(view, 0); - updateSwipeProgressFromOffset(view, /* dismissable= */ true, 0); + updateSwipeProgressFromOffset( + view, + /* dismissable= */ true, + /* translation= */ 0 + ); } /** This method resets the swipe state, and if `resetAll` is true, also resets the snap state */ @@ -893,7 +892,6 @@ public class SwipeHelper implements Gefingerpoken, Dumpable { pw.append("mTranslation=").println(mTranslation); pw.append("mCanCurrViewBeDimissed=").println(mCanCurrViewBeDimissed); pw.append("mMenuRowIntercepting=").println(mMenuRowIntercepting); - pw.append("mDisableHwLayers=").println(mDisableHwLayers); pw.append("mDismissPendingMap: ").println(mDismissPendingMap.size()); if (!mDismissPendingMap.isEmpty()) { mDismissPendingMap.forEach((view, animator) -> { diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java index d81a6862c1c1..6c46318afe47 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java @@ -415,17 +415,13 @@ public class MagnificationImpl implements Magnification, CommandQueue.Callbacks @Override @MainThread public void showMagnificationButton(int displayId, int magnificationMode) { - if (Flags.delayShowMagnificationButton()) { - if (mHandler.hasMessages(MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL)) { - return; - } - mHandler.sendMessageDelayed( - mHandler.obtainMessage( - MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL, displayId, magnificationMode), - DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS); - } else { - showMagnificationButtonInternal(displayId, magnificationMode); + if (mHandler.hasMessages(MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL)) { + return; } + mHandler.sendMessageDelayed( + mHandler.obtainMessage( + MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL, displayId, magnificationMode), + DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS); } @MainThread @@ -441,9 +437,7 @@ public class MagnificationImpl implements Magnification, CommandQueue.Callbacks @Override @MainThread public void removeMagnificationButton(int displayId) { - if (Flags.delayShowMagnificationButton()) { - mHandler.removeMessages(MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL); - } + mHandler.removeMessages(MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL); mModeSwitchesController.removeButton(displayId); } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerViewModule.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerViewModule.kt index aebc50f92e8d..34107821341d 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerViewModule.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerViewModule.kt @@ -18,8 +18,6 @@ package com.android.systemui.bouncer.ui import android.app.AlertDialog import android.content.Context -import com.android.systemui.bouncer.ui.viewmodel.BouncerMessageViewModelModule -import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModelModule import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.statusbar.phone.SystemUIDialog @@ -27,13 +25,7 @@ import dagger.Binds import dagger.Module import dagger.Provides -@Module( - includes = - [ - BouncerViewModelModule::class, - BouncerMessageViewModelModule::class, - ], -) +@Module interface BouncerViewModule { /** Binds BouncerView to BouncerViewImpl and makes it injectable. */ @Binds fun bindBouncerView(bouncerViewImpl: BouncerViewImpl): BouncerView diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt index 78811a96a026..ad93a25f39a5 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt @@ -9,7 +9,7 @@ import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags import com.android.systemui.bouncer.ui.BouncerDialogFactory -import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel +import com.android.systemui.bouncer.ui.viewmodel.BouncerSceneContentViewModel import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel @@ -40,7 +40,7 @@ data class ComposeBouncerDependencies @Inject constructor( val legacyInteractor: PrimaryBouncerInteractor, - val viewModel: BouncerViewModel, + val viewModelFactory: BouncerSceneContentViewModel.Factory, val dialogFactory: BouncerDialogFactory, val authenticationInteractor: AuthenticationInteractor, val viewMediatorCallback: ViewMediatorCallback?, @@ -65,7 +65,7 @@ constructor( ComposeBouncerViewBinder.bind( view, deps.legacyInteractor, - deps.viewModel, + deps.viewModelFactory, deps.dialogFactory, deps.authenticationInteractor, deps.selectedUserInteractor, diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt index eaca2767a2e8..c1f7d590d08e 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt @@ -14,7 +14,8 @@ import com.android.systemui.authentication.domain.interactor.AuthenticationInter import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.bouncer.ui.BouncerDialogFactory import com.android.systemui.bouncer.ui.composable.BouncerContent -import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel +import com.android.systemui.bouncer.ui.viewmodel.BouncerSceneContentViewModel +import com.android.systemui.lifecycle.rememberViewModel import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.user.domain.interactor.SelectedUserInteractor import kotlinx.coroutines.flow.collectLatest @@ -25,7 +26,7 @@ object ComposeBouncerViewBinder { fun bind( view: ViewGroup, legacyInteractor: PrimaryBouncerInteractor, - viewModel: BouncerViewModel, + viewModelFactory: BouncerSceneContentViewModel.Factory, dialogFactory: BouncerDialogFactory, authenticationInteractor: AuthenticationInteractor, selectedUserInteractor: SelectedUserInteractor, @@ -48,7 +49,14 @@ object ComposeBouncerViewBinder { this@repeatWhenAttached.lifecycle } ) - setContent { PlatformTheme { BouncerContent(viewModel, dialogFactory) } } + setContent { + PlatformTheme { + BouncerContent( + rememberViewModel { viewModelFactory.create() }, + dialogFactory, + ) + } + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt index 4fbf735a62a2..e7dd974c44e5 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt @@ -17,17 +17,18 @@ package com.android.systemui.bouncer.ui.viewmodel import android.annotation.StringRes +import com.android.app.tracing.coroutines.flow.collectLatest import com.android.systemui.authentication.domain.interactor.AuthenticationResult import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.bouncer.domain.interactor.BouncerInteractor -import kotlinx.coroutines.CoroutineScope +import com.android.systemui.lifecycle.SysUiViewModel +import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.launch +import kotlinx.coroutines.flow.receiveAsFlow sealed class AuthMethodBouncerViewModel( - protected val viewModelScope: CoroutineScope, protected val interactor: BouncerInteractor, /** @@ -37,7 +38,7 @@ sealed class AuthMethodBouncerViewModel( * being able to attempt to unlock the device. */ val isInputEnabled: StateFlow<Boolean>, -) { +) : SysUiViewModel() { private val _animateFailure = MutableStateFlow(false) /** @@ -57,6 +58,29 @@ sealed class AuthMethodBouncerViewModel( */ @get:StringRes abstract val lockoutMessageId: Int + private val authenticationRequests = Channel<AuthenticationRequest>(Channel.BUFFERED) + + override suspend fun onActivated() { + authenticationRequests.receiveAsFlow().collectLatest { request -> + if (!isInputEnabled.value) { + return@collectLatest + } + + val authenticationResult = + interactor.authenticate( + input = request.input, + tryAutoConfirm = request.useAutoConfirm, + ) + + if (authenticationResult == AuthenticationResult.SKIPPED && request.useAutoConfirm) { + return@collectLatest + } + + _animateFailure.value = authenticationResult != AuthenticationResult.SUCCEEDED + clearInput() + } + } + /** * Notifies that the UI has been hidden from the user (after any transitions have completed). */ @@ -92,14 +116,11 @@ sealed class AuthMethodBouncerViewModel( input: List<Any> = getInput(), useAutoConfirm: Boolean = false, ) { - viewModelScope.launch { - val authenticationResult = interactor.authenticate(input, useAutoConfirm) - if (authenticationResult == AuthenticationResult.SKIPPED && useAutoConfirm) { - return@launch - } - _animateFailure.value = authenticationResult != AuthenticationResult.SUCCEEDED - - clearInput() - } + authenticationRequests.trySend(AuthenticationRequest(input, useAutoConfirm)) } + + private data class AuthenticationRequest( + val input: List<Any>, + val useAutoConfirm: Boolean, + ) } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt index 31479f131ba3..c3215b4ada9e 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt @@ -27,7 +27,6 @@ import com.android.systemui.bouncer.shared.model.BouncerMessagePair import com.android.systemui.bouncer.shared.model.BouncerMessageStrings import com.android.systemui.bouncer.shared.model.primaryMessage import com.android.systemui.bouncer.shared.model.secondaryMessage -import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.deviceentry.domain.interactor.BiometricMessageInteractor import com.android.systemui.deviceentry.domain.interactor.DeviceEntryBiometricsAllowedInteractor @@ -39,19 +38,19 @@ import com.android.systemui.deviceentry.shared.model.FaceLockoutMessage import com.android.systemui.deviceentry.shared.model.FaceTimeoutMessage import com.android.systemui.deviceentry.shared.model.FingerprintFailureMessage import com.android.systemui.deviceentry.shared.model.FingerprintLockoutMessage +import com.android.systemui.lifecycle.SysUiViewModel import com.android.systemui.res.R.string.kg_too_many_failed_attempts_countdown import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel -import com.android.systemui.user.ui.viewmodel.UserViewModel import com.android.systemui.util.kotlin.Utils.Companion.sample import com.android.systemui.util.time.SystemClock -import dagger.Module -import dagger.Provides +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import kotlin.math.ceil import kotlin.math.max import kotlin.time.Duration.Companion.seconds -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow @@ -65,20 +64,21 @@ import kotlinx.coroutines.launch /** Holds UI state for the 2-line status message shown on the bouncer. */ @OptIn(ExperimentalCoroutinesApi::class) -class BouncerMessageViewModel( +class BouncerMessageViewModel +@AssistedInject +constructor( @Application private val applicationContext: Context, - @Application private val applicationScope: CoroutineScope, private val bouncerInteractor: BouncerInteractor, private val simBouncerInteractor: SimBouncerInteractor, private val authenticationInteractor: AuthenticationInteractor, - selectedUser: Flow<UserViewModel>, + private val userSwitcherViewModel: UserSwitcherViewModel, private val clock: SystemClock, private val biometricMessageInteractor: BiometricMessageInteractor, private val faceAuthInteractor: DeviceEntryFaceAuthInteractor, private val deviceUnlockedInteractor: DeviceUnlockedInteractor, private val deviceEntryBiometricsAllowedInteractor: DeviceEntryBiometricsAllowedInteractor, - flags: ComposeBouncerFlags, -) { + private val flags: ComposeBouncerFlags, +) : SysUiViewModel() { /** * A message shown when the user has attempted the wrong credential too many times and now must * wait a while before attempting to authenticate again. @@ -94,6 +94,25 @@ class BouncerMessageViewModel( /** The user-facing message to show in the bouncer. */ val message: MutableStateFlow<MessageViewModel?> = MutableStateFlow(null) + override suspend fun onActivated() { + if (!flags.isComposeBouncerOrSceneContainerEnabled()) { + return + } + + coroutineScope { + launch { + // Update the lockout countdown whenever the selected user is switched. + userSwitcherViewModel.selectedUser.collect { startLockoutCountdown() } + } + + launch { defaultBouncerMessageInitializer() } + launch { listenForSimBouncerEvents() } + launch { listenForBouncerEvents() } + launch { listenForFaceMessages() } + launch { listenForFingerprintMessages() } + } + } + /** Initializes the bouncer message to default whenever it is shown. */ fun onShown() { showDefaultMessage() @@ -108,173 +127,161 @@ class BouncerMessageViewModel( private var lockoutCountdownJob: Job? = null - private fun defaultBouncerMessageInitializer() { - applicationScope.launch { - resetToDefault.emit(Unit) - authenticationInteractor.authenticationMethod - .flatMapLatest { authMethod -> - if (authMethod == AuthenticationMethodModel.Sim) { - resetToDefault.map { - MessageViewModel(simBouncerInteractor.getDefaultMessage()) - } - } else if (authMethod.isSecure) { - combine( - deviceUnlockedInteractor.deviceEntryRestrictionReason, - lockoutMessage, - deviceEntryBiometricsAllowedInteractor - .isFingerprintCurrentlyAllowedOnBouncer, - resetToDefault, - ) { deviceEntryRestrictedReason, lockoutMsg, isFpAllowedInBouncer, _ -> - lockoutMsg - ?: deviceEntryRestrictedReason.toMessage( - authMethod, - isFpAllowedInBouncer - ) - } - } else { - emptyFlow() + private suspend fun defaultBouncerMessageInitializer() { + resetToDefault.emit(Unit) + authenticationInteractor.authenticationMethod + .flatMapLatest { authMethod -> + if (authMethod == AuthenticationMethodModel.Sim) { + resetToDefault.map { + MessageViewModel(simBouncerInteractor.getDefaultMessage()) } + } else if (authMethod.isSecure) { + combine( + deviceUnlockedInteractor.deviceEntryRestrictionReason, + lockoutMessage, + deviceEntryBiometricsAllowedInteractor + .isFingerprintCurrentlyAllowedOnBouncer, + resetToDefault, + ) { deviceEntryRestrictedReason, lockoutMsg, isFpAllowedInBouncer, _ -> + lockoutMsg + ?: deviceEntryRestrictedReason.toMessage( + authMethod, + isFpAllowedInBouncer + ) + } + } else { + emptyFlow() } - .collectLatest { messageViewModel -> message.value = messageViewModel } - } + } + .collectLatest { messageViewModel -> message.value = messageViewModel } } - private fun listenForSimBouncerEvents() { + private suspend fun listenForSimBouncerEvents() { // Listen for any events from the SIM bouncer and update the message shown on the bouncer. - applicationScope.launch { - authenticationInteractor.authenticationMethod - .flatMapLatest { authMethod -> - if (authMethod == AuthenticationMethodModel.Sim) { - simBouncerInteractor.bouncerMessageChanged.map { simMsg -> - simMsg?.let { MessageViewModel(it) } - } - } else { - emptyFlow() + authenticationInteractor.authenticationMethod + .flatMapLatest { authMethod -> + if (authMethod == AuthenticationMethodModel.Sim) { + simBouncerInteractor.bouncerMessageChanged.map { simMsg -> + simMsg?.let { MessageViewModel(it) } } + } else { + emptyFlow() } - .collectLatest { - if (it != null) { - message.value = it - } else { - resetToDefault.emit(Unit) - } + } + .collectLatest { + if (it != null) { + message.value = it + } else { + resetToDefault.emit(Unit) } - } + } } - private fun listenForFaceMessages() { + private suspend fun listenForFaceMessages() { // Listen for any events from face authentication and update the message shown on the // bouncer. - applicationScope.launch { - biometricMessageInteractor.faceMessage - .sample( - authenticationInteractor.authenticationMethod, - deviceEntryBiometricsAllowedInteractor.isFingerprintCurrentlyAllowedOnBouncer, - ) - .collectLatest { (faceMessage, authMethod, fingerprintAllowedOnBouncer) -> - val isFaceAuthStrong = faceAuthInteractor.isFaceAuthStrong() - val defaultPrimaryMessage = - BouncerMessageStrings.defaultMessage( - authMethod, - fingerprintAllowedOnBouncer + biometricMessageInteractor.faceMessage + .sample( + authenticationInteractor.authenticationMethod, + deviceEntryBiometricsAllowedInteractor.isFingerprintCurrentlyAllowedOnBouncer, + ) + .collectLatest { (faceMessage, authMethod, fingerprintAllowedOnBouncer) -> + val isFaceAuthStrong = faceAuthInteractor.isFaceAuthStrong() + val defaultPrimaryMessage = + BouncerMessageStrings.defaultMessage(authMethod, fingerprintAllowedOnBouncer) + .primaryMessage + .toResString() + message.value = + when (faceMessage) { + is FaceTimeoutMessage -> + MessageViewModel( + text = defaultPrimaryMessage, + secondaryText = faceMessage.message, + isUpdateAnimated = true ) - .primaryMessage - .toResString() - message.value = - when (faceMessage) { - is FaceTimeoutMessage -> - MessageViewModel( - text = defaultPrimaryMessage, - secondaryText = faceMessage.message, - isUpdateAnimated = true - ) - is FaceLockoutMessage -> - if (isFaceAuthStrong) - BouncerMessageStrings.class3AuthLockedOut(authMethod) - .toMessage() - else - BouncerMessageStrings.faceLockedOut( - authMethod, - fingerprintAllowedOnBouncer - ) - .toMessage() - is FaceFailureMessage -> - BouncerMessageStrings.incorrectFaceInput( + is FaceLockoutMessage -> + if (isFaceAuthStrong) + BouncerMessageStrings.class3AuthLockedOut(authMethod).toMessage() + else + BouncerMessageStrings.faceLockedOut( authMethod, fingerprintAllowedOnBouncer ) .toMessage() - else -> - MessageViewModel( - text = defaultPrimaryMessage, - secondaryText = faceMessage.message, - isUpdateAnimated = false + is FaceFailureMessage -> + BouncerMessageStrings.incorrectFaceInput( + authMethod, + fingerprintAllowedOnBouncer ) - } - delay(MESSAGE_DURATION) - resetToDefault.emit(Unit) - } - } + .toMessage() + else -> + MessageViewModel( + text = defaultPrimaryMessage, + secondaryText = faceMessage.message, + isUpdateAnimated = false + ) + } + delay(MESSAGE_DURATION) + resetToDefault.emit(Unit) + } } - private fun listenForFingerprintMessages() { - applicationScope.launch { - // Listen for any events from fingerprint authentication and update the message shown - // on the bouncer. - biometricMessageInteractor.fingerprintMessage - .sample( - authenticationInteractor.authenticationMethod, - deviceEntryBiometricsAllowedInteractor.isFingerprintCurrentlyAllowedOnBouncer - ) - .collectLatest { (fingerprintMessage, authMethod, isFingerprintAllowed) -> - val defaultPrimaryMessage = - BouncerMessageStrings.defaultMessage(authMethod, isFingerprintAllowed) - .primaryMessage - .toResString() - message.value = - when (fingerprintMessage) { - is FingerprintLockoutMessage -> - BouncerMessageStrings.class3AuthLockedOut(authMethod).toMessage() - is FingerprintFailureMessage -> - BouncerMessageStrings.incorrectFingerprintInput(authMethod) - .toMessage() - else -> - MessageViewModel( - text = defaultPrimaryMessage, - secondaryText = fingerprintMessage.message, - isUpdateAnimated = false - ) - } - delay(MESSAGE_DURATION) - resetToDefault.emit(Unit) - } - } + private suspend fun listenForFingerprintMessages() { + // Listen for any events from fingerprint authentication and update the message shown + // on the bouncer. + biometricMessageInteractor.fingerprintMessage + .sample( + authenticationInteractor.authenticationMethod, + deviceEntryBiometricsAllowedInteractor.isFingerprintCurrentlyAllowedOnBouncer + ) + .collectLatest { (fingerprintMessage, authMethod, isFingerprintAllowed) -> + val defaultPrimaryMessage = + BouncerMessageStrings.defaultMessage(authMethod, isFingerprintAllowed) + .primaryMessage + .toResString() + message.value = + when (fingerprintMessage) { + is FingerprintLockoutMessage -> + BouncerMessageStrings.class3AuthLockedOut(authMethod).toMessage() + is FingerprintFailureMessage -> + BouncerMessageStrings.incorrectFingerprintInput(authMethod).toMessage() + else -> + MessageViewModel( + text = defaultPrimaryMessage, + secondaryText = fingerprintMessage.message, + isUpdateAnimated = false + ) + } + delay(MESSAGE_DURATION) + resetToDefault.emit(Unit) + } } - private fun listenForBouncerEvents() { - // Keeps the lockout message up-to-date. - applicationScope.launch { - bouncerInteractor.onLockoutStarted.collect { startLockoutCountdown() } - } + private suspend fun listenForBouncerEvents() { + coroutineScope { + // Keeps the lockout message up-to-date. + launch { bouncerInteractor.onLockoutStarted.collect { startLockoutCountdown() } } - // Listens to relevant bouncer events - applicationScope.launch { - bouncerInteractor.onIncorrectBouncerInput - .sample( - authenticationInteractor.authenticationMethod, - deviceEntryBiometricsAllowedInteractor.isFingerprintCurrentlyAllowedOnBouncer - ) - .collectLatest { (_, authMethod, isFingerprintAllowed) -> - message.emit( - BouncerMessageStrings.incorrectSecurityInput( - authMethod, - isFingerprintAllowed - ) - .toMessage() + // Listens to relevant bouncer events + launch { + bouncerInteractor.onIncorrectBouncerInput + .sample( + authenticationInteractor.authenticationMethod, + deviceEntryBiometricsAllowedInteractor + .isFingerprintCurrentlyAllowedOnBouncer ) - delay(MESSAGE_DURATION) - resetToDefault.emit(Unit) - } + .collectLatest { (_, authMethod, isFingerprintAllowed) -> + message.emit( + BouncerMessageStrings.incorrectSecurityInput( + authMethod, + isFingerprintAllowed + ) + .toMessage() + ) + delay(MESSAGE_DURATION) + resetToDefault.emit(Unit) + } + } } } @@ -323,10 +330,10 @@ class BouncerMessageViewModel( } /** Shows the countdown message and refreshes it every second. */ - private fun startLockoutCountdown() { + private suspend fun startLockoutCountdown() { lockoutCountdownJob?.cancel() - lockoutCountdownJob = - applicationScope.launch { + lockoutCountdownJob = coroutineScope { + launch { authenticationInteractor.authenticationMethod.collectLatest { authMethod -> do { val remainingSeconds = remainingLockoutSeconds() @@ -352,6 +359,7 @@ class BouncerMessageViewModel( lockoutCountdownJob = null } } + } } private fun remainingLockoutSeconds(): Int { @@ -365,20 +373,9 @@ class BouncerMessageViewModel( private fun Int.toResString(): String = applicationContext.getString(this) - init { - if (flags.isComposeBouncerOrSceneContainerEnabled()) { - applicationScope.launch { - // Update the lockout countdown whenever the selected user is switched. - selectedUser.collect { startLockoutCountdown() } - } - - defaultBouncerMessageInitializer() - - listenForSimBouncerEvents() - listenForBouncerEvents() - listenForFaceMessages() - listenForFingerprintMessages() - } + @AssistedFactory + interface Factory { + fun create(): BouncerMessageViewModel } companion object { @@ -398,40 +395,3 @@ data class MessageViewModel( */ val isUpdateAnimated: Boolean = true, ) - -@OptIn(ExperimentalCoroutinesApi::class) -@Module -object BouncerMessageViewModelModule { - - @Provides - @SysUISingleton - fun viewModel( - @Application applicationContext: Context, - @Application applicationScope: CoroutineScope, - bouncerInteractor: BouncerInteractor, - simBouncerInteractor: SimBouncerInteractor, - authenticationInteractor: AuthenticationInteractor, - clock: SystemClock, - biometricMessageInteractor: BiometricMessageInteractor, - faceAuthInteractor: DeviceEntryFaceAuthInteractor, - deviceUnlockedInteractor: DeviceUnlockedInteractor, - deviceEntryBiometricsAllowedInteractor: DeviceEntryBiometricsAllowedInteractor, - flags: ComposeBouncerFlags, - userSwitcherViewModel: UserSwitcherViewModel, - ): BouncerMessageViewModel { - return BouncerMessageViewModel( - applicationContext = applicationContext, - applicationScope = applicationScope, - bouncerInteractor = bouncerInteractor, - simBouncerInteractor = simBouncerInteractor, - authenticationInteractor = authenticationInteractor, - clock = clock, - biometricMessageInteractor = biometricMessageInteractor, - faceAuthInteractor = faceAuthInteractor, - deviceUnlockedInteractor = deviceUnlockedInteractor, - deviceEntryBiometricsAllowedInteractor = deviceEntryBiometricsAllowedInteractor, - flags = flags, - selectedUser = userSwitcherViewModel.selectedUser, - ) - } -} diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneActionsViewModel.kt new file mode 100644 index 000000000000..2a272714db37 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneActionsViewModel.kt @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.bouncer.ui.viewmodel + +import com.android.compose.animation.scene.Back +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.bouncer.domain.interactor.BouncerInteractor +import com.android.systemui.scene.ui.viewmodel.SceneActionsViewModel +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.map + +/** + * Models UI state for user actions that can lead to navigation to other scenes when showing the + * bouncer scene. + */ +class BouncerSceneActionsViewModel +@AssistedInject +constructor( + private val bouncerInteractor: BouncerInteractor, +) : SceneActionsViewModel() { + + override suspend fun hydrateActions(setActions: (Map<UserAction, UserActionResult>) -> Unit) { + bouncerInteractor.dismissDestination + .map { prevScene -> + mapOf( + Back to UserActionResult(prevScene), + Swipe(SwipeDirection.Down) to UserActionResult(prevScene), + ) + } + .collectLatest { actions -> setActions(actions) } + } + + @AssistedFactory + interface Factory { + fun create(): BouncerSceneActionsViewModel + } +} diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt index e2089bbb4504..aede63b0ac23 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt @@ -23,125 +23,62 @@ import android.graphics.Bitmap import androidx.compose.ui.input.key.KeyEvent import androidx.compose.ui.input.key.type import androidx.core.graphics.drawable.toBitmap -import com.android.compose.animation.scene.Back -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.authentication.domain.interactor.AuthenticationInteractor import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.authentication.shared.model.AuthenticationWipeModel import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor import com.android.systemui.bouncer.domain.interactor.BouncerInteractor -import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.shared.model.Text -import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.inputmethod.domain.interactor.InputMethodInteractor -import com.android.systemui.user.domain.interactor.SelectedUserInteractor -import com.android.systemui.user.ui.viewmodel.UserActionViewModel +import com.android.systemui.lifecycle.SysUiViewModel import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel -import com.android.systemui.user.ui.viewmodel.UserViewModel -import dagger.Module -import dagger.Provides -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.cancel -import kotlinx.coroutines.flow.Flow +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.stateIn -import kotlinx.coroutines.job import kotlinx.coroutines.launch -/** Holds UI state and handles user input on bouncer UIs. */ -class BouncerViewModel( +/** Models UI state for the content of the bouncer scene. */ +class BouncerSceneContentViewModel +@AssistedInject +constructor( @Application private val applicationContext: Context, - @Deprecated("TODO(b/354270224): remove this. Injecting CoroutineScope to view-models is banned") - @Application - private val applicationScope: CoroutineScope, - @Main private val mainDispatcher: CoroutineDispatcher, private val bouncerInteractor: BouncerInteractor, - private val inputMethodInteractor: InputMethodInteractor, - private val simBouncerInteractor: SimBouncerInteractor, private val authenticationInteractor: AuthenticationInteractor, - private val selectedUserInteractor: SelectedUserInteractor, private val devicePolicyManager: DevicePolicyManager, - bouncerMessageViewModel: BouncerMessageViewModel, - flags: ComposeBouncerFlags, - selectedUser: Flow<UserViewModel>, - users: Flow<List<UserViewModel>>, - userSwitcherMenu: Flow<List<UserActionViewModel>>, - actionButton: Flow<BouncerActionButtonModel?>, -) { - val selectedUserImage: StateFlow<Bitmap?> = - selectedUser - .map { it.image.toBitmap() } - .stateIn( - scope = applicationScope, - started = SharingStarted.WhileSubscribed(), - initialValue = null, - ) - - val destinationScenes: Flow<Map<UserAction, UserActionResult>> = - bouncerInteractor.dismissDestination.map { prevScene -> - mapOf( - Back to UserActionResult(prevScene), - Swipe(SwipeDirection.Down) to UserActionResult(prevScene), - ) - } - - val message: BouncerMessageViewModel = bouncerMessageViewModel - + private val bouncerMessageViewModelFactory: BouncerMessageViewModel.Factory, + private val flags: ComposeBouncerFlags, + private val userSwitcher: UserSwitcherViewModel, + private val actionButtonInteractor: BouncerActionButtonInteractor, + private val pinViewModelFactory: PinBouncerViewModel.Factory, + private val patternViewModelFactory: PatternBouncerViewModel.Factory, + private val passwordViewModelFactory: PasswordBouncerViewModel.Factory, +) : SysUiViewModel() { + private val _selectedUserImage = MutableStateFlow<Bitmap?>(null) + val selectedUserImage: StateFlow<Bitmap?> = _selectedUserImage.asStateFlow() + + val message: BouncerMessageViewModel by lazy { bouncerMessageViewModelFactory.create() } + + private val _userSwitcherDropdown = + MutableStateFlow<List<UserSwitcherDropdownItemViewModel>>(emptyList()) val userSwitcherDropdown: StateFlow<List<UserSwitcherDropdownItemViewModel>> = - combine( - users, - userSwitcherMenu, - ) { users, actions -> - users.map { user -> - UserSwitcherDropdownItemViewModel( - icon = Icon.Loaded(user.image, contentDescription = null), - text = user.name, - onClick = user.onClicked ?: {}, - ) - } + - actions.map { action -> - UserSwitcherDropdownItemViewModel( - icon = Icon.Resource(action.iconResourceId, contentDescription = null), - text = Text.Resource(action.textResourceId), - onClick = action.onClicked, - ) - } - } - .stateIn( - scope = applicationScope, - started = SharingStarted.WhileSubscribed(), - initialValue = emptyList(), - ) + _userSwitcherDropdown.asStateFlow() val isUserSwitcherVisible: Boolean get() = bouncerInteractor.isUserSwitcherVisible - // Handle to the scope of the child ViewModel (stored in [authMethod]). - private var childViewModelScope: CoroutineScope? = null - /** View-model for the current UI, based on the current authentication method. */ + private val _authMethodViewModel = MutableStateFlow<AuthMethodBouncerViewModel?>(null) val authMethodViewModel: StateFlow<AuthMethodBouncerViewModel?> = - authenticationInteractor.authenticationMethod - .map(::getChildViewModel) - .stateIn( - scope = applicationScope, - started = SharingStarted.WhileSubscribed(), - initialValue = null, - ) + _authMethodViewModel.asStateFlow() /** * A message for a dialog to show when the user has attempted the wrong credential too many @@ -160,31 +97,24 @@ class BouncerViewModel( */ private val wipeDialogMessage = MutableStateFlow<String?>(null) + private val _dialogViewModel = MutableStateFlow<DialogViewModel?>(createDialogViewModel()) /** * Models the dialog to be shown to the user, or `null` if no dialog should be shown. * * Once the dialog is shown, the UI should call [DialogViewModel.onDismiss] when the user * dismisses this dialog. */ - val dialogViewModel: StateFlow<DialogViewModel?> = - combine(wipeDialogMessage, lockoutDialogMessage) { _, _ -> createDialogViewModel() } - .stateIn( - scope = applicationScope, - started = SharingStarted.WhileSubscribed(), - initialValue = createDialogViewModel(), - ) + val dialogViewModel: StateFlow<DialogViewModel?> = _dialogViewModel.asStateFlow() + private val _actionButton = MutableStateFlow<BouncerActionButtonModel?>(null) /** * The bouncer action button (Return to Call / Emergency Call). If `null`, the button should not * be shown. */ - val actionButton: StateFlow<BouncerActionButtonModel?> = - actionButton.stateIn( - scope = applicationScope, - started = SharingStarted.WhileSubscribed(), - initialValue = null - ) + val actionButton: StateFlow<BouncerActionButtonModel?> = _actionButton.asStateFlow() + private val _isSideBySideSupported = + MutableStateFlow(isSideBySideSupported(authMethodViewModel.value)) /** * Whether the "side-by-side" layout is supported. * @@ -193,45 +123,97 @@ class BouncerViewModel( * side-by-side layout; these need to be shown with the standard layout so they can take up as * much width as possible. */ - val isSideBySideSupported: StateFlow<Boolean> = - authMethodViewModel - .map { authMethod -> isSideBySideSupported(authMethod) } - .stateIn( - scope = applicationScope, - started = SharingStarted.WhileSubscribed(), - initialValue = isSideBySideSupported(authMethodViewModel.value), - ) + val isSideBySideSupported: StateFlow<Boolean> = _isSideBySideSupported.asStateFlow() + private val _isFoldSplitRequired = + MutableStateFlow(isFoldSplitRequired(authMethodViewModel.value)) /** * Whether the splitting the UI around the fold seam (where the hinge is on a foldable device) * is required. */ - val isFoldSplitRequired: StateFlow<Boolean> = - authMethodViewModel - .map { authMethod -> isFoldSplitRequired(authMethod) } - .stateIn( - scope = applicationScope, - started = SharingStarted.WhileSubscribed(), - initialValue = isFoldSplitRequired(authMethodViewModel.value), - ) - - private val isInputEnabled: StateFlow<Boolean> = - bouncerMessageViewModel.isLockoutMessagePresent - .map { lockoutMessagePresent -> !lockoutMessagePresent } - .stateIn( - scope = applicationScope, - started = SharingStarted.WhileSubscribed(), - initialValue = authenticationInteractor.lockoutEndTimestamp == null, - ) + val isFoldSplitRequired: StateFlow<Boolean> = _isFoldSplitRequired.asStateFlow() + + private val _isInputEnabled = + MutableStateFlow(authenticationInteractor.lockoutEndTimestamp == null) + private val isInputEnabled: StateFlow<Boolean> = _isInputEnabled.asStateFlow() + + override suspend fun onActivated() { + coroutineScope { + launch { message.activate() } + launch { + authenticationInteractor.authenticationMethod + .map(::getChildViewModel) + .collectLatest { childViewModelOrNull -> + _authMethodViewModel.value = childViewModelOrNull + childViewModelOrNull?.activate() + } + } - init { - if (flags.isComposeBouncerOrSceneContainerEnabled()) { - // Keeps the upcoming wipe dialog up-to-date. - applicationScope.launch { + launch { authenticationInteractor.upcomingWipe.collect { wipeModel -> wipeDialogMessage.value = wipeModel?.message } } + + launch { + userSwitcher.selectedUser + .map { it.image.toBitmap() } + .collectLatest { _selectedUserImage.value = it } + } + + launch { + combine( + userSwitcher.users, + userSwitcher.menu, + ) { users, actions -> + users.map { user -> + UserSwitcherDropdownItemViewModel( + icon = Icon.Loaded(user.image, contentDescription = null), + text = user.name, + onClick = user.onClicked ?: {}, + ) + } + + actions.map { action -> + UserSwitcherDropdownItemViewModel( + icon = + Icon.Resource( + action.iconResourceId, + contentDescription = null + ), + text = Text.Resource(action.textResourceId), + onClick = action.onClicked, + ) + } + } + .collectLatest { _userSwitcherDropdown.value = it } + } + + launch { + combine(wipeDialogMessage, lockoutDialogMessage) { _, _ -> createDialogViewModel() } + .collectLatest { _dialogViewModel.value = it } + } + + launch { + actionButtonInteractor.actionButton.collectLatest { _actionButton.value = it } + } + + launch { + authMethodViewModel + .map { authMethod -> isSideBySideSupported(authMethod) } + .collectLatest { _isSideBySideSupported.value = it } + } + + launch { + authMethodViewModel + .map { authMethod -> isFoldSplitRequired(authMethod) } + .collectLatest { _isFoldSplitRequired.value = it } + } + + launch { + message.isLockoutMessagePresent + .map { lockoutMessagePresent -> !lockoutMessagePresent } + .collectLatest { _isInputEnabled.value = it } + } } } @@ -253,46 +235,28 @@ class BouncerViewModel( return childViewModel } - childViewModelScope?.cancel() - val newViewModelScope = createChildCoroutineScope(applicationScope) - childViewModelScope = newViewModelScope return when (authenticationMethod) { is AuthenticationMethodModel.Pin -> - PinBouncerViewModel( - applicationContext = applicationContext, - viewModelScope = newViewModelScope, - interactor = bouncerInteractor, - isInputEnabled = isInputEnabled, - simBouncerInteractor = simBouncerInteractor, + pinViewModelFactory.create( authenticationMethod = authenticationMethod, - onIntentionalUserInput = ::onIntentionalUserInput + onIntentionalUserInput = ::onIntentionalUserInput, + isInputEnabled = isInputEnabled, ) is AuthenticationMethodModel.Sim -> - PinBouncerViewModel( - applicationContext = applicationContext, - viewModelScope = newViewModelScope, - interactor = bouncerInteractor, - isInputEnabled = isInputEnabled, - simBouncerInteractor = simBouncerInteractor, + pinViewModelFactory.create( authenticationMethod = authenticationMethod, - onIntentionalUserInput = ::onIntentionalUserInput + onIntentionalUserInput = ::onIntentionalUserInput, + isInputEnabled = isInputEnabled, ) is AuthenticationMethodModel.Password -> - PasswordBouncerViewModel( - viewModelScope = newViewModelScope, + passwordViewModelFactory.create( + onIntentionalUserInput = ::onIntentionalUserInput, isInputEnabled = isInputEnabled, - interactor = bouncerInteractor, - inputMethodInteractor = inputMethodInteractor, - selectedUserInteractor = selectedUserInteractor, - onIntentionalUserInput = ::onIntentionalUserInput ) is AuthenticationMethodModel.Pattern -> - PatternBouncerViewModel( - applicationContext = applicationContext, - viewModelScope = newViewModelScope, - interactor = bouncerInteractor, + patternViewModelFactory.create( + onIntentionalUserInput = ::onIntentionalUserInput, isInputEnabled = isInputEnabled, - onIntentionalUserInput = ::onIntentionalUserInput ) else -> null } @@ -303,12 +267,6 @@ class BouncerViewModel( bouncerInteractor.onIntentionalUserInput() } - private fun createChildCoroutineScope(parentScope: CoroutineScope): CoroutineScope { - return CoroutineScope( - SupervisorJob(parent = parentScope.coroutineContext.job) + mainDispatcher - ) - } - /** * @return A message warning the user that the user/profile/device will be wiped upon a further * [AuthenticationWipeModel.remainingAttempts] unsuccessful authentication attempts. @@ -396,44 +354,9 @@ class BouncerViewModel( val text: Text, val onClick: () -> Unit, ) -} -@Module -object BouncerViewModelModule { - - @Provides - @SysUISingleton - fun viewModel( - @Application applicationContext: Context, - @Application applicationScope: CoroutineScope, - @Main mainDispatcher: CoroutineDispatcher, - bouncerInteractor: BouncerInteractor, - imeInteractor: InputMethodInteractor, - simBouncerInteractor: SimBouncerInteractor, - actionButtonInteractor: BouncerActionButtonInteractor, - authenticationInteractor: AuthenticationInteractor, - selectedUserInteractor: SelectedUserInteractor, - flags: ComposeBouncerFlags, - userSwitcherViewModel: UserSwitcherViewModel, - devicePolicyManager: DevicePolicyManager, - bouncerMessageViewModel: BouncerMessageViewModel, - ): BouncerViewModel { - return BouncerViewModel( - applicationContext = applicationContext, - applicationScope = applicationScope, - mainDispatcher = mainDispatcher, - bouncerInteractor = bouncerInteractor, - inputMethodInteractor = imeInteractor, - simBouncerInteractor = simBouncerInteractor, - authenticationInteractor = authenticationInteractor, - selectedUserInteractor = selectedUserInteractor, - devicePolicyManager = devicePolicyManager, - bouncerMessageViewModel = bouncerMessageViewModel, - flags = flags, - selectedUser = userSwitcherViewModel.selectedUser, - users = userSwitcherViewModel.users, - userSwitcherMenu = userSwitcherViewModel.menu, - actionButton = actionButtonInteractor.actionButton, - ) + @AssistedFactory + interface Factory { + fun create(): BouncerSceneContentViewModel } } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt index 052fb6b3c4d7..9ead7a0dcf4d 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt @@ -23,29 +23,33 @@ import com.android.systemui.inputmethod.domain.interactor.InputMethodInteractor import com.android.systemui.res.R import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.kotlin.onSubscriberAdded +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import kotlin.time.Duration.Companion.milliseconds -import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.launch /** Holds UI state and handles user input for the password bouncer UI. */ -class PasswordBouncerViewModel( - viewModelScope: CoroutineScope, - isInputEnabled: StateFlow<Boolean>, +class PasswordBouncerViewModel +@AssistedInject +constructor( interactor: BouncerInteractor, - private val onIntentionalUserInput: () -> Unit, private val inputMethodInteractor: InputMethodInteractor, private val selectedUserInteractor: SelectedUserInteractor, + @Assisted isInputEnabled: StateFlow<Boolean>, + @Assisted private val onIntentionalUserInput: () -> Unit, ) : AuthMethodBouncerViewModel( - viewModelScope = viewModelScope, interactor = interactor, isInputEnabled = isInputEnabled, ) { @@ -59,28 +63,70 @@ class PasswordBouncerViewModel( override val lockoutMessageId = R.string.kg_too_many_failed_password_attempts_dialog_message + private val _isImeSwitcherButtonVisible = MutableStateFlow(false) /** Informs the UI whether the input method switcher button should be visible. */ - val isImeSwitcherButtonVisible: StateFlow<Boolean> = imeSwitcherRefreshingFlow() + val isImeSwitcherButtonVisible: StateFlow<Boolean> = _isImeSwitcherButtonVisible.asStateFlow() /** Whether the text field element currently has focus. */ private val isTextFieldFocused = MutableStateFlow(false) + private val _isTextFieldFocusRequested = + MutableStateFlow(isInputEnabled.value && !isTextFieldFocused.value) /** Whether the UI should request focus on the text field element. */ - val isTextFieldFocusRequested = - combine(isInputEnabled, isTextFieldFocused) { hasInput, hasFocus -> hasInput && !hasFocus } - .stateIn( - scope = viewModelScope, - started = SharingStarted.WhileSubscribed(), - initialValue = isInputEnabled.value && !isTextFieldFocused.value, - ) + val isTextFieldFocusRequested = _isTextFieldFocusRequested.asStateFlow() + private val _selectedUserId = MutableStateFlow(selectedUserInteractor.getSelectedUserId()) /** The ID of the currently-selected user. */ - val selectedUserId: StateFlow<Int> = - selectedUserInteractor.selectedUser.stateIn( - scope = viewModelScope, - started = SharingStarted.WhileSubscribed(), - initialValue = selectedUserInteractor.getSelectedUserId(), - ) + val selectedUserId: StateFlow<Int> = _selectedUserId.asStateFlow() + + private val requests = Channel<Request>(Channel.BUFFERED) + + override suspend fun onActivated() { + coroutineScope { + launch { super.onActivated() } + launch { + requests.receiveAsFlow().collect { request -> + when (request) { + is OnImeSwitcherButtonClicked -> { + inputMethodInteractor.showInputMethodPicker( + displayId = request.displayId, + showAuxiliarySubtypes = false, + ) + } + is OnImeDismissed -> { + interactor.onImeHiddenByUser() + } + } + } + } + launch { + combine(isInputEnabled, isTextFieldFocused) { hasInput, hasFocus -> + hasInput && !hasFocus + } + .collectLatest { _isTextFieldFocusRequested.value = it } + } + launch { + selectedUserInteractor.selectedUser.collectLatest { _selectedUserId.value = it } + } + launch { + // Re-fetch the currently-enabled IMEs whenever the selected user changes, and + // whenever + // the UI subscribes to the `isImeSwitcherButtonVisible` flow. + combine( + // InputMethodManagerService sometimes takes some time to update its + // internal + // state when the selected user changes. As a workaround, delay fetching the + // IME + // info. + selectedUserInteractor.selectedUser.onEach { delay(DELAY_TO_FETCH_IMES) }, + _isImeSwitcherButtonVisible.onSubscriberAdded() + ) { selectedUserId, _ -> + inputMethodInteractor.hasMultipleEnabledImesOrSubtypes(selectedUserId) + } + .collectLatest { _isImeSwitcherButtonVisible.value = it } + } + } + } override fun onHidden() { super.onHidden() @@ -106,9 +152,7 @@ class PasswordBouncerViewModel( /** Notifies that the user clicked the button to change the input method. */ fun onImeSwitcherButtonClicked(displayId: Int) { - viewModelScope.launch { - inputMethodInteractor.showInputMethodPicker(displayId, showAuxiliarySubtypes = false) - } + requests.trySend(OnImeSwitcherButtonClicked(displayId)) } /** Notifies that the user has pressed the key for attempting to authenticate the password. */ @@ -120,7 +164,7 @@ class PasswordBouncerViewModel( /** Notifies that the user has dismissed the software keyboard (IME). */ fun onImeDismissed() { - viewModelScope.launch { interactor.onImeHiddenByUser() } + requests.trySend(OnImeDismissed) } /** Notifies that the password text field has gained or lost focus. */ @@ -128,34 +172,21 @@ class PasswordBouncerViewModel( isTextFieldFocused.value = isFocused } - /** - * Whether the input method switcher button should be displayed in the password bouncer UI. The - * value may be stale at the moment of subscription to this flow, but it is guaranteed to be - * shortly updated with a fresh value. - * - * Note: Each added subscription triggers an IPC call in the background, so this should only be - * subscribed to by the UI once in its lifecycle (i.e. when the bouncer is shown). - */ - private fun imeSwitcherRefreshingFlow(): StateFlow<Boolean> { - val isImeSwitcherButtonVisible = MutableStateFlow(value = false) - viewModelScope.launch { - // Re-fetch the currently-enabled IMEs whenever the selected user changes, and whenever - // the UI subscribes to the `isImeSwitcherButtonVisible` flow. - combine( - // InputMethodManagerService sometimes takes some time to update its internal - // state when the selected user changes. As a workaround, delay fetching the IME - // info. - selectedUserInteractor.selectedUser.onEach { delay(DELAY_TO_FETCH_IMES) }, - isImeSwitcherButtonVisible.onSubscriberAdded() - ) { selectedUserId, _ -> - inputMethodInteractor.hasMultipleEnabledImesOrSubtypes(selectedUserId) - } - .collect { isImeSwitcherButtonVisible.value = it } - } - return isImeSwitcherButtonVisible.asStateFlow() + @AssistedFactory + interface Factory { + fun create( + isInputEnabled: StateFlow<Boolean>, + onIntentionalUserInput: () -> Unit, + ): PasswordBouncerViewModel } companion object { @VisibleForTesting val DELAY_TO_FETCH_IMES = 300.milliseconds } + + private sealed interface Request + + private data class OnImeSwitcherButtonClicked(val displayId: Int) : Request + + private data object OnImeDismissed : Request } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt index 8b9c0a9a0bdd..b1df04b3f76b 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt @@ -22,28 +22,32 @@ import com.android.systemui.authentication.shared.model.AuthenticationMethodMode import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import com.android.systemui.res.R +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import kotlin.math.max import kotlin.math.min import kotlin.math.pow import kotlin.math.sqrt -import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.toList +import kotlinx.coroutines.launch /** Holds UI state and handles user input for the pattern bouncer UI. */ -class PatternBouncerViewModel( +class PatternBouncerViewModel +@AssistedInject +constructor( private val applicationContext: Context, - viewModelScope: CoroutineScope, interactor: BouncerInteractor, - isInputEnabled: StateFlow<Boolean>, - private val onIntentionalUserInput: () -> Unit, + @Assisted isInputEnabled: StateFlow<Boolean>, + @Assisted private val onIntentionalUserInput: () -> Unit, ) : AuthMethodBouncerViewModel( - viewModelScope = viewModelScope, interactor = interactor, isInputEnabled = isInputEnabled, ) { @@ -54,17 +58,10 @@ class PatternBouncerViewModel( /** The number of rows in the dot grid. */ val rowCount = 3 - private val _selectedDots = MutableStateFlow<LinkedHashSet<PatternDotViewModel>>(linkedSetOf()) - + private val selectedDotSet = MutableStateFlow<LinkedHashSet<PatternDotViewModel>>(linkedSetOf()) + private val selectedDotList = MutableStateFlow(selectedDotSet.value.toList()) /** The dots that were selected by the user, in the order of selection. */ - val selectedDots: StateFlow<List<PatternDotViewModel>> = - _selectedDots - .map { it.toList() } - .stateIn( - scope = viewModelScope, - started = SharingStarted.WhileSubscribed(), - initialValue = emptyList(), - ) + val selectedDots: StateFlow<List<PatternDotViewModel>> = selectedDotList.asStateFlow() private val _currentDot = MutableStateFlow<PatternDotViewModel?>(null) @@ -83,6 +80,17 @@ class PatternBouncerViewModel( override val lockoutMessageId = R.string.kg_too_many_failed_pattern_attempts_dialog_message + override suspend fun onActivated() { + coroutineScope { + launch { super.onActivated() } + launch { + selectedDotSet + .map { it.toList() } + .collectLatest { selectedDotList.value = it.toList() } + } + } + } + /** Notifies that the user has started a drag gesture across the dot grid. */ fun onDragStart() { onIntentionalUserInput() @@ -120,7 +128,7 @@ class PatternBouncerViewModel( } val hitDot = dots.value.firstOrNull { dot -> dot.x == dotColumn && dot.y == dotRow } - if (hitDot != null && !_selectedDots.value.contains(hitDot)) { + if (hitDot != null && !selectedDotSet.value.contains(hitDot)) { val skippedOverDots = currentDot.value?.let { previousDot -> buildList { @@ -147,9 +155,9 @@ class PatternBouncerViewModel( } } ?: emptyList() - _selectedDots.value = + selectedDotSet.value = linkedSetOf<PatternDotViewModel>().apply { - addAll(_selectedDots.value) + addAll(selectedDotSet.value) addAll(skippedOverDots) add(hitDot) } @@ -172,11 +180,11 @@ class PatternBouncerViewModel( override fun clearInput() { _dots.value = defaultDots() _currentDot.value = null - _selectedDots.value = linkedSetOf() + selectedDotSet.value = linkedSetOf() } override fun getInput(): List<Any> { - return _selectedDots.value.map(PatternDotViewModel::toCoordinate) + return selectedDotSet.value.map(PatternDotViewModel::toCoordinate) } private fun defaultDots(): List<PatternDotViewModel> { @@ -204,6 +212,14 @@ class PatternBouncerViewModel( max(min(outValue.float, 1f), MIN_DOT_HIT_FACTOR) } + @AssistedFactory + interface Factory { + fun create( + isInputEnabled: StateFlow<Boolean>, + onIntentionalUserInput: () -> Unit, + ): PatternBouncerViewModel + } + companion object { private const val MIN_DOT_HIT_FACTOR = 0.2f } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt index aa447ffac154..cb36560545c8 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt @@ -32,29 +32,34 @@ import com.android.systemui.authentication.shared.model.AuthenticationMethodMode import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor import com.android.systemui.res.R -import kotlinx.coroutines.CoroutineScope +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.launch /** Holds UI state and handles user input for the PIN code bouncer UI. */ -class PinBouncerViewModel( +class PinBouncerViewModel +@AssistedInject +constructor( applicationContext: Context, - viewModelScope: CoroutineScope, interactor: BouncerInteractor, - isInputEnabled: StateFlow<Boolean>, - private val onIntentionalUserInput: () -> Unit, private val simBouncerInteractor: SimBouncerInteractor, - authenticationMethod: AuthenticationMethodModel, + @Assisted isInputEnabled: StateFlow<Boolean>, + @Assisted private val onIntentionalUserInput: () -> Unit, + @Assisted override val authenticationMethod: AuthenticationMethodModel, ) : AuthMethodBouncerViewModel( - viewModelScope = viewModelScope, interactor = interactor, isInputEnabled = isInputEnabled, ) { @@ -73,69 +78,89 @@ class PinBouncerViewModel( /** Currently entered pin keys. */ val pinInput: StateFlow<PinInputViewModel> = mutablePinInput + private val _hintedPinLength = MutableStateFlow<Int?>(null) /** The length of the PIN for which we should show a hint. */ - val hintedPinLength: StateFlow<Int?> = - if (isSimAreaVisible) { - flowOf(null) - } else { - interactor.hintedPinLength - } - .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null) + val hintedPinLength: StateFlow<Int?> = _hintedPinLength.asStateFlow() + private val _backspaceButtonAppearance = MutableStateFlow(ActionButtonAppearance.Hidden) /** Appearance of the backspace button. */ val backspaceButtonAppearance: StateFlow<ActionButtonAppearance> = - combine( - mutablePinInput, - interactor.isAutoConfirmEnabled, - ) { mutablePinEntries, isAutoConfirmEnabled -> - computeBackspaceButtonAppearance( - pinInput = mutablePinEntries, - isAutoConfirmEnabled = isAutoConfirmEnabled, - ) - } - .stateIn( - scope = viewModelScope, - // Make sure this is kept as WhileSubscribed or we can run into a bug where the - // downstream continues to receive old/stale/cached values. - started = SharingStarted.WhileSubscribed(), - initialValue = ActionButtonAppearance.Hidden, - ) + _backspaceButtonAppearance.asStateFlow() + private val _confirmButtonAppearance = MutableStateFlow(ActionButtonAppearance.Hidden) /** Appearance of the confirm button. */ val confirmButtonAppearance: StateFlow<ActionButtonAppearance> = - interactor.isAutoConfirmEnabled - .map { if (it) ActionButtonAppearance.Hidden else ActionButtonAppearance.Shown } - .stateIn( - scope = viewModelScope, - started = SharingStarted.WhileSubscribed(), - initialValue = ActionButtonAppearance.Hidden, - ) - - override val authenticationMethod: AuthenticationMethodModel = authenticationMethod + _confirmButtonAppearance.asStateFlow() override val lockoutMessageId = R.string.kg_too_many_failed_pin_attempts_dialog_message - init { - viewModelScope.launch { simBouncerInteractor.subId.collect { onResetSimFlow() } } + private val requests = Channel<Request>(Channel.BUFFERED) + + override suspend fun onActivated() { + coroutineScope { + launch { super.onActivated() } + launch { + requests.receiveAsFlow().collect { request -> + when (request) { + is OnErrorDialogDismissed -> { + simBouncerInteractor.onErrorDialogDismissed() + } + is OnAuthenticateButtonClickedForSim -> { + isSimUnlockingDialogVisible.value = true + simBouncerInteractor.verifySim(getInput()) + isSimUnlockingDialogVisible.value = false + clearInput() + } + } + } + } + launch { simBouncerInteractor.subId.collect { onResetSimFlow() } } + launch { + if (isSimAreaVisible) { + flowOf(null) + } else { + interactor.hintedPinLength + } + .collectLatest { _hintedPinLength.value = it } + } + launch { + combine( + mutablePinInput, + interactor.isAutoConfirmEnabled, + ) { mutablePinEntries, isAutoConfirmEnabled -> + computeBackspaceButtonAppearance( + pinInput = mutablePinEntries, + isAutoConfirmEnabled = isAutoConfirmEnabled, + ) + } + .collectLatest { _backspaceButtonAppearance.value = it } + } + launch { + interactor.isAutoConfirmEnabled + .map { if (it) ActionButtonAppearance.Hidden else ActionButtonAppearance.Shown } + .collectLatest { _confirmButtonAppearance.value = it } + } + launch { + interactor.isPinEnhancedPrivacyEnabled + .map { !it } + .collectLatest { _isDigitButtonAnimationEnabled.value = it } + } + } } /** Notifies that the user dismissed the sim pin error dialog. */ fun onErrorDialogDismissed() { - viewModelScope.launch { simBouncerInteractor.onErrorDialogDismissed() } + requests.trySend(OnErrorDialogDismissed) } + private val _isDigitButtonAnimationEnabled = + MutableStateFlow(!interactor.isPinEnhancedPrivacyEnabled.value) /** * Whether the digit buttons should be animated when touched. Note that this doesn't affect the * delete or enter buttons; those should always animate. */ val isDigitButtonAnimationEnabled: StateFlow<Boolean> = - interactor.isPinEnhancedPrivacyEnabled - .map { !it } - .stateIn( - scope = viewModelScope, - started = SharingStarted.WhileSubscribed(), - initialValue = !interactor.isPinEnhancedPrivacyEnabled.value, - ) + _isDigitButtonAnimationEnabled.asStateFlow() /** Notifies that the user clicked on a PIN button with the given digit value. */ fun onPinButtonClicked(input: Int) { @@ -163,19 +188,14 @@ class PinBouncerViewModel( /** Notifies that the user clicked the "enter" button. */ fun onAuthenticateButtonClicked() { if (authenticationMethod == AuthenticationMethodModel.Sim) { - viewModelScope.launch { - isSimUnlockingDialogVisible.value = true - simBouncerInteractor.verifySim(getInput()) - isSimUnlockingDialogVisible.value = false - clearInput() - } + requests.trySend(OnAuthenticateButtonClickedForSim) } else { tryAuthenticate(useAutoConfirm = false) } } fun onDisableEsimButtonClicked() { - viewModelScope.launch { simBouncerInteractor.disableEsim() } + simBouncerInteractor.disableEsim() } /** Resets the sim screen and shows a default message. */ @@ -242,6 +262,21 @@ class PinBouncerViewModel( else -> false } } + + @AssistedFactory + interface Factory { + fun create( + isInputEnabled: StateFlow<Boolean>, + onIntentionalUserInput: () -> Unit, + authenticationMethod: AuthenticationMethodModel, + ): PinBouncerViewModel + } + + private sealed interface Request + + private data object OnErrorDialogDismissed : Request + + private data object OnAuthenticateButtonClickedForSim : Request } /** Appearance of pin-pad action buttons. */ diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt index b7c02ea91a6b..6e01393015ed 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt @@ -112,7 +112,11 @@ constructor( communalSceneInteractor.editModeState.value == EditModeState.STARTING || communalSceneInteractor.isLaunchingWidget.value if (!delaySceneTransition) { - communalSceneInteractor.changeScene(nextScene, nextTransition) + communalSceneInteractor.changeScene( + newScene = nextScene, + loggingReason = "KTF syncing", + transitionKey = nextTransition, + ) } } .launchIn(applicationScope) @@ -176,7 +180,10 @@ constructor( if (scene == CommunalScenes.Communal && isDreaming && timeoutJob == null) { // If dreaming starts after timeout has expired, ex. if dream restarts under // the hub, just close the hub immediately. - communalSceneInteractor.changeScene(CommunalScenes.Blank) + communalSceneInteractor.changeScene( + CommunalScenes.Blank, + "dream started after timeout", + ) } } } @@ -201,7 +208,10 @@ constructor( bgScope.launch { delay(screenTimeout.milliseconds) if (isDreaming) { - communalSceneInteractor.changeScene(CommunalScenes.Blank) + communalSceneInteractor.changeScene( + newScene = CommunalScenes.Blank, + loggingReason = "hub timeout", + ) } timeoutJob = null } diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt index 99bcc12da576..7181b15138b9 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt @@ -329,8 +329,11 @@ constructor( @Deprecated( "Use com.android.systemui.communal.domain.interactor.CommunalSceneInteractor instead" ) - fun changeScene(newScene: SceneKey, transitionKey: TransitionKey? = null) = - communalSceneInteractor.changeScene(newScene, transitionKey) + fun changeScene( + newScene: SceneKey, + loggingReason: String, + transitionKey: TransitionKey? = null + ) = communalSceneInteractor.changeScene(newScene, loggingReason, transitionKey) fun setEditModeOpen(isOpen: Boolean) { _editModeOpen.value = isOpen diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt index e45a69599a68..a0b996675331 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt @@ -22,6 +22,7 @@ import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.TransitionKey import com.android.systemui.communal.data.repository.CommunalSceneRepository import com.android.systemui.communal.domain.model.CommunalTransitionProgressModel +import com.android.systemui.communal.shared.log.CommunalSceneLogger import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.communal.shared.model.CommunalTransitionKeys import com.android.systemui.communal.shared.model.EditModeState @@ -29,6 +30,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf +import com.android.systemui.util.kotlin.pairwiseBy import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -42,8 +44,8 @@ import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn -import kotlinx.coroutines.launch @OptIn(ExperimentalCoroutinesApi::class) @SysUISingleton @@ -51,7 +53,8 @@ class CommunalSceneInteractor @Inject constructor( @Application private val applicationScope: CoroutineScope, - private val communalSceneRepository: CommunalSceneRepository, + private val repository: CommunalSceneRepository, + private val logger: CommunalSceneLogger, ) { private val _isLaunchingWidget = MutableStateFlow(false) @@ -80,25 +83,39 @@ constructor( */ fun changeScene( newScene: SceneKey, + loggingReason: String, transitionKey: TransitionKey? = null, keyguardState: KeyguardState? = null, ) { - applicationScope.launch { + applicationScope.launch("$TAG#changeScene") { + logger.logSceneChangeRequested( + from = currentScene.value, + to = newScene, + reason = loggingReason, + isInstant = false, + ) notifyListeners(newScene, keyguardState) - communalSceneRepository.changeScene(newScene, transitionKey) + repository.changeScene(newScene, transitionKey) } } /** Immediately snaps to the new scene. */ fun snapToScene( newScene: SceneKey, + loggingReason: String, delayMillis: Long = 0, keyguardState: KeyguardState? = null ) { applicationScope.launch("$TAG#snapToScene") { delay(delayMillis) + logger.logSceneChangeRequested( + from = currentScene.value, + to = newScene, + reason = loggingReason, + isInstant = true, + ) notifyListeners(newScene, keyguardState) - communalSceneRepository.snapToScene(newScene) + repository.snapToScene(newScene) } } @@ -113,13 +130,30 @@ constructor( if (_editModeState.value == EditModeState.STARTING) { return } - changeScene(CommunalScenes.Blank, CommunalTransitionKeys.SimpleFade) + changeScene( + CommunalScenes.Blank, + "activity start dismissing keyguard", + CommunalTransitionKeys.SimpleFade, + ) } /** * Target scene as requested by the underlying [SceneTransitionLayout] or through [changeScene]. */ - val currentScene: Flow<SceneKey> = communalSceneRepository.currentScene + val currentScene: StateFlow<SceneKey> = + repository.currentScene + .pairwiseBy(initialValue = repository.currentScene.value) { from, to -> + logger.logSceneChangeCommitted( + from = from, + to = to, + ) + to + } + .stateIn( + scope = applicationScope, + started = SharingStarted.Eagerly, + initialValue = repository.currentScene.value, + ) private val _editModeState = MutableStateFlow<EditModeState?>(null) /** @@ -134,7 +168,13 @@ constructor( /** Transition state of the hub mode. */ val transitionState: StateFlow<ObservableTransitionState> = - communalSceneRepository.transitionState + repository.transitionState + .onEach { logger.logSceneTransition(it) } + .stateIn( + scope = applicationScope, + started = SharingStarted.Eagerly, + initialValue = repository.transitionState.value, + ) /** * Updates the transition state of the hub [SceneTransitionLayout]. @@ -142,7 +182,7 @@ constructor( * Note that you must call is with `null` when the UI is done or risk a memory leak. */ fun setTransitionState(transitionState: Flow<ObservableTransitionState>?) { - communalSceneRepository.setTransitionState(transitionState) + repository.setTransitionState(transitionState) } /** Returns a flow that tracks the progress of transitions to the given scene from 0-1. */ diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractor.kt new file mode 100644 index 000000000000..7453368d0ee7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractor.kt @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.domain.interactor + +import android.app.ActivityManager +import com.android.systemui.common.usagestats.domain.UsageStatsInteractor +import com.android.systemui.common.usagestats.shared.model.ActivityEventModel +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.core.Logger +import com.android.systemui.log.dagger.CommunalLog +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.shared.system.TaskStackChangeListener +import com.android.systemui.shared.system.TaskStackChangeListeners +import com.android.systemui.util.kotlin.race +import com.android.systemui.util.time.SystemClock +import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.Duration.Companion.seconds +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.TimeoutCancellationException +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.takeWhile +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.withTimeout + +/** + * Detects activity starts that occur while the communal hub is showing, within a short delay of a + * widget interaction occurring. Used for detecting non-activity trampolines which otherwise would + * not prompt the user for authentication. + */ +@OptIn(ExperimentalCoroutinesApi::class) +@SysUISingleton +class WidgetTrampolineInteractor +@Inject +constructor( + private val activityStarter: ActivityStarter, + private val systemClock: SystemClock, + private val keyguardTransitionInteractor: KeyguardTransitionInteractor, + private val taskStackChangeListeners: TaskStackChangeListeners, + private val usageStatsInteractor: UsageStatsInteractor, + @CommunalLog logBuffer: LogBuffer, +) { + private companion object { + const val TAG = "WidgetTrampolineInteractor" + } + + private val logger = Logger(logBuffer, TAG) + + /** Waits for a new task to be moved to the foreground. */ + private suspend fun waitForNewForegroundTask() = suspendCancellableCoroutine { cont -> + val listener = + object : TaskStackChangeListener { + override fun onTaskMovedToFront(taskInfo: ActivityManager.RunningTaskInfo) { + if (!cont.isCompleted) { + cont.resume(Unit, null) + } + } + } + taskStackChangeListeners.registerTaskStackListener(listener) + cont.invokeOnCancellation { taskStackChangeListeners.unregisterTaskStackListener(listener) } + } + + /** + * Waits for an activity to enter a [ActivityEventModel.Lifecycle.RESUMED] state by periodically + * polling the system to see if any activities have started. + */ + private suspend fun waitForActivityStartByPolling(startTime: Long): Boolean { + while (true) { + val events = usageStatsInteractor.queryActivityEvents(startTime = startTime) + if (events.any { event -> event.lifecycle == ActivityEventModel.Lifecycle.RESUMED }) { + return true + } else { + // Poll again in the future to check if an activity started. + delay(200.milliseconds) + } + } + } + + /** Waits for a transition away from the hub to occur. */ + private suspend fun waitForTransitionAwayFromHub() { + keyguardTransitionInteractor + .isFinishedIn(Scenes.Communal, KeyguardState.GLANCEABLE_HUB) + .takeWhile { it } + .collect {} + } + + private suspend fun waitForActivityStartWhileOnHub(): Boolean { + val startTime = systemClock.currentTimeMillis() + return try { + return withTimeout(1.seconds) { + race( + { + waitForNewForegroundTask() + true + }, + { waitForActivityStartByPolling(startTime) }, + { + waitForTransitionAwayFromHub() + false + }, + ) + } + } catch (e: TimeoutCancellationException) { + false + } + } + + /** + * Checks if an activity starts while on the glanceable hub and dismisses the keyguard if it + * does. This can detect activities started due to broadcast trampolines from widgets. + */ + suspend fun waitForActivityStartAndDismissKeyguard() { + if (waitForActivityStartWhileOnHub()) { + logger.d("Detected trampoline, requesting unlock") + activityStarter.dismissKeyguardThenExecute( + /* action= */ { false }, + /* cancel= */ null, + /* afterKeyguardGone= */ false + ) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalSceneLogger.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalSceneLogger.kt new file mode 100644 index 000000000000..aed92156cfc3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalSceneLogger.kt @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.shared.log + +import com.android.compose.animation.scene.ObservableTransitionState +import com.android.compose.animation.scene.SceneKey +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.core.LogLevel +import com.android.systemui.log.dagger.CommunalLog +import javax.inject.Inject + +class CommunalSceneLogger @Inject constructor(@CommunalLog private val logBuffer: LogBuffer) { + + fun logSceneChangeRequested( + from: SceneKey, + to: SceneKey, + reason: String, + isInstant: Boolean, + ) { + logBuffer.log( + tag = TAG, + level = LogLevel.INFO, + messageInitializer = { + str1 = from.toString() + str2 = to.toString() + str3 = reason + bool1 = isInstant + }, + messagePrinter = { + buildString { + append("Scene change requested: $str1 → $str2") + if (isInstant) { + append(" (instant)") + } + append(", reason: $str3") + } + }, + ) + } + + fun logSceneChangeCommitted( + from: SceneKey, + to: SceneKey, + ) { + logBuffer.log( + tag = TAG, + level = LogLevel.INFO, + messageInitializer = { + str1 = from.toString() + str2 = to.toString() + }, + messagePrinter = { "Scene change committed: $str1 → $str2" }, + ) + } + + fun logSceneTransition(transitionState: ObservableTransitionState) { + when (transitionState) { + is ObservableTransitionState.Transition -> { + logBuffer.log( + tag = TAG, + level = LogLevel.INFO, + messageInitializer = { + str1 = transitionState.fromScene.toString() + str2 = transitionState.toScene.toString() + }, + messagePrinter = { "Scene transition started: $str1 → $str2" }, + ) + } + is ObservableTransitionState.Idle -> { + logBuffer.log( + tag = TAG, + level = LogLevel.INFO, + messageInitializer = { str1 = transitionState.currentScene.toString() }, + messagePrinter = { "Scene transition idle on: $str1" }, + ) + } + } + } + + companion object { + private const val TAG = "CommunalSceneLogger" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandler.kt b/packages/SystemUI/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandler.kt index c4edcac1a2a1..99e3232a70c2 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandler.kt @@ -48,7 +48,17 @@ constructor( InteractionHandlerDelegate( communalSceneInteractor, findViewToAnimate = { view -> view is SmartspaceAppWidgetHostView }, - intentStarter = this::startIntent, + intentStarter = + object : InteractionHandlerDelegate.IntentStarter { + override fun startActivity( + intent: PendingIntent, + fillInIntent: Intent, + activityOptions: ActivityOptions, + controller: ActivityTransitionAnimator.Controller? + ): Boolean { + return startIntent(intent, fillInIntent, activityOptions, controller) + } + }, logger = Logger(logBuffer, TAG), ) diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalAppWidgetHostViewBinder.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalAppWidgetHostViewBinder.kt index 7a0567190bd0..5f421fd19550 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalAppWidgetHostViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalAppWidgetHostViewBinder.kt @@ -21,10 +21,13 @@ import android.util.SizeF import android.view.View import android.view.ViewGroup import android.widget.FrameLayout +import androidx.core.view.doOnLayout import com.android.app.tracing.coroutines.launch import com.android.systemui.communal.domain.model.CommunalContentModel import com.android.systemui.communal.util.WidgetViewFactory import com.android.systemui.util.kotlin.DisposableHandles +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.DisposableHandle @@ -44,13 +47,8 @@ object CommunalAppWidgetHostViewBinder { val loadingJob = applicationScope.launch("$TAG#createWidgetView") { val widget = factory.createWidget(context, model, size) - // TODO(b/358662507): Remove this workaround - (container.parent as? ViewGroup)?.let { parent -> - val index = parent.indexOfChild(container) - parent.removeView(container) - parent.addView(container, index) - } - container.setView(widget) + waitForLayout(container) + container.post { container.setView(widget) } } disposables += DisposableHandle { loadingJob.cancel() } @@ -58,6 +56,10 @@ object CommunalAppWidgetHostViewBinder { return disposables } + + private suspend fun waitForLayout(container: FrameLayout) = suspendCoroutine { cont -> + container.doOnLayout { cont.resume(Unit) } + } } private fun ViewGroup.setView(view: View) { diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt index d1a5a4b8641f..b8221332eadf 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt @@ -106,10 +106,11 @@ abstract class BaseCommunalViewModel( */ fun changeScene( scene: SceneKey, + loggingReason: String, transitionKey: TransitionKey? = null, keyguardState: KeyguardState? = null ) { - communalSceneInteractor.changeScene(scene, transitionKey, keyguardState) + communalSceneInteractor.changeScene(scene, loggingReason, transitionKey, keyguardState) } fun setEditModeState(state: EditModeState?) = communalSceneInteractor.setEditModeState(state) diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt index bbd8596a76bd..623937305921 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt @@ -67,7 +67,10 @@ constructor( * transition. */ fun snapToCommunal() { - communalSceneInteractor.snapToScene(CommunalScenes.Communal) + communalSceneInteractor.snapToScene( + newScene = CommunalScenes.Communal, + loggingReason = "transition view model", + ) } // Show UMO on glanceable hub immediately on transition into glanceable hub diff --git a/packages/SystemUI/src/com/android/systemui/communal/util/InteractionHandlerDelegate.kt b/packages/SystemUI/src/com/android/systemui/communal/util/InteractionHandlerDelegate.kt index d2029d50bf63..5e21afacf9f3 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/util/InteractionHandlerDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/util/InteractionHandlerDelegate.kt @@ -19,6 +19,7 @@ package com.android.systemui.communal.util import android.app.ActivityOptions import android.app.PendingIntent import android.content.Intent +import android.util.Pair as UtilPair import android.view.View import android.widget.RemoteViews import androidx.core.util.component1 @@ -36,14 +37,28 @@ class InteractionHandlerDelegate( private val logger: Logger, ) : RemoteViews.InteractionHandler { - /** Responsible for starting the pending intent for launching activities. */ - fun interface IntentStarter { - fun startPendingIntent( + interface IntentStarter { + /** Responsible for starting the pending intent for launching activities. */ + fun startActivity( intent: PendingIntent, fillInIntent: Intent, activityOptions: ActivityOptions, controller: ActivityTransitionAnimator.Controller?, ): Boolean + + /** Responsible for starting the pending intent for non-activity launches. */ + fun startPendingIntent( + view: View, + pendingIntent: PendingIntent, + fillInIntent: Intent, + activityOptions: ActivityOptions, + ): Boolean { + return RemoteViews.startPendingIntent( + view, + pendingIntent, + UtilPair(fillInIntent, activityOptions), + ) + } } override fun onInteraction( @@ -55,7 +70,7 @@ class InteractionHandlerDelegate( str1 = pendingIntent.toLoggingString() str2 = pendingIntent.creatorPackage } - val launchOptions = response.getLaunchOptions(view) + val (fillInIntent, activityOptions) = response.getLaunchOptions(view) return when { pendingIntent.isActivity -> { // Forward the fill-in intent and activity options retrieved from the response @@ -67,15 +82,15 @@ class InteractionHandlerDelegate( communalSceneInteractor.setIsLaunchingWidget(true) CommunalTransitionAnimatorController(it, communalSceneInteractor) } - val (fillInIntent, activityOptions) = launchOptions - intentStarter.startPendingIntent( + intentStarter.startActivity( pendingIntent, fillInIntent, activityOptions, animationController ) } - else -> RemoteViews.startPendingIntent(view, pendingIntent, launchOptions) + else -> + intentStarter.startPendingIntent(view, pendingIntent, fillInIntent, activityOptions) } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorController.kt index 08444623f24d..e7cedc61610f 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorController.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorController.kt @@ -42,6 +42,7 @@ class CommunalTransitionAnimatorController( // TODO(b/330672236): move this to onTransitionAnimationEnd() without the delay. communalSceneInteractor.snapToScene( CommunalScenes.Blank, + "CommunalTransitionAnimatorController", ActivityTransitionAnimator.TIMINGS.totalDuration ) } diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt index 668fef6130bb..6d7cdc472f0a 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt @@ -218,9 +218,10 @@ constructor( lifecycleScope.launch { communalViewModel.canShowEditMode.collect { communalViewModel.changeScene( - CommunalScenes.Blank, - CommunalTransitionKeys.ToEditMode, - KeyguardState.GONE, + scene = CommunalScenes.Blank, + loggingReason = "edit mode opening", + transitionKey = CommunalTransitionKeys.ToEditMode, + keyguardState = KeyguardState.GONE, ) // wait till transitioned to Blank scene, then animate in communal content in // edit mode @@ -252,8 +253,9 @@ constructor( communalViewModel.cleanupEditModeState() communalViewModel.changeScene( - CommunalScenes.Communal, - CommunalTransitionKeys.FromEditMode + scene = CommunalScenes.Communal, + loggingReason = "edit mode closing", + transitionKey = CommunalTransitionKeys.FromEditMode ) // Wait for the current scene to be idle on communal. diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt index 0eeb506ccc09..121b4a304c3a 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt @@ -21,22 +21,30 @@ import android.app.PendingIntent import android.content.Intent import android.view.View import android.widget.RemoteViews +import com.android.app.tracing.coroutines.launch +import com.android.systemui.Flags.communalWidgetTrampolineFix import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor +import com.android.systemui.communal.domain.interactor.WidgetTrampolineInteractor import com.android.systemui.communal.util.InteractionHandlerDelegate import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.Logger import com.android.systemui.log.dagger.CommunalLog import com.android.systemui.plugins.ActivityStarter import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job @SysUISingleton class WidgetInteractionHandler @Inject constructor( + @Application applicationScope: CoroutineScope, private val activityStarter: ActivityStarter, communalSceneInteractor: CommunalSceneInteractor, + private val widgetTrampolineInteractor: WidgetTrampolineInteractor, @CommunalLog val logBuffer: LogBuffer, ) : RemoteViews.InteractionHandler { @@ -48,7 +56,52 @@ constructor( InteractionHandlerDelegate( communalSceneInteractor, findViewToAnimate = { view -> view is CommunalAppWidgetHostView }, - intentStarter = this::startIntent, + intentStarter = + object : InteractionHandlerDelegate.IntentStarter { + private var job: Job? = null + + override fun startActivity( + intent: PendingIntent, + fillInIntent: Intent, + activityOptions: ActivityOptions, + controller: ActivityTransitionAnimator.Controller? + ): Boolean { + cancelTrampolineMonitoring() + return startActivityIntent( + intent, + fillInIntent, + activityOptions, + controller + ) + } + + override fun startPendingIntent( + view: View, + pendingIntent: PendingIntent, + fillInIntent: Intent, + activityOptions: ActivityOptions + ): Boolean { + cancelTrampolineMonitoring() + if (communalWidgetTrampolineFix()) { + job = + applicationScope.launch("$TAG#monitorForActivityStart") { + widgetTrampolineInteractor + .waitForActivityStartAndDismissKeyguard() + } + } + return super.startPendingIntent( + view, + pendingIntent, + fillInIntent, + activityOptions + ) + } + + private fun cancelTrampolineMonitoring() { + job?.cancel() + job = null + } + }, logger = Logger(logBuffer, TAG), ) @@ -58,7 +111,7 @@ constructor( response: RemoteViews.RemoteResponse ): Boolean = delegate.onInteraction(view, pendingIntent, response) - private fun startIntent( + private fun startActivityIntent( pendingIntent: PendingIntent, fillInIntent: Intent, extraOptions: ActivityOptions, diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java index 4b9e5a024393..0c1fb72cc581 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java @@ -442,7 +442,9 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ @Override public void onWakeRequested() { mUiEventLogger.log(CommunalUiEvent.DREAM_TO_COMMUNAL_HUB_DREAM_AWAKE_START); - mCommunalInteractor.changeScene(CommunalScenes.Communal, null); + mCommunalInteractor.changeScene(CommunalScenes.Communal, + "dream wake requested", + null); } private Lifecycle.State getLifecycleStateLocked() { @@ -493,7 +495,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ mSystemDialogsCloser.closeSystemDialogs(); // Hide glanceable hub (this is a nop if glanceable hub is not open). - mCommunalInteractor.changeScene(CommunalScenes.Blank, null); + mCommunalInteractor.changeScene(CommunalScenes.Blank, "dream come to front", null); } /** diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt index 4b07f78eb825..5c0335a6dfae 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt @@ -20,9 +20,9 @@ import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.Flags.glanceableHubAllowKeyguardWhenDreaming import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.communal.domain.interactor.CommunalInteractor -import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dump.DumpManager +import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING @@ -51,6 +51,7 @@ constructor( fromGlanceableHubTransitionInteractor: GlanceableHubToDreamingTransitionViewModel, toGlanceableHubTransitionViewModel: DreamingToGlanceableHubTransitionViewModel, private val toLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel, + private val fromDreamingTransitionInteractor: FromDreamingTransitionInteractor, private val communalInteractor: CommunalInteractor, private val keyguardUpdateMonitor: KeyguardUpdateMonitor, private val userTracker: UserTracker, @@ -61,11 +62,9 @@ constructor( val showGlanceableHub = communalInteractor.isCommunalEnabled.value && !keyguardUpdateMonitor.isEncryptedOrLockdown(userTracker.userId) - if (showGlanceableHub && !glanceableHubAllowKeyguardWhenDreaming()) { - communalInteractor.changeScene(CommunalScenes.Communal) - } else { - toLockscreenTransitionViewModel.startTransition() - } + fromDreamingTransitionInteractor.startToLockscreenOrGlanceableHubTransition( + showGlanceableHub && !glanceableHubAllowKeyguardWhenDreaming() + ) } val dreamOverlayTranslationX: Flow<Float> = diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/repository/TutorialSchedulerRepository.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/repository/TutorialSchedulerRepository.kt index b9b38954784e..36b9ac70d290 100644 --- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/repository/TutorialSchedulerRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/repository/TutorialSchedulerRepository.kt @@ -17,6 +17,7 @@ package com.android.systemui.inputdevice.tutorial.data.repository import android.content.Context +import androidx.annotation.VisibleForTesting import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.booleanPreferencesKey @@ -33,15 +34,19 @@ import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map @SysUISingleton -class TutorialSchedulerRepository -@Inject -constructor( - @Application private val applicationContext: Context, - @Background private val backgroundScope: CoroutineScope +class TutorialSchedulerRepository( + private val applicationContext: Context, + backgroundScope: CoroutineScope, + dataStoreName: String ) { + @Inject + constructor( + @Application applicationContext: Context, + @Background backgroundScope: CoroutineScope + ) : this(applicationContext, backgroundScope, dataStoreName = "TutorialScheduler") private val Context.dataStore: DataStore<Preferences> by - preferencesDataStore(name = DATASTORE_NAME, scope = backgroundScope) + preferencesDataStore(name = dataStoreName, scope = backgroundScope) suspend fun isLaunched(deviceType: DeviceType): Boolean = loadData()[deviceType]!!.isLaunched @@ -81,8 +86,12 @@ constructor( private fun getConnectKey(device: DeviceType) = longPreferencesKey(device.name + CONNECT_TIME_SUFFIX) + @VisibleForTesting + suspend fun clearDataStore() { + applicationContext.dataStore.edit { it.clear() } + } + companion object { - const val DATASTORE_NAME = "TutorialScheduler" const val IS_LAUNCHED_SUFFIX = "_IS_LAUNCHED" const val CONNECT_TIME_SUFFIX = "_CONNECTED_TIME" } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt index 81b0064f0f03..49303e089036 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt @@ -91,11 +91,11 @@ interface KeyguardRepository { * the z-order (which is not really above the system UI window, but rather - the lock-screen * becomes invisible to reveal the "occluding activity"). */ - val isKeyguardShowing: Flow<Boolean> + val isKeyguardShowing: StateFlow<Boolean> /** Is an activity showing over the keyguard? */ @Deprecated("Use KeyguardTransitionInteractor + KeyguardState.OCCLUDED") - val isKeyguardOccluded: Flow<Boolean> + val isKeyguardOccluded: StateFlow<Boolean> /** * Whether the device is locked or unlocked right now. This is true when keyguard has been diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt index 2a8bb471c217..13d54bac8339 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt @@ -36,8 +36,6 @@ import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce @SysUISingleton @@ -73,15 +71,11 @@ constructor( listenForTransitionToCamera(scope, keyguardInteractor) } - private val canDismissLockscreen: Flow<Boolean> = - combine( - keyguardInteractor.isKeyguardShowing, - keyguardInteractor.isKeyguardDismissible, - keyguardInteractor.biometricUnlockState, - ) { isKeyguardShowing, isKeyguardDismissible, biometricUnlockState -> - (isWakeAndUnlock(biometricUnlockState.mode) || - (!isKeyguardShowing && isKeyguardDismissible)) - } + private fun canDismissLockscreen(): Boolean { + return isWakeAndUnlock(keyguardInteractor.biometricUnlockState.value.mode) || + (!keyguardInteractor.isKeyguardShowing.value && + keyguardInteractor.isKeyguardDismissible.value) + } /** * Listen for the signal that we're waking up and figure what state we need to transition to. @@ -96,22 +90,18 @@ constructor( .debounce(50L) .sample( startedKeyguardTransitionStep, - keyguardInteractor.biometricUnlockState, - keyguardInteractor.primaryBouncerShowing, - keyguardInteractor.isKeyguardOccluded, - canDismissLockscreen, wakeToGoneInteractor.canWakeDirectlyToGone, ) .collect { ( _, startedStep, - biometricUnlockState, - primaryBouncerShowing, - isKeyguardOccludedLegacy, - canDismissLockscreen, canWakeDirectlyToGone, ) -> + val isKeyguardOccludedLegacy = keyguardInteractor.isKeyguardOccluded.value + val biometricUnlockMode = keyguardInteractor.biometricUnlockState.value.mode + val primaryBouncerShowing = keyguardInteractor.primaryBouncerShowing.value + if (!maybeHandleInsecurePowerGesture()) { val shouldTransitionToLockscreen = if (KeyguardWmStateRefactor.isEnabled) { @@ -121,12 +111,10 @@ constructor( // completes. !maybeStartTransitionToOccludedOrInsecureCamera { state, reason -> startTransitionTo(state, ownerReason = reason) - } && - !isWakeAndUnlock(biometricUnlockState.mode) && - !primaryBouncerShowing + } && !isWakeAndUnlock(biometricUnlockMode) && !primaryBouncerShowing } else { !isKeyguardOccludedLegacy && - !isWakeAndUnlock(biometricUnlockState.mode) && + !isWakeAndUnlock(biometricUnlockMode) && !primaryBouncerShowing } @@ -136,7 +124,7 @@ constructor( !KeyguardWmStateRefactor.isEnabled && isKeyguardOccludedLegacy val shouldTransitionToGone = - (!KeyguardWmStateRefactor.isEnabled && canDismissLockscreen) || + (!KeyguardWmStateRefactor.isEnabled && canDismissLockscreen()) || (KeyguardWmStateRefactor.isEnabled && canWakeDirectlyToGone) if (shouldTransitionToGone) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt index 61446c19605c..90aaf0d617f7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt @@ -42,8 +42,6 @@ import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.launch @@ -83,13 +81,10 @@ constructor( listenForTransitionToCamera(scope, keyguardInteractor) } - private val canTransitionToGoneOnWake: Flow<Boolean> = - combine( - keyguardInteractor.isKeyguardShowing, - keyguardInteractor.isKeyguardDismissible, - ) { isKeyguardShowing, isKeyguardDismissible -> - isKeyguardDismissible && !isKeyguardShowing - } + private fun canDismissLockscreen(): Boolean { + return !keyguardInteractor.isKeyguardShowing.value && + keyguardInteractor.isKeyguardDismissible.value + } private fun listenForDozingToGoneViaBiometrics() { if (KeyguardWmStateRefactor.isEnabled) { @@ -135,27 +130,20 @@ constructor( .debounce(50L) .filterRelevantKeyguardStateAnd { isAwake -> isAwake } .sample( - keyguardInteractor.isKeyguardOccluded, communalInteractor.isCommunalAvailable, communalSceneInteractor.isIdleOnCommunal, - canTransitionToGoneOnWake, - keyguardInteractor.primaryBouncerShowing, ) - .collect { - ( - _, - occluded, - isCommunalAvailable, - isIdleOnCommunal, - canTransitionToGoneOnWake, - primaryBouncerShowing) -> + .collect { (_, isCommunalAvailable, isIdleOnCommunal) -> + val isKeyguardOccludedLegacy = keyguardInteractor.isKeyguardOccluded.value + val primaryBouncerShowing = keyguardInteractor.primaryBouncerShowing.value + if (!deviceEntryInteractor.isLockscreenEnabled()) { if (SceneContainerFlag.isEnabled) { // TODO(b/336576536): Check if adaptation for scene framework is needed } else { startTransitionTo(KeyguardState.GONE) } - } else if (canTransitionToGoneOnWake) { + } else if (canDismissLockscreen()) { if (SceneContainerFlag.isEnabled) { // TODO(b/336576536): Check if adaptation for scene framework is needed } else { @@ -167,7 +155,7 @@ constructor( } else { startTransitionTo(KeyguardState.PRIMARY_BOUNCER) } - } else if (occluded) { + } else if (isKeyguardOccludedLegacy) { startTransitionTo(KeyguardState.OCCLUDED) } else if (isIdleOnCommunal && !communalSceneKtfRefactor()) { if (SceneContainerFlag.isEnabled) { @@ -285,9 +273,10 @@ constructor( private suspend fun transitionToGlanceableHub() { if (communalSceneKtfRefactor()) { communalSceneInteractor.changeScene( - CommunalScenes.Communal, + newScene = CommunalScenes.Communal, + loggingReason = "from dozing to hub", // Immediately show the hub when transitioning from dozing to hub. - CommunalTransitionKeys.Immediately, + transitionKey = CommunalTransitionKeys.Immediately, ) } else { startTransitionTo(KeyguardState.GLANCEABLE_HUB) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt index 17c1e823a1ca..4666430398ec 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt @@ -20,7 +20,9 @@ import android.animation.ValueAnimator import com.android.app.animation.Interpolators import com.android.app.tracing.coroutines.launch import com.android.systemui.Flags.communalSceneKtfRefactor +import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor +import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main @@ -32,7 +34,6 @@ import com.android.systemui.keyguard.shared.model.DozeStateModel import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.scene.shared.flag.SceneContainerFlag -import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds @@ -59,6 +60,7 @@ constructor( @Main mainDispatcher: CoroutineDispatcher, keyguardInteractor: KeyguardInteractor, private val glanceableHubTransitions: GlanceableHubTransitions, + private val communalSceneInteractor: CommunalSceneInteractor, private val communalSettingsInteractor: CommunalSettingsInteractor, powerInteractor: PowerInteractor, keyguardOcclusionInteractor: KeyguardOcclusionInteractor, @@ -127,17 +129,24 @@ constructor( } } - fun startToLockscreenTransition() { + fun startToLockscreenOrGlanceableHubTransition(openHub: Boolean) { scope.launch { if ( transitionInteractor.startedKeyguardState.replayCache.last() == KeyguardState.DREAMING ) { if (powerInteractor.detailedWakefulness.value.isAwake()) { - startTransitionTo( - KeyguardState.LOCKSCREEN, - ownerReason = "Dream has ended and device is awake" - ) + if (openHub) { + communalSceneInteractor.changeScene( + newScene = CommunalScenes.Communal, + loggingReason = "FromDreamingTransitionInteractor", + ) + } else { + startTransitionTo( + KeyguardState.LOCKSCREEN, + ownerReason = "Dream has ended and device is awake" + ) + } } } } @@ -208,15 +217,15 @@ constructor( scope.launch { keyguardInteractor.isAbleToDream - .sampleCombine( - keyguardInteractor.isKeyguardShowing, - keyguardInteractor.isKeyguardDismissible, - ) - .filterRelevantKeyguardStateAnd { - (isDreaming, isKeyguardShowing, isKeyguardDismissible) -> - !isDreaming && isKeyguardDismissible && !isKeyguardShowing + .filterRelevantKeyguardStateAnd { isDreaming -> !isDreaming } + .collect { + if ( + keyguardInteractor.isKeyguardDismissible.value && + !keyguardInteractor.isKeyguardShowing.value + ) { + startTransitionTo(KeyguardState.GONE) + } } - .collect { startTransitionTo(KeyguardState.GONE) } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt index befcc9e6c8ee..c9db26dca9e2 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt @@ -159,6 +159,7 @@ constructor( if (communalSceneKtfRefactor()) { communalSceneInteractor.changeScene( newScene = CommunalScenes.Blank, + loggingReason = "hub to dozing", transitionKey = CommunalTransitionKeys.Immediately, keyguardState = KeyguardState.DOZING, ) @@ -182,6 +183,7 @@ constructor( if (communalSceneKtfRefactor()) { communalSceneInteractor.changeScene( newScene = CommunalScenes.Blank, + loggingReason = "hub to occluded (KeyguardWmStateRefactor)", transitionKey = CommunalTransitionKeys.SimpleFade, keyguardState = state, ) @@ -211,6 +213,7 @@ constructor( .collect { _ -> communalSceneInteractor.changeScene( newScene = CommunalScenes.Blank, + loggingReason = "hub to occluded", transitionKey = CommunalTransitionKeys.SimpleFade, keyguardState = KeyguardState.OCCLUDED, ) @@ -254,6 +257,7 @@ constructor( } else { communalSceneInteractor.changeScene( newScene = CommunalScenes.Blank, + loggingReason = "hub to gone", transitionKey = CommunalTransitionKeys.SimpleFade, keyguardState = KeyguardState.GONE ) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt index 905ca8eda87f..7b6949fdaa2c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt @@ -146,8 +146,9 @@ constructor( if (SceneContainerFlag.isEnabled) return if (communalSceneKtfRefactor()) { communalSceneInteractor.changeScene( - CommunalScenes.Communal, - CommunalTransitionKeys.SimpleFade + newScene = CommunalScenes.Communal, + loggingReason = "occluded to hub", + transitionKey = CommunalTransitionKeys.SimpleFade ) } else { startTransitionTo(KeyguardState.GLANCEABLE_HUB) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt index 2823b93b4847..0118f8e21f79 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt @@ -175,7 +175,10 @@ constructor( !communalSceneInteractor.isLaunchingWidget.value && communalSceneInteractor.editModeState.value == null ) { - communalSceneInteractor.snapToScene(CommunalScenes.Blank) + communalSceneInteractor.snapToScene( + newScene = CommunalScenes.Blank, + loggingReason = "FromPrimaryBouncerTransitionInteractor", + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt index 0df989e9353f..4cab2bb5dba8 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt @@ -216,14 +216,14 @@ constructor( /** Whether the keyguard is showing or not. */ @Deprecated("Use KeyguardTransitionInteractor + KeyguardState") - val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing + val isKeyguardShowing: StateFlow<Boolean> = repository.isKeyguardShowing /** Whether the keyguard is dismissible or not. */ val isKeyguardDismissible: StateFlow<Boolean> = repository.isKeyguardDismissible /** Whether the keyguard is occluded (covered by an activity). */ @Deprecated("Use KeyguardTransitionInteractor + KeyguardState.OCCLUDED") - val isKeyguardOccluded: Flow<Boolean> = repository.isKeyguardOccluded + val isKeyguardOccluded: StateFlow<Boolean> = repository.isKeyguardOccluded /** Whether the keyguard is going away. */ @Deprecated("Use KeyguardTransitionInteractor + KeyguardState.GONE") @@ -253,7 +253,7 @@ constructor( val ambientIndicationVisible: Flow<Boolean> = repository.ambientIndicationVisible.asStateFlow() /** Whether the primary bouncer is showing or not. */ - @JvmField val primaryBouncerShowing: Flow<Boolean> = bouncerRepository.primaryBouncerShow + @JvmField val primaryBouncerShowing: StateFlow<Boolean> = bouncerRepository.primaryBouncerShow /** Whether the alternate bouncer is showing or not. */ val alternateBouncerShowing: Flow<Boolean> = @@ -274,7 +274,7 @@ constructor( val statusBarState: Flow<StatusBarState> = repository.statusBarState /** Observable for [BiometricUnlockModel] when biometrics are used to unlock the device. */ - val biometricUnlockState: Flow<BiometricUnlockModel> = repository.biometricUnlockState + val biometricUnlockState: StateFlow<BiometricUnlockModel> = repository.biometricUnlockState /** Keyguard is present and is not occluded. */ val isKeyguardVisible: Flow<Boolean> = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt index 15e6b1d78ea0..d119ed4f6580 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt @@ -92,8 +92,8 @@ constructor( val button = view as ImageView val configurationBasedDimensions = MutableStateFlow(loadFromResources(view)) val disposableHandle = - view.repeatWhenAttached(mainImmediateDispatcher) { - repeatOnLifecycle(Lifecycle.State.STARTED) { + view.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.CREATED) { launch("$TAG#viewModel") { viewModel.collect { buttonModel -> updateButton( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt index aab5b9b29680..89851dbec7bc 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt @@ -152,6 +152,62 @@ object KeyguardRootViewBinder { } } } + + if ( + KeyguardBottomAreaRefactor.isEnabled || DeviceEntryUdfpsRefactor.isEnabled + ) { + launch("$TAG#alpha") { + viewModel.alpha(viewState).collect { alpha -> + view.alpha = alpha + if (KeyguardBottomAreaRefactor.isEnabled) { + childViews[statusViewId]?.alpha = alpha + childViews[burnInLayerId]?.alpha = alpha + } + } + } + } + + if (MigrateClocksToBlueprint.isEnabled) { + launch("$TAG#translationY") { + // When translation happens in burnInLayer, it won't be weather clock + // large clock isn't added to burnInLayer due to its scale transition + // so we also need to add translation to it here + // same as translationX + viewModel.translationY.collect { y -> + childViews[burnInLayerId]?.translationY = y + childViews[largeClockId]?.translationY = y + childViews[aodNotificationIconContainerId]?.translationY = y + } + } + + launch("$TAG#translationX") { + viewModel.translationX.collect { state -> + val px = state.value ?: return@collect + when { + state.isToOrFrom(KeyguardState.AOD) -> { + // Large Clock is not translated in the x direction + childViews[burnInLayerId]?.translationX = px + childViews[aodNotificationIconContainerId]?.translationX = + px + } + state.isToOrFrom(KeyguardState.GLANCEABLE_HUB) -> { + for ((key, childView) in childViews.entries) { + when (key) { + indicationArea, + startButton, + endButton, + lockIcon, + deviceEntryIcon -> { + // Do not move these views + } + else -> childView.translationX = px + } + } + } + } + } + } + } } } disposables += @@ -188,20 +244,6 @@ object KeyguardRootViewBinder { } } - if ( - KeyguardBottomAreaRefactor.isEnabled || DeviceEntryUdfpsRefactor.isEnabled - ) { - launch { - viewModel.alpha(viewState).collect { alpha -> - view.alpha = alpha - if (KeyguardBottomAreaRefactor.isEnabled) { - childViews[statusViewId]?.alpha = alpha - childViews[burnInLayerId]?.alpha = alpha - } - } - } - } - if (MigrateClocksToBlueprint.isEnabled) { launch { viewModel.burnInLayerVisibility.collect { visibility -> @@ -222,46 +264,6 @@ object KeyguardRootViewBinder { } launch { - // When translation happens in burnInLayer, it won't be weather clock - // large clock isn't added to burnInLayer due to its scale transition - // so we also need to add translation to it here - // same as translationX - viewModel.translationY.collect { y -> - childViews[burnInLayerId]?.translationY = y - childViews[largeClockId]?.translationY = y - childViews[aodNotificationIconContainerId]?.translationY = y - } - } - - launch { - viewModel.translationX.collect { state -> - val px = state.value ?: return@collect - when { - state.isToOrFrom(KeyguardState.AOD) -> { - // Large Clock is not translated in the x direction - childViews[burnInLayerId]?.translationX = px - childViews[aodNotificationIconContainerId]?.translationX = - px - } - state.isToOrFrom(KeyguardState.GLANCEABLE_HUB) -> { - for ((key, childView) in childViews.entries) { - when (key) { - indicationArea, - startButton, - endButton, - lockIcon, - deviceEntryIcon -> { - // Do not move these views - } - else -> childView.translationX = px - } - } - } - } - } - } - - launch { viewModel.scale.collect { scaleViewModel -> if (scaleViewModel.scaleClockOnly) { // For clocks except weather clock, we have scale transition diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AccessibilityActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AccessibilityActionsViewModel.kt index 34c1436c3235..38f5d3e76c7c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AccessibilityActionsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AccessibilityActionsViewModel.kt @@ -50,5 +50,9 @@ constructor( } .distinctUntilChanged() - fun openCommunalHub() = communalInteractor.changeScene(CommunalScenes.Communal) + fun openCommunalHub() = + communalInteractor.changeScene( + newScene = CommunalScenes.Communal, + loggingReason = "accessibility", + ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt index b5ec7a6511f1..10605b28a862 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt @@ -18,7 +18,6 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.app.animation.Interpolators.EMPHASIZED import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING @@ -39,10 +38,8 @@ import kotlinx.coroutines.flow.Flow class DreamingToLockscreenTransitionViewModel @Inject constructor( - private val fromDreamingTransitionInteractor: FromDreamingTransitionInteractor, animationFlow: KeyguardTransitionAnimationFlow, ) : DeviceEntryIconTransition { - fun startTransition() = fromDreamingTransitionInteractor.startToLockscreenTransition() private val transitionAnimation = animationFlow.setup( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt index 050ef6f94f0a..06f77bfe54c6 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt @@ -270,7 +270,7 @@ constructor( occludedToLockscreenTransitionViewModel.lockscreenAlpha, primaryBouncerToAodTransitionViewModel.lockscreenAlpha, primaryBouncerToGoneTransitionViewModel.lockscreenAlpha, - primaryBouncerToLockscreenTransitionViewModel.lockscreenAlpha, + primaryBouncerToLockscreenTransitionViewModel.lockscreenAlpha(viewState), ) .onStart { emit(1f) } ) { hideKeyguard, alpha -> diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt index 7511101bf04e..d29f5129bd59 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt @@ -16,6 +16,7 @@ package com.android.systemui.keyguard.ui.viewmodel +import android.util.MathUtils import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor @@ -58,7 +59,14 @@ constructor( onStep = { it } ) - val lockscreenAlpha: Flow<Float> = shortcutsAlpha + fun lockscreenAlpha(viewState: ViewStateAccessor): Flow<Float> { + var currentAlpha = 0f + return transitionAnimation.sharedFlow( + duration = 250.milliseconds, + onStart = { currentAlpha = viewState.alpha() }, + onStep = { MathUtils.lerp(currentAlpha, 1f, it) }, + ) + } val deviceEntryBackgroundViewAlpha: Flow<Float> = transitionAnimation.immediatelyTransitionTo(1f) diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt index 62a72184190d..c21301c62c8d 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt @@ -223,7 +223,7 @@ constructor( if (!mediaFlags.isSceneContainerEnabled()) { MediaPlayerData.players().forEach { it.updateAnimatorDurationScale() } } else { - controllerByViewModel.values.forEach { it.updateAnimatorDurationScale() } + controllerById.values.forEach { it.updateAnimatorDurationScale() } } } } @@ -324,7 +324,7 @@ constructor( private var widthInSceneContainerPx = 0 private var heightInSceneContainerPx = 0 - private val controllerByViewModel = mutableMapOf<MediaCommonViewModel, MediaViewController>() + private val controllerById = mutableMapOf<String, MediaViewController>() private val commonViewModels = mutableListOf<MediaCommonViewModel>() init { @@ -738,7 +738,7 @@ constructor( viewController.heightInSceneContainerPx = heightInSceneContainerPx } viewController.attachPlayer(viewHolder) - viewController.mediaViewHolder.player.layoutParams = lp + viewController.mediaViewHolder?.player?.layoutParams = lp MediaControlViewBinder.bind( viewHolder, commonViewModel.controlViewModel, @@ -749,12 +749,13 @@ constructor( mediaFlags ) mediaContent.addView(viewHolder.player, position) + controllerById[commonViewModel.instanceId.toString()] = viewController } is MediaCommonViewModel.MediaRecommendations -> { val viewHolder = RecommendationViewHolder.create(LayoutInflater.from(context), mediaContent) viewController.attachRecommendations(viewHolder) - viewController.recommendationViewHolder.recommendations.layoutParams = lp + viewController.recommendationViewHolder?.recommendations?.layoutParams = lp MediaRecommendationsViewBinder.bind( viewHolder, commonViewModel.recsViewModel, @@ -762,11 +763,11 @@ constructor( falsingManager, ) mediaContent.addView(viewHolder.recommendations, position) + controllerById[commonViewModel.key] = viewController } } onAddOrUpdateVisibleToUserCard(position, isMediaCardUpdate = false) viewController.setListening(mediaCarouselScrollHandler.visibleToUser && currentlyExpanded) - controllerByViewModel[commonViewModel] = viewController updateViewControllerToState(viewController, noAnimation = true) updatePageIndicator() if ( @@ -793,14 +794,19 @@ constructor( } private fun onRemoved(commonViewModel: MediaCommonViewModel) { - controllerByViewModel.remove(commonViewModel)?.let { + val id = + when (commonViewModel) { + is MediaCommonViewModel.MediaControl -> commonViewModel.instanceId.toString() + is MediaCommonViewModel.MediaRecommendations -> commonViewModel.key + } + controllerById.remove(id)?.let { when (commonViewModel) { is MediaCommonViewModel.MediaControl -> { - mediaCarouselScrollHandler.onPrePlayerRemoved(it.mediaViewHolder.player) - mediaContent.removeView(it.mediaViewHolder.player) + mediaCarouselScrollHandler.onPrePlayerRemoved(it.mediaViewHolder!!.player) + mediaContent.removeView(it.mediaViewHolder!!.player) } is MediaCommonViewModel.MediaRecommendations -> { - mediaContent.removeView(it.recommendationViewHolder.recommendations) + mediaContent.removeView(it.recommendationViewHolder!!.recommendations) } } it.onDestroy() @@ -811,14 +817,19 @@ constructor( } private fun onMoved(commonViewModel: MediaCommonViewModel, from: Int, to: Int) { - controllerByViewModel[commonViewModel]?.let { + val id = + when (commonViewModel) { + is MediaCommonViewModel.MediaControl -> commonViewModel.instanceId.toString() + is MediaCommonViewModel.MediaRecommendations -> commonViewModel.key + } + controllerById[id]?.let { mediaContent.removeViewAt(from) when (commonViewModel) { is MediaCommonViewModel.MediaControl -> { - mediaContent.addView(it.mediaViewHolder.player, to) + mediaContent.addView(it.mediaViewHolder!!.player, to) } is MediaCommonViewModel.MediaRecommendations -> { - mediaContent.addView(it.recommendationViewHolder.recommendations, to) + mediaContent.addView(it.recommendationViewHolder!!.recommendations, to) } } } @@ -855,15 +866,16 @@ constructor( } } .toHashSet() - controllerByViewModel - .filter { - when (val viewModel = it.key) { - is MediaCommonViewModel.MediaControl -> - !viewIds.contains(viewModel.instanceId.toString()) - is MediaCommonViewModel.MediaRecommendations -> !viewIds.contains(viewModel.key) - } + controllerById + .filter { !viewIds.contains(it.key) } + .forEach { + mediaCarouselScrollHandler.onPrePlayerRemoved(it.value.mediaViewHolder?.player) + mediaContent.removeView(it.value.mediaViewHolder?.player) + mediaContent.removeView(it.value.recommendationViewHolder?.recommendations) + it.value.onDestroy() + mediaCarouselScrollHandler.onPlayersChanged() + updatePageIndicator() } - .forEach { onRemoved(it.key) } } private suspend fun getMediaLockScreenSetting(): Boolean { @@ -1176,12 +1188,12 @@ constructor( commonViewModels.forEach { viewModel -> when (viewModel) { is MediaCommonViewModel.MediaControl -> { - controllerByViewModel[viewModel]?.mediaViewHolder?.let { + controllerById[viewModel.instanceId.toString()]?.mediaViewHolder?.let { mediaContent.addView(it.player) } } is MediaCommonViewModel.MediaRecommendations -> { - controllerByViewModel[viewModel]?.recommendationViewHolder?.let { + controllerById[viewModel.key]?.recommendationViewHolder?.let { mediaContent.addView(it.recommendations) } } @@ -1234,9 +1246,7 @@ constructor( updateViewControllerToState(mediaPlayer.mediaViewController, immediately) } } else { - controllerByViewModel.values.forEach { - updateViewControllerToState(it, immediately) - } + controllerById.values.forEach { updateViewControllerToState(it, immediately) } } maybeResetSettingsCog() updatePageIndicatorAlpha() @@ -1296,9 +1306,7 @@ constructor( player.setListening(visibleToUser && currentlyExpanded) } } else { - controllerByViewModel.values.forEach { - it.setListening(visibleToUser && currentlyExpanded) - } + controllerById.values.forEach { it.setListening(visibleToUser && currentlyExpanded) } } } @@ -1316,7 +1324,7 @@ constructor( Math.max(height, controller.currentHeight + controller.translationY.toInt()) } } else { - controllerByViewModel.values.forEach { + controllerById.values.forEach { // When transitioning the view to gone, the view gets smaller, but the translation // Doesn't, let's add the translation width = Math.max(width, it.currentWidth + it.translationX.toInt()) @@ -1413,7 +1421,7 @@ constructor( mediaPlayer.mediaViewController.onLocationPreChange(desiredLocation) } } else { - controllerByViewModel.values.forEach { controller -> + controllerById.values.forEach { controller -> if (animate) { controller.animatePendingStateChange(duration, startDelay) } @@ -1441,7 +1449,7 @@ constructor( if (!mediaFlags.isSceneContainerEnabled()) { MediaPlayerData.players().forEach { it.closeGuts(immediate) } } else { - controllerByViewModel.values.forEach { it.closeGuts(immediate) } + controllerById.values.forEach { it.closeGuts(immediate) } } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt index 681bf390e3e9..584908ff2aad 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt @@ -196,8 +196,8 @@ constructor( private var isNextButtonAvailable = false /** View holders for controller */ - lateinit var recommendationViewHolder: RecommendationViewHolder - lateinit var mediaViewHolder: MediaViewHolder + var recommendationViewHolder: RecommendationViewHolder? = null + var mediaViewHolder: MediaViewHolder? = null private lateinit var seekBarObserver: SeekBarObserver private lateinit var turbulenceNoiseController: TurbulenceNoiseController @@ -752,16 +752,18 @@ constructor( private fun updateDisplayForScrubbingChange() { mainExecutor.execute { val isTimeVisible = canShowScrubbingTime && isScrubbing - MediaControlViewBinder.setVisibleAndAlpha( - expandedLayout, - mediaViewHolder.scrubbingTotalTimeView.id, - isTimeVisible - ) - MediaControlViewBinder.setVisibleAndAlpha( - expandedLayout, - mediaViewHolder.scrubbingElapsedTimeView.id, - isTimeVisible - ) + mediaViewHolder!!.let { + MediaControlViewBinder.setVisibleAndAlpha( + expandedLayout, + it.scrubbingTotalTimeView.id, + isTimeVisible + ) + MediaControlViewBinder.setVisibleAndAlpha( + expandedLayout, + it.scrubbingElapsedTimeView.id, + isTimeVisible + ) + } MediaControlViewModel.SEMANTIC_ACTIONS_HIDE_WHEN_SCRUBBING.forEach { id -> val isButtonVisible: Boolean @@ -780,14 +782,16 @@ constructor( notVisibleValue = ConstraintSet.GONE } } - MediaControlViewBinder.setSemanticButtonVisibleAndAlpha( - mediaViewHolder.getAction(id), - expandedLayout, - collapsedLayout, - isButtonVisible, - notVisibleValue, - showInCollapsed = true - ) + mediaViewHolder!!.let { + MediaControlViewBinder.setSemanticButtonVisibleAndAlpha( + it.getAction(id), + expandedLayout, + collapsedLayout, + isButtonVisible, + notVisibleValue, + showInCollapsed = true + ) + } } if (!metadataAnimationHandler.isRunning) { @@ -813,39 +817,41 @@ constructor( fun setUpTurbulenceNoise() { if (!mediaFlags.isSceneContainerEnabled()) return - if (!this::turbulenceNoiseAnimationConfig.isInitialized) { - turbulenceNoiseAnimationConfig = - createTurbulenceNoiseConfig( - mediaViewHolder.loadingEffectView, - mediaViewHolder.turbulenceNoiseView, - colorSchemeTransition - ) - } - if (Flags.shaderlibLoadingEffectRefactor()) { - if (!this::loadingEffect.isInitialized) { - loadingEffect = - LoadingEffect( - TurbulenceNoiseShader.Companion.Type.SIMPLEX_NOISE, - turbulenceNoiseAnimationConfig, - noiseDrawCallback, - stateChangedCallback + mediaViewHolder!!.let { + if (!this::turbulenceNoiseAnimationConfig.isInitialized) { + turbulenceNoiseAnimationConfig = + createTurbulenceNoiseConfig( + it.loadingEffectView, + it.turbulenceNoiseView, + colorSchemeTransition ) } - colorSchemeTransition.loadingEffect = loadingEffect - loadingEffect.play() - mainExecutor.executeDelayed( - loadingEffect::finish, - MediaControlViewModel.TURBULENCE_NOISE_PLAY_MS_DURATION - ) - } else { - turbulenceNoiseController.play( - TurbulenceNoiseShader.Companion.Type.SIMPLEX_NOISE, - turbulenceNoiseAnimationConfig - ) - mainExecutor.executeDelayed( - turbulenceNoiseController::finish, - MediaControlViewModel.TURBULENCE_NOISE_PLAY_MS_DURATION - ) + if (Flags.shaderlibLoadingEffectRefactor()) { + if (!this::loadingEffect.isInitialized) { + loadingEffect = + LoadingEffect( + TurbulenceNoiseShader.Companion.Type.SIMPLEX_NOISE, + turbulenceNoiseAnimationConfig, + noiseDrawCallback, + stateChangedCallback + ) + } + colorSchemeTransition.loadingEffect = loadingEffect + loadingEffect.play() + mainExecutor.executeDelayed( + loadingEffect::finish, + MediaControlViewModel.TURBULENCE_NOISE_PLAY_MS_DURATION + ) + } else { + turbulenceNoiseController.play( + TurbulenceNoiseShader.Companion.Type.SIMPLEX_NOISE, + turbulenceNoiseAnimationConfig + ) + mainExecutor.executeDelayed( + turbulenceNoiseController::finish, + MediaControlViewModel.TURBULENCE_NOISE_PLAY_MS_DURATION + ) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt b/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt deleted file mode 100644 index e4bafcd9fdb3..000000000000 --- a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt +++ /dev/null @@ -1,298 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.qs - -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import android.content.IntentFilter -import android.database.ContentObserver -import android.net.Uri -import android.os.Handler -import android.os.UserHandle -import android.provider.Settings -import android.text.TextUtils -import android.util.ArraySet -import android.util.Log -import androidx.annotation.GuardedBy -import androidx.annotation.VisibleForTesting -import com.android.systemui.Dumpable -import com.android.systemui.broadcast.BroadcastDispatcher -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Background -import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.dump.DumpManager -import com.android.systemui.util.UserAwareController -import com.android.systemui.util.settings.SecureSettings -import java.io.PrintWriter -import java.util.concurrent.Executor -import javax.inject.Inject - -private const val TAG = "AutoAddTracker" -private const val DELIMITER = "," - -/** - * Class to track tiles that have been auto-added - * - * The list is backed by [Settings.Secure.QS_AUTO_ADDED_TILES]. - * - * It also handles restore gracefully. - */ -class AutoAddTracker -@VisibleForTesting -constructor( - private val secureSettings: SecureSettings, - private val broadcastDispatcher: BroadcastDispatcher, - private val qsHost: QSHost, - private val dumpManager: DumpManager, - private val mainHandler: Handler?, - private val backgroundExecutor: Executor, - private var userId: Int -) : UserAwareController, Dumpable { - - companion object { - private val FILTER = IntentFilter(Intent.ACTION_SETTING_RESTORED) - } - - @GuardedBy("autoAdded") private val autoAdded = ArraySet<String>() - private var restoredTiles: Map<String, AutoTile>? = null - - override val currentUserId: Int - get() = userId - - private val contentObserver = - object : ContentObserver(mainHandler) { - override fun onChange( - selfChange: Boolean, - uris: Collection<Uri>, - flags: Int, - _userId: Int - ) { - if (_userId != userId) { - // Ignore changes outside of our user. We'll load the correct value on user - // change - return - } - loadTiles() - } - } - - private val restoreReceiver = - object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - if (intent.action != Intent.ACTION_SETTING_RESTORED) return - processRestoreIntent(intent) - } - } - - private fun processRestoreIntent(intent: Intent) { - when (intent.getStringExtra(Intent.EXTRA_SETTING_NAME)) { - Settings.Secure.QS_TILES -> { - restoredTiles = - intent - .getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE) - ?.split(DELIMITER) - ?.mapIndexed(::AutoTile) - ?.associateBy(AutoTile::tileType) - ?: run { - Log.w(TAG, "Null restored tiles for user $userId") - emptyMap() - } - } - Settings.Secure.QS_AUTO_ADDED_TILES -> { - restoredTiles?.let { restoredTiles -> - val restoredAutoAdded = - intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE)?.split(DELIMITER) - ?: emptyList() - val autoAddedBeforeRestore = - intent.getStringExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE)?.split(DELIMITER) - ?: emptyList() - - val tilesToRemove = restoredAutoAdded.filter { it !in restoredTiles } - if (tilesToRemove.isNotEmpty()) { - Log.d(TAG, "Removing tiles: $tilesToRemove") - qsHost.removeTiles(tilesToRemove) - } - val tiles = - synchronized(autoAdded) { - autoAdded.clear() - autoAdded.addAll(restoredAutoAdded + autoAddedBeforeRestore) - getTilesFromListLocked() - } - saveTiles(tiles) - } - ?: run { - Log.w( - TAG, - "${Settings.Secure.QS_AUTO_ADDED_TILES} restored before " + - "${Settings.Secure.QS_TILES} for user $userId" - ) - } - } - else -> {} // Do nothing for other Settings - } - } - - /** Init method must be called after construction to start listening */ - fun initialize() { - dumpManager.registerDumpable(TAG, this) - loadTiles() - secureSettings.registerContentObserverForUserSync( - secureSettings.getUriFor(Settings.Secure.QS_AUTO_ADDED_TILES), - contentObserver, - UserHandle.USER_ALL - ) - registerBroadcastReceiver() - } - - /** Unregister listeners, receivers and observers */ - fun destroy() { - dumpManager.unregisterDumpable(TAG) - secureSettings.unregisterContentObserverSync(contentObserver) - unregisterBroadcastReceiver() - } - - private fun registerBroadcastReceiver() { - broadcastDispatcher.registerReceiver( - restoreReceiver, - FILTER, - backgroundExecutor, - UserHandle.of(userId) - ) - } - - private fun unregisterBroadcastReceiver() { - broadcastDispatcher.unregisterReceiver(restoreReceiver) - } - - override fun changeUser(newUser: UserHandle) { - if (newUser.identifier == userId) return - unregisterBroadcastReceiver() - userId = newUser.identifier - restoredTiles = null - loadTiles() - registerBroadcastReceiver() - } - - fun getRestoredTilePosition(tile: String): Int = - restoredTiles?.get(tile)?.index ?: QSHost.POSITION_AT_END - - /** Returns `true` if the tile has been auto-added before */ - fun isAdded(tile: String): Boolean { - return synchronized(autoAdded) { tile in autoAdded } - } - - /** - * Sets a tile as auto-added. - * - * From here on, [isAdded] will return true for that tile. - */ - fun setTileAdded(tile: String) { - val tiles = - synchronized(autoAdded) { - if (autoAdded.add(tile)) { - getTilesFromListLocked() - } else { - null - } - } - tiles?.let { saveTiles(it) } - } - - /** - * Removes a tile from the list of auto-added. - * - * This allows for this tile to be auto-added again in the future. - */ - fun setTileRemoved(tile: String) { - val tiles = - synchronized(autoAdded) { - if (autoAdded.remove(tile)) { - getTilesFromListLocked() - } else { - null - } - } - tiles?.let { saveTiles(it) } - } - - private fun getTilesFromListLocked(): String { - return TextUtils.join(DELIMITER, autoAdded) - } - - private fun saveTiles(tiles: String) { - secureSettings.putStringForUser( - Settings.Secure.QS_AUTO_ADDED_TILES, - tiles, - /* tag */ null, - /* makeDefault */ false, - userId, - /* overrideableByRestore */ true - ) - } - - private fun loadTiles() { - synchronized(autoAdded) { - autoAdded.clear() - autoAdded.addAll(getAdded()) - } - } - - private fun getAdded(): Collection<String> { - val current = secureSettings.getStringForUser(Settings.Secure.QS_AUTO_ADDED_TILES, userId) - return current?.split(DELIMITER) ?: emptySet() - } - - override fun dump(pw: PrintWriter, args: Array<out String>) { - pw.println("Current user: $userId") - pw.println("Restored tiles: $restoredTiles") - pw.println("Added tiles: $autoAdded") - } - - @SysUISingleton - class Builder - @Inject - constructor( - private val secureSettings: SecureSettings, - private val broadcastDispatcher: BroadcastDispatcher, - private val qsHost: QSHost, - private val dumpManager: DumpManager, - @Main private val handler: Handler, - @Background private val executor: Executor - ) { - private var userId: Int = 0 - - fun setUserId(_userId: Int): Builder { - userId = _userId - return this - } - - fun build(): AutoAddTracker { - return AutoAddTracker( - secureSettings, - broadcastDispatcher, - qsHost, - dumpManager, - handler, - executor, - userId - ) - } - } - - private data class AutoTile(val index: Int, val tileType: String) -} diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentStartable.kt b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentStartable.kt index 9fa6769fe5f3..bb238f2e20fe 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentStartable.kt @@ -19,6 +19,7 @@ package com.android.systemui.qs import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.fragments.FragmentService +import com.android.systemui.qs.composefragment.QSFragmentCompose import dagger.Binds import dagger.Module import dagger.multibindings.ClassKey @@ -31,13 +32,18 @@ class QSFragmentStartable @Inject constructor( private val fragmentService: FragmentService, - private val qsFragmentLegacyProvider: Provider<QSFragmentLegacy> + private val qsFragmentLegacyProvider: Provider<QSFragmentLegacy>, + private val qsFragmentComposeProvider: Provider<QSFragmentCompose>, ) : CoreStartable { override fun start() { fragmentService.addFragmentInstantiationProvider( QSFragmentLegacy::class.java, qsFragmentLegacyProvider ) + fragmentService.addFragmentInstantiationProvider( + QSFragmentCompose::class.java, + qsFragmentComposeProvider + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt index c77233eb1737..4323b3199d57 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt @@ -47,12 +47,10 @@ import kotlinx.coroutines.launch class QSHostAdapter @Inject constructor( - private val qsTileHost: QSTileHost, private val interactor: CurrentTilesInteractor, private val context: Context, private val tileServiceRequestControllerBuilder: TileServiceRequestController.Builder, @Application private val scope: CoroutineScope, - flags: QSPipelineFlagsRepository, dumpManager: DumpManager, ) : QSHost { @@ -60,123 +58,69 @@ constructor( private const val TAG = "QSTileHost" } - private val useNewHost = flags.pipelineEnabled - @GuardedBy("callbacksMap") private val callbacksMap = mutableMapOf<QSHost.Callback, Job>() init { scope.launch { tileServiceRequestControllerBuilder.create(this@QSHostAdapter).init() } // Redirect dump to the correct host (needed for CTS tests) - dumpManager.registerCriticalDumpable(TAG, if (useNewHost) interactor else qsTileHost) + dumpManager.registerCriticalDumpable(TAG, interactor) } override fun getTiles(): Collection<QSTile> { - return if (useNewHost) { - interactor.currentQSTiles - } else { - qsTileHost.getTiles() - } + return interactor.currentQSTiles } override fun getSpecs(): List<String> { - return if (useNewHost) { - interactor.currentTilesSpecs.map { it.spec } - } else { - qsTileHost.getSpecs() - } + return interactor.currentTilesSpecs.map { it.spec } } override fun removeTile(spec: String) { - if (useNewHost) { - interactor.removeTiles(listOf(TileSpec.create(spec))) - } else { - qsTileHost.removeTile(spec) - } + interactor.removeTiles(listOf(TileSpec.create(spec))) } override fun addCallback(callback: QSHost.Callback) { - if (useNewHost) { - val job = scope.launch { interactor.currentTiles.collect { callback.onTilesChanged() } } - synchronized(callbacksMap) { callbacksMap.put(callback, job) } - } else { - qsTileHost.addCallback(callback) - } + val job = scope.launch { interactor.currentTiles.collect { callback.onTilesChanged() } } + synchronized(callbacksMap) { callbacksMap.put(callback, job) } } override fun removeCallback(callback: QSHost.Callback) { - if (useNewHost) { - synchronized(callbacksMap) { callbacksMap.remove(callback)?.cancel() } - } else { - qsTileHost.removeCallback(callback) - } + synchronized(callbacksMap) { callbacksMap.remove(callback)?.cancel() } } override fun removeTiles(specs: Collection<String>) { - if (useNewHost) { - interactor.removeTiles(specs.map(TileSpec::create)) - } else { - qsTileHost.removeTiles(specs) - } + interactor.removeTiles(specs.map(TileSpec::create)) } override fun removeTileByUser(component: ComponentName) { - if (useNewHost) { - interactor.removeTiles(listOf(TileSpec.create(component))) - } else { - qsTileHost.removeTileByUser(component) - } + interactor.removeTiles(listOf(TileSpec.create(component))) } override fun addTile(spec: String, position: Int) { - if (useNewHost) { - interactor.addTile(TileSpec.create(spec), position) - } else { - qsTileHost.addTile(spec, position) - } + interactor.addTile(TileSpec.create(spec), position) } override fun addTile(component: ComponentName, end: Boolean) { - if (useNewHost) { - interactor.addTile(TileSpec.create(component), if (end) POSITION_AT_END else 0) - } else { - qsTileHost.addTile(component, end) - } + interactor.addTile(TileSpec.create(component), if (end) POSITION_AT_END else 0) } override fun changeTilesByUser(previousTiles: List<String>, newTiles: List<String>) { - if (useNewHost) { - interactor.setTiles(newTiles.map(TileSpec::create)) - } else { - qsTileHost.changeTilesByUser(previousTiles, newTiles) - } + interactor.setTiles(newTiles.map(TileSpec::create)) } override fun getContext(): Context { - return if (useNewHost) { - context - } else { - qsTileHost.context - } + return context } override fun getUserContext(): Context { - return if (useNewHost) { - interactor.userContext.value - } else { - qsTileHost.userContext - } + return interactor.userContext.value } override fun getUserId(): Int { - return if (useNewHost) { - interactor.userId.value - } else { - qsTileHost.userId - } + return interactor.userId.value } override fun createTile(tileSpec: String): QSTile? { - return qsTileHost.createTile(tileSpec) + return interactor.createTileSync(TileSpec.create(tileSpec)) } override fun addTile(spec: String) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSModesEvent.kt b/packages/SystemUI/src/com/android/systemui/qs/QSModesEvent.kt new file mode 100644 index 000000000000..1891c41f364b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/QSModesEvent.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs + +import com.android.internal.logging.UiEvent +import com.android.internal.logging.UiEventLogger + +/** Events of user interactions with modes from the QS Modes dialog. {@see ModesDialogViewModel} */ +enum class QSModesEvent(private val _id: Int) : UiEventLogger.UiEventEnum { + @UiEvent(doc = "User turned manual Do Not Disturb on via modes dialog") QS_MODES_DND_ON(1870), + @UiEvent(doc = "User turned manual Do Not Disturb off via modes dialog") QS_MODES_DND_OFF(1871), + @UiEvent(doc = "User opened mode settings from the Do Not Disturb tile in the modes dialog") + QS_MODES_DND_SETTINGS(1872), + @UiEvent(doc = "User turned automatic mode on via modes dialog") QS_MODES_MODE_ON(1873), + @UiEvent(doc = "User turned automatic mode off via modes dialog") QS_MODES_MODE_OFF(1874), + @UiEvent(doc = "User opened mode settings from a mode tile in the modes dialog") + QS_MODES_MODE_SETTINGS(1875), + @UiEvent(doc = "User clicked on Settings from the modes dialog") QS_MODES_SETTINGS(1876), + @UiEvent(doc = "User clicked on Do Not Disturb tile, opening the time selection dialog") + QS_MODES_DURATION_DIALOG(1879); + + override fun getId() = _id +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java deleted file mode 100644 index 03c2aa6f4bc4..000000000000 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java +++ /dev/null @@ -1,630 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file - * except in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the - * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -package com.android.systemui.qs; - -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.res.Resources; -import android.os.UserHandle; -import android.os.UserManager; -import android.provider.Settings.Secure; -import android.text.TextUtils; -import android.util.ArraySet; -import android.util.Log; - -import androidx.annotation.MainThread; -import androidx.annotation.Nullable; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.systemui.Dumpable; -import com.android.systemui.ProtoDumpable; -import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.dump.nano.SystemUIProtoDump; -import com.android.systemui.plugins.PluginListener; -import com.android.systemui.plugins.PluginManager; -import com.android.systemui.plugins.qs.QSFactory; -import com.android.systemui.plugins.qs.QSTile; -import com.android.systemui.qs.external.CustomTile; -import com.android.systemui.qs.external.CustomTileStatePersister; -import com.android.systemui.qs.external.TileLifecycleManager; -import com.android.systemui.qs.external.TileServiceKey; -import com.android.systemui.qs.logging.QSLogger; -import com.android.systemui.qs.nano.QsTileState; -import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository; -import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor; -import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository; -import com.android.systemui.qs.tiles.di.NewQSTileFactory; -import com.android.systemui.res.R; -import com.android.systemui.settings.UserFileManager; -import com.android.systemui.settings.UserTracker; -import com.android.systemui.shade.ShadeController; -import com.android.systemui.statusbar.phone.AutoTileManager; -import com.android.systemui.tuner.TunerService; -import com.android.systemui.tuner.TunerService.Tunable; -import com.android.systemui.util.settings.SecureSettings; - -import dagger.Lazy; - -import org.jetbrains.annotations.NotNull; - -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.Collection; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Objects; -import java.util.Set; -import java.util.concurrent.Executor; -import java.util.function.Predicate; -import java.util.stream.Collectors; - -import javax.inject.Inject; -import javax.inject.Provider; - -/** Platform implementation of the quick settings tile host - * - * This class keeps track of the set of current tiles and is the in memory source of truth - * (ground truth is kept in {@link Secure#QS_TILES}). When the ground truth changes, - * {@link #onTuningChanged} will be called and the tiles will be re-created as needed. - * - * This class also provides the interface for adding/removing/changing tiles. - */ -@SysUISingleton -public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, ProtoDumpable, - PanelInteractor, CustomTileAddedRepository { - private static final String TAG = "QSTileHost"; - private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - - // Shared prefs that hold tile lifecycle info. - @VisibleForTesting - static final String TILES = "tiles_prefs"; - - private final Context mContext; - private final LinkedHashMap<String, QSTile> mTiles = new LinkedHashMap<>(); - private final ArrayList<String> mTileSpecs = new ArrayList<>(); - private final TunerService mTunerService; - private final PluginManager mPluginManager; - private final QSLogger mQSLogger; - private final CustomTileStatePersister mCustomTileStatePersister; - private final Executor mMainExecutor; - private final UserFileManager mUserFileManager; - - private final List<Callback> mCallbacks = new ArrayList<>(); - @Nullable - private AutoTileManager mAutoTiles; - private final ArrayList<QSFactory> mQsFactories = new ArrayList<>(); - private int mCurrentUser; - private final Lazy<ShadeController> mShadeControllerProvider; - private Context mUserContext; - private UserTracker mUserTracker; - private SecureSettings mSecureSettings; - // Keep track of whether mTilesList contains the same information as the Settings value. - // This is a performance optimization to reduce the number of blocking calls to Settings from - // main thread. - // This is enforced by only cleaning the flag at the end of a successful run of #onTuningChanged - private boolean mTilesListDirty = true; - - private TileLifecycleManager.Factory mTileLifeCycleManagerFactory; - - private final QSPipelineFlagsRepository mFeatureFlags; - - @Inject - public QSTileHost(Context context, - Lazy<NewQSTileFactory> newQsTileFactoryProvider, - QSFactory defaultFactory, - @Main Executor mainExecutor, - PluginManager pluginManager, - TunerService tunerService, - Provider<AutoTileManager> autoTiles, - Lazy<ShadeController> shadeControllerProvider, - QSLogger qsLogger, - UserTracker userTracker, - SecureSettings secureSettings, - CustomTileStatePersister customTileStatePersister, - TileLifecycleManager.Factory tileLifecycleManagerFactory, - UserFileManager userFileManager, - QSPipelineFlagsRepository featureFlags - ) { - mContext = context; - mUserContext = context; - mTunerService = tunerService; - mPluginManager = pluginManager; - mQSLogger = qsLogger; - mMainExecutor = mainExecutor; - mTileLifeCycleManagerFactory = tileLifecycleManagerFactory; - mUserFileManager = userFileManager; - mFeatureFlags = featureFlags; - - mShadeControllerProvider = shadeControllerProvider; - - if (featureFlags.getTilesEnabled()) { - mQsFactories.add(newQsTileFactoryProvider.get()); - } - mQsFactories.add(defaultFactory); - pluginManager.addPluginListener(this, QSFactory.class, true); - mUserTracker = userTracker; - mCurrentUser = userTracker.getUserId(); - mSecureSettings = secureSettings; - mCustomTileStatePersister = customTileStatePersister; - - mainExecutor.execute(() -> { - // This is technically a hack to avoid circular dependency of - // QSTileHost -> XXXTile -> QSTileHost. Posting ensures creation - // finishes before creating any tiles. - tunerService.addTunable(this, TILES_SETTING); - // AutoTileManager can modify mTiles so make sure mTiles has already been initialized. - if (!mFeatureFlags.getPipelineEnabled()) { - mAutoTiles = autoTiles.get(); - } - }); - } - - public void destroy() { - mTiles.values().forEach(tile -> tile.destroy()); - mAutoTiles.destroy(); - mTunerService.removeTunable(this); - mPluginManager.removePluginListener(this); - } - - @Override - public void onPluginConnected(QSFactory plugin, Context pluginContext) { - // Give plugins priority over creation so they can override if they wish. - mQsFactories.add(0, plugin); - String value = mTunerService.getValue(TILES_SETTING); - // Force remove and recreate of all tiles. - onTuningChanged(TILES_SETTING, ""); - onTuningChanged(TILES_SETTING, value); - } - - @Override - public void onPluginDisconnected(QSFactory plugin) { - mQsFactories.remove(plugin); - // Force remove and recreate of all tiles. - String value = mTunerService.getValue(TILES_SETTING); - onTuningChanged(TILES_SETTING, ""); - onTuningChanged(TILES_SETTING, value); - } - - @Override - public void addCallback(Callback callback) { - mCallbacks.add(callback); - } - - @Override - public void removeCallback(Callback callback) { - mCallbacks.remove(callback); - } - - @Override - public Collection<QSTile> getTiles() { - return mTiles.values(); - } - - @Override - public void collapsePanels() { - mShadeControllerProvider.get().postAnimateCollapseShade(); - } - - @Override - public void forceCollapsePanels() { - mShadeControllerProvider.get().postAnimateForceCollapseShade(); - } - - @Override - public void openPanels() { - mShadeControllerProvider.get().postAnimateExpandQs(); - } - - @Override - public Context getContext() { - return mContext; - } - - @Override - public Context getUserContext() { - return mUserContext; - } - - @Override - public int getUserId() { - return mCurrentUser; - } - - public int indexOf(String spec) { - return mTileSpecs.indexOf(spec); - } - - /** - * Whenever the Secure Setting keeping track of the current tiles changes (or upon start) this - * will be called with the new value of the setting. - * - * This method will do the following: - * <ol> - * <li>Destroy any existing tile that's not one of the current tiles (in the setting)</li> - * <li>Create new tiles for those that don't already exist. If this tiles end up being - * not available, they'll also be destroyed.</li> - * <li>Save the resolved list of tiles (current tiles that are available) into the setting. - * This means that after this call ends, the tiles in the Setting, {@link #mTileSpecs}, - * and visible tiles ({@link #mTiles}) must match. - * </li> - * </ol> - * - * Additionally, if the user has changed, it'll do the following: - * <ul> - * <li>Change the user for SystemUI tiles: {@link QSTile#userSwitch}.</li> - * <li>Destroy any {@link CustomTile} and recreate it for the new user.</li> - * </ul> - * - * This happens in main thread as {@link com.android.systemui.tuner.TunerServiceImpl} dispatches - * in main thread. - * - * @see QSTile#isAvailable - */ - @MainThread - @Override - public void onTuningChanged(String key, String newValue) { - if (!TILES_SETTING.equals(key)) { - return; - } - int currentUser = mUserTracker.getUserId(); - if (currentUser != mCurrentUser) { - mUserContext = mUserTracker.getUserContext(); - if (mAutoTiles != null) { - mAutoTiles.changeUser(UserHandle.of(currentUser)); - } - } - // Do not process tiles if the flag is enabled. - if (mFeatureFlags.getPipelineEnabled()) { - return; - } - QSPipelineFlagsRepository.Utils.assertInLegacyMode(); - if (newValue == null && UserManager.isDeviceInDemoMode(mContext)) { - newValue = mContext.getResources().getString(R.string.quick_settings_tiles_retail_mode); - } - final List<String> tileSpecs = loadTileSpecs(mContext, newValue); - if (tileSpecs.equals(mTileSpecs) && currentUser == mCurrentUser) return; - Log.d(TAG, "Recreating tiles: " + tileSpecs); - mTiles.entrySet().stream().filter(tile -> !tileSpecs.contains(tile.getKey())).forEach( - tile -> { - Log.d(TAG, "Destroying tile: " + tile.getKey()); - mQSLogger.logTileDestroyed(tile.getKey(), "Tile removed"); - tile.getValue().destroy(); - }); - final LinkedHashMap<String, QSTile> newTiles = new LinkedHashMap<>(); - for (String tileSpec : tileSpecs) { - QSTile tile = mTiles.get(tileSpec); - if (tile != null && (!(tile instanceof CustomTile) - || ((CustomTile) tile).getUser() == currentUser)) { - if (tile.isAvailable()) { - Log.d(TAG, "Adding " + tile); - tile.removeCallbacks(); - if (!(tile instanceof CustomTile) && mCurrentUser != currentUser) { - tile.userSwitch(currentUser); - } - newTiles.put(tileSpec, tile); - mQSLogger.logTileAdded(tileSpec); - } else { - tile.destroy(); - Log.d(TAG, "Destroying not available tile: " + tileSpec); - mQSLogger.logTileDestroyed(tileSpec, "Tile not available"); - } - } else { - // This means that the tile is a CustomTile AND the user is different, so let's - // destroy it - if (tile != null) { - tile.destroy(); - Log.d(TAG, "Destroying tile for wrong user: " + tileSpec); - mQSLogger.logTileDestroyed(tileSpec, "Tile for wrong user"); - } - Log.d(TAG, "Creating tile: " + tileSpec); - try { - tile = createTile(tileSpec); - if (tile != null) { - if (tile.isAvailable()) { - newTiles.put(tileSpec, tile); - mQSLogger.logTileAdded(tileSpec); - } else { - tile.destroy(); - Log.d(TAG, "Destroying not available tile: " + tileSpec); - mQSLogger.logTileDestroyed(tileSpec, "Tile not available"); - } - } else { - Log.d(TAG, "No factory for a spec: " + tileSpec); - } - } catch (Throwable t) { - Log.w(TAG, "Error creating tile for spec: " + tileSpec, t); - } - } - } - mCurrentUser = currentUser; - List<String> currentSpecs = new ArrayList<>(mTileSpecs); - mTileSpecs.clear(); - mTileSpecs.addAll(newTiles.keySet()); // Only add the valid (available) tiles. - mTiles.clear(); - mTiles.putAll(newTiles); - if (newTiles.isEmpty() && !tileSpecs.isEmpty()) { - // If we didn't manage to create any tiles, set it to empty (default) - Log.d(TAG, "No valid tiles on tuning changed. Setting to default."); - changeTilesByUser(currentSpecs, loadTileSpecs(mContext, "")); - } else { - String resolvedTiles = TextUtils.join(",", mTileSpecs); - if (!resolvedTiles.equals(newValue)) { - // If the resolved tiles (those we actually ended up with) are different than - // the ones that are in the setting, update the Setting. - saveTilesToSettings(mTileSpecs); - } - mTilesListDirty = false; - for (int i = 0; i < mCallbacks.size(); i++) { - mCallbacks.get(i).onTilesChanged(); - } - } - } - - /** - * Only use with [CustomTile] if the tile doesn't exist anymore (and therefore doesn't need - * its lifecycle terminated). - */ - @Override - public void removeTile(String spec) { - if (spec.startsWith(CustomTile.PREFIX)) { - // If the tile is removed (due to it not actually existing), mark it as removed. That - // way it will be marked as newly added if it appears in the future. - setTileAdded(CustomTile.getComponentFromSpec(spec), mCurrentUser, false); - } - mMainExecutor.execute(() -> changeTileSpecs(tileSpecs-> tileSpecs.remove(spec))); - } - - /** - * Remove many tiles at once. - * - * It will only save to settings once (as opposed to {@link QSTileHost#removeTileByUser} called - * multiple times). - */ - @Override - public void removeTiles(Collection<String> specs) { - mMainExecutor.execute(() -> changeTileSpecs(tileSpecs -> tileSpecs.removeAll(specs))); - } - - /** - * Add a tile to the end - * - * @param spec string matching a pre-defined tilespec - */ - public void addTile(String spec) { - addTile(spec, POSITION_AT_END); - } - - @Override - public void addTile(String spec, int requestPosition) { - mMainExecutor.execute(() -> - changeTileSpecs(tileSpecs -> { - if (tileSpecs.contains(spec)) return false; - - int size = tileSpecs.size(); - if (requestPosition == POSITION_AT_END || requestPosition >= size) { - tileSpecs.add(spec); - } else { - tileSpecs.add(requestPosition, spec); - } - return true; - }) - ); - } - - // When calling this, you may want to modify mTilesListDirty accordingly. - @MainThread - private void saveTilesToSettings(List<String> tileSpecs) { - Log.d(TAG, "Saving tiles: " + tileSpecs + " for user: " + mCurrentUser); - mSecureSettings.putStringForUser(TILES_SETTING, TextUtils.join(",", tileSpecs), - null /* tag */, false /* default */, mCurrentUser, - true /* overrideable by restore */); - } - - @MainThread - private void changeTileSpecs(Predicate<List<String>> changeFunction) { - final List<String> tileSpecs; - if (!mTilesListDirty) { - tileSpecs = new ArrayList<>(mTileSpecs); - } else { - tileSpecs = loadTileSpecs(mContext, - mSecureSettings.getStringForUser(TILES_SETTING, mCurrentUser)); - } - if (changeFunction.test(tileSpecs)) { - mTilesListDirty = true; - saveTilesToSettings(tileSpecs); - } - } - - @Override - public void addTile(ComponentName tile) { - addTile(tile, /* end */ false); - } - - @Override - public void addTile(ComponentName tile, boolean end) { - String spec = CustomTile.toSpec(tile); - addTile(spec, end ? POSITION_AT_END : 0); - } - - /** - * This will call through {@link #changeTilesByUser}. It should only be used when a tile is - * removed by a <b>user action</b> like {@code adb}. - */ - @Override - public void removeTileByUser(ComponentName tile) { - mMainExecutor.execute(() -> { - List<String> newSpecs = new ArrayList<>(mTileSpecs); - if (newSpecs.remove(CustomTile.toSpec(tile))) { - changeTilesByUser(mTileSpecs, newSpecs); - } - }); - } - - /** - * Change the tiles triggered by the user editing. - * <p> - * This is not called on device start, or on user change. - * - * {@link android.service.quicksettings.TileService#onTileRemoved} will be called for tiles - * that are removed. - */ - @MainThread - @Override - public void changeTilesByUser(List<String> previousTiles, List<String> newTiles) { - final List<String> copy = new ArrayList<>(previousTiles); - final int NP = copy.size(); - for (int i = 0; i < NP; i++) { - String tileSpec = copy.get(i); - if (!tileSpec.startsWith(CustomTile.PREFIX)) continue; - if (!newTiles.contains(tileSpec)) { - ComponentName component = CustomTile.getComponentFromSpec(tileSpec); - Intent intent = new Intent().setComponent(component); - TileLifecycleManager lifecycleManager = mTileLifeCycleManagerFactory.create( - intent, new UserHandle(mCurrentUser)); - lifecycleManager.onStopListening(); - lifecycleManager.onTileRemoved(); - mCustomTileStatePersister.removeState(new TileServiceKey(component, mCurrentUser)); - setTileAdded(component, mCurrentUser, false); - lifecycleManager.flushMessagesAndUnbind(); - } - } - Log.d(TAG, "saveCurrentTiles " + newTiles); - mTilesListDirty = true; - saveTilesToSettings(newTiles); - } - - @Nullable - @Override - public QSTile createTile(String tileSpec) { - for (int i = 0; i < mQsFactories.size(); i++) { - QSTile t = mQsFactories.get(i).createTile(tileSpec); - if (t != null) { - return t; - } - } - return null; - } - - /** - * Check if a particular {@link CustomTile} has been added for a user and has not been removed - * since. - * @param componentName the {@link ComponentName} of the - * {@link android.service.quicksettings.TileService} associated with the - * tile. - * @param userId the user to check - */ - @Override - public boolean isTileAdded(ComponentName componentName, int userId) { - return mUserFileManager - .getSharedPreferences(TILES, 0, userId) - .getBoolean(componentName.flattenToString(), false); - } - - /** - * Persists whether a particular {@link CustomTile} has been added and it's currently in the - * set of selected tiles ({@link #mTiles}. - * @param componentName the {@link ComponentName} of the - * {@link android.service.quicksettings.TileService} associated - * with the tile. - * @param userId the user for this tile - * @param added {@code true} if the tile is being added, {@code false} otherwise - */ - @Override - public void setTileAdded(ComponentName componentName, int userId, boolean added) { - mUserFileManager.getSharedPreferences(TILES, 0, userId) - .edit() - .putBoolean(componentName.flattenToString(), added) - .apply(); - } - - @Override - public List<String> getSpecs() { - return mTileSpecs; - } - - protected static List<String> loadTileSpecs(Context context, String tileList) { - final Resources res = context.getResources(); - - if (TextUtils.isEmpty(tileList)) { - tileList = res.getString(R.string.quick_settings_tiles); - Log.d(TAG, "Loaded tile specs from default config: " + tileList); - } else { - Log.d(TAG, "Loaded tile specs from setting: " + tileList); - } - final ArrayList<String> tiles = new ArrayList<String>(); - boolean addedDefault = false; - Set<String> addedSpecs = new ArraySet<>(); - for (String tile : tileList.split(",")) { - tile = tile.trim(); - if (tile.isEmpty()) continue; - if (tile.equals("default")) { - if (!addedDefault) { - List<String> defaultSpecs = QSHost.getDefaultSpecs(context.getResources()); - for (String spec : defaultSpecs) { - if (!addedSpecs.contains(spec)) { - tiles.add(spec); - addedSpecs.add(spec); - } - } - addedDefault = true; - } - } else { - if (!addedSpecs.contains(tile)) { - tiles.add(tile); - addedSpecs.add(tile); - } - } - } - - if (!tiles.contains("internet")) { - if (tiles.contains("wifi")) { - // Replace the WiFi with Internet, and remove the Cell - tiles.set(tiles.indexOf("wifi"), "internet"); - tiles.remove("cell"); - } else if (tiles.contains("cell")) { - // Replace the Cell with Internet - tiles.set(tiles.indexOf("cell"), "internet"); - } - } else { - tiles.remove("wifi"); - tiles.remove("cell"); - } - return tiles; - } - - @Override - public void dump(PrintWriter pw, String[] args) { - pw.println("QSTileHost:"); - pw.println("tile specs: " + mTileSpecs); - pw.println("current user: " + mCurrentUser); - pw.println("is dirty: " + mTilesListDirty); - pw.println("tiles:"); - mTiles.values().stream().filter(obj -> obj instanceof Dumpable) - .forEach(o -> ((Dumpable) o).dump(pw, args)); - } - - @Override - public void dumpProto(@NotNull SystemUIProtoDump systemUIProtoDump, @NotNull String[] args) { - List<QsTileState> data = mTiles.values().stream() - .map(QSTile::getState) - .map(TileStateToProtoKt::toProto) - .filter(Objects::nonNull) - .collect(Collectors.toList()); - - systemUIProtoDump.tiles = data.toArray(new QsTileState[0]); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java index f207b1de3cba..bc695bdd4e05 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java @@ -34,6 +34,7 @@ import com.android.systemui.qs.customize.QSCustomizerController; import com.android.systemui.qs.dagger.QSScope; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.res.R; +import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.statusbar.policy.SplitShadeStateController; import com.android.systemui.util.leak.RotationUtils; @@ -77,9 +78,11 @@ public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel> @Override protected void onInit() { super.onInit(); - updateMediaExpansion(); - mMediaHost.setShowsOnlyActiveMedia(true); - mMediaHost.init(MediaHierarchyManager.LOCATION_QQS); + if (!SceneContainerFlag.isEnabled()) { + updateMediaExpansion(); + mMediaHost.setShowsOnlyActiveMedia(true); + mMediaHost.init(MediaHierarchyManager.LOCATION_QQS); + } } @Override @@ -125,7 +128,9 @@ public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel> if (newMaxTiles != mView.getNumQuickTiles()) { setMaxTiles(newMaxTiles); } - updateMediaExpansion(); + if (!SceneContainerFlag.isEnabled()) { + updateMediaExpansion(); + } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt new file mode 100644 index 000000000000..5d81d4f3f9ec --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt @@ -0,0 +1,442 @@ +/* + * 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.qs.composefragment + +import android.annotation.SuppressLint +import android.graphics.Rect +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.activity.OnBackPressedDispatcher +import androidx.activity.OnBackPressedDispatcherOwner +import androidx.activity.setViewTreeOnBackPressedDispatcherOwner +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.navigationBars +import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.layout +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.layout.positionInRoot +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.unit.round +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import com.android.compose.modifiers.height +import com.android.compose.modifiers.padding +import com.android.compose.theme.PlatformTheme +import com.android.systemui.compose.modifiers.sysuiResTag +import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.plugins.qs.QS +import com.android.systemui.plugins.qs.QSContainerController +import com.android.systemui.qs.composefragment.viewmodel.QSFragmentComposeViewModel +import com.android.systemui.qs.flags.QSComposeFragment +import com.android.systemui.qs.footer.ui.compose.FooterActions +import com.android.systemui.qs.panels.ui.compose.QuickQuickSettings +import com.android.systemui.qs.ui.composable.QuickSettingsTheme +import com.android.systemui.qs.ui.composable.ShadeBody +import com.android.systemui.res.R +import com.android.systemui.util.LifecycleFragment +import java.util.function.Consumer +import javax.inject.Inject +import kotlinx.coroutines.awaitCancellation +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch + +@SuppressLint("ValidFragment") +class QSFragmentCompose +@Inject +constructor( + private val qsFragmentComposeViewModelFactory: QSFragmentComposeViewModel.Factory, +) : LifecycleFragment(), QS { + + private val scrollListener = MutableStateFlow<QS.ScrollListener?>(null) + private val heightListener = MutableStateFlow<QS.HeightListener?>(null) + private val qsContainerController = MutableStateFlow<QSContainerController?>(null) + + private lateinit var viewModel: QSFragmentComposeViewModel + + // Starting with a non-zero value makes it so that it has a non-zero height on first expansion + // This is important for `QuickSettingsControllerImpl.mMinExpansionHeight` to detect a "change". + private val qqsHeight = MutableStateFlow(1) + private val qsHeight = MutableStateFlow(0) + private val qqsVisible = MutableStateFlow(false) + private val qqsPositionOnRoot = Rect() + private val composeViewPositionOnScreen = Rect() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + QSComposeFragment.isUnexpectedlyInLegacyMode() + viewModel = qsFragmentComposeViewModelFactory.create(lifecycleScope) + + setListenerCollections() + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + val context = inflater.context + return ComposeView(context).apply { + setBackPressedDispatcher() + setContent { + PlatformTheme { + val visible by viewModel.qsVisible.collectAsStateWithLifecycle() + val qsState by viewModel.expansionState.collectAsStateWithLifecycle() + + AnimatedVisibility( + visible = visible, + modifier = Modifier.windowInsetsPadding(WindowInsets.navigationBars) + ) { + AnimatedContent(targetState = qsState) { + when (it) { + QSFragmentComposeViewModel.QSExpansionState.QQS -> { + QuickQuickSettingsElement() + } + QSFragmentComposeViewModel.QSExpansionState.QS -> { + QuickSettingsElement() + } + else -> {} + } + } + } + } + } + } + } + + override fun setPanelView(notificationPanelView: QS.HeightListener?) { + heightListener.value = notificationPanelView + } + + override fun hideImmediately() { + // view?.animate()?.cancel() + // view?.y = -qsMinExpansionHeight.toFloat() + } + + override fun getQsMinExpansionHeight(): Int { + // TODO (b/353253277) implement split screen + return qqsHeight.value + } + + override fun getDesiredHeight(): Int { + /* + * Looking at the code, it seems that + * * If customizing, then the height is that of the view post-layout, which is set by + * QSContainerImpl.calculateContainerHeight, which is the height the customizer takes + * * If not customizing, it's the measured height. So we may want to surface that. + */ + return view?.height ?: 0 + } + + override fun setHeightOverride(desiredHeight: Int) { + viewModel.heightOverrideValue = desiredHeight + } + + override fun setHeaderClickable(qsExpansionEnabled: Boolean) { + // Empty method + } + + override fun isCustomizing(): Boolean { + return viewModel.containerViewModel.editModeViewModel.isEditing.value + } + + override fun closeCustomizer() { + viewModel.containerViewModel.editModeViewModel.stopEditing() + } + + override fun setOverscrolling(overscrolling: Boolean) { + viewModel.stackScrollerOverscrollingValue = overscrolling + } + + override fun setExpanded(qsExpanded: Boolean) { + viewModel.isQSExpanded = qsExpanded + } + + override fun setListening(listening: Boolean) { + // Not needed, views start listening and collection when composed + } + + override fun setQsVisible(qsVisible: Boolean) { + viewModel.isQSVisible = qsVisible + } + + override fun isShowingDetail(): Boolean { + return isCustomizing + } + + override fun closeDetail() { + closeCustomizer() + } + + override fun animateHeaderSlidingOut() { + // TODO(b/353254353) + } + + override fun setQsExpansion( + qsExpansionFraction: Float, + panelExpansionFraction: Float, + headerTranslation: Float, + squishinessFraction: Float + ) { + viewModel.qsExpansionValue = qsExpansionFraction + viewModel.panelExpansionFractionValue = panelExpansionFraction + viewModel.squishinessFractionValue = squishinessFraction + + // TODO(b/353254353) Handle header translation + } + + override fun setHeaderListening(listening: Boolean) { + // Not needed, header will start listening as soon as it's composed + } + + override fun notifyCustomizeChanged() { + // Not needed, only called from inside customizer + } + + override fun setContainerController(controller: QSContainerController?) { + qsContainerController.value = controller + } + + override fun setCollapseExpandAction(action: Runnable?) { + // Nothing to do yet. But this should be wired to a11y + } + + override fun getHeightDiff(): Int { + return 0 // For now TODO(b/353254353) + } + + override fun getHeader(): View? { + QSComposeFragment.isUnexpectedlyInLegacyMode() + return null + } + + override fun setShouldUpdateSquishinessOnMedia(shouldUpdate: Boolean) { + super.setShouldUpdateSquishinessOnMedia(shouldUpdate) + // TODO (b/353253280) + } + + override fun setInSplitShade(shouldTranslate: Boolean) { + // TODO (b/356435605) + } + + override fun setTransitionToFullShadeProgress( + isTransitioningToFullShade: Boolean, + qsTransitionFraction: Float, + qsSquishinessFraction: Float + ) { + super.setTransitionToFullShadeProgress( + isTransitioningToFullShade, + qsTransitionFraction, + qsSquishinessFraction + ) + } + + override fun setFancyClipping( + leftInset: Int, + top: Int, + rightInset: Int, + bottom: Int, + cornerRadius: Int, + visible: Boolean, + fullWidth: Boolean + ) {} + + override fun isFullyCollapsed(): Boolean { + return !viewModel.isQSVisible + } + + override fun setCollapsedMediaVisibilityChangedListener(listener: Consumer<Boolean>?) { + // TODO (b/353253280) + } + + override fun setScrollListener(scrollListener: QS.ScrollListener?) { + this.scrollListener.value = scrollListener + } + + override fun setOverScrollAmount(overScrollAmount: Int) { + super.setOverScrollAmount(overScrollAmount) + } + + override fun setIsNotificationPanelFullWidth(isFullWidth: Boolean) { + viewModel.isSmallScreenValue = isFullWidth + } + + override fun getHeaderTop(): Int { + return viewModel.qqsHeaderHeight.value + } + + override fun getHeaderBottom(): Int { + return headerTop + qqsHeight.value + } + + override fun getHeaderLeft(): Int { + return qqsPositionOnRoot.left + } + + override fun getHeaderBoundsOnScreen(outBounds: Rect) { + outBounds.set(qqsPositionOnRoot) + view?.getBoundsOnScreen(composeViewPositionOnScreen) + ?: run { composeViewPositionOnScreen.setEmpty() } + qqsPositionOnRoot.offset(composeViewPositionOnScreen.left, composeViewPositionOnScreen.top) + } + + override fun isHeaderShown(): Boolean { + return qqsVisible.value + } + + private fun setListenerCollections() { + lifecycleScope.launch { + lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { + launch { + // TODO + // setListenerJob( + // scrollListener, + // + // ) + } + launch { + setListenerJob( + heightListener, + viewModel.containerViewModel.editModeViewModel.isEditing + ) { + onQsHeightChanged() + } + } + launch { + setListenerJob( + qsContainerController, + viewModel.containerViewModel.editModeViewModel.isEditing + ) { + setCustomizerShowing(it) + } + } + } + } + } + + @Composable + private fun QuickQuickSettingsElement() { + val qqsPadding by viewModel.qqsHeaderHeight.collectAsStateWithLifecycle() + DisposableEffect(Unit) { + qqsVisible.value = true + + onDispose { qqsVisible.value = false } + } + Column(modifier = Modifier.sysuiResTag("quick_qs_panel")) { + QuickQuickSettings( + viewModel = viewModel.containerViewModel.quickQuickSettingsViewModel, + modifier = + Modifier.onGloballyPositioned { coordinates -> + val (leftFromRoot, topFromRoot) = coordinates.positionInRoot().round() + val (width, height) = coordinates.size + qqsPositionOnRoot.set( + leftFromRoot, + topFromRoot, + leftFromRoot + width, + topFromRoot + height + ) + } + .layout { measurable, constraints -> + val placeable = measurable.measure(constraints) + qqsHeight.value = placeable.height + + layout(placeable.width, placeable.height) { placeable.place(0, 0) } + } + .padding(top = { qqsPadding }) + ) + Spacer(modifier = Modifier.weight(1f)) + } + } + + @Composable + private fun QuickSettingsElement() { + val qqsPadding by viewModel.qqsHeaderHeight.collectAsStateWithLifecycle() + val qsExtraPadding = dimensionResource(R.dimen.qs_panel_padding_top) + Column { + Box(modifier = Modifier.fillMaxSize().weight(1f)) { + Column { + Spacer(modifier = Modifier.height { qqsPadding + qsExtraPadding.roundToPx() }) + ShadeBody(viewModel = viewModel.containerViewModel) + } + } + QuickSettingsTheme { + FooterActions( + viewModel = viewModel.footerActionsViewModel, + qsVisibilityLifecycleOwner = this@QSFragmentCompose, + modifier = Modifier.sysuiResTag("qs_footer_actions") + ) + } + } + } +} + +private fun View.setBackPressedDispatcher() { + repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.CREATED) { + setViewTreeOnBackPressedDispatcherOwner( + object : OnBackPressedDispatcherOwner { + override val onBackPressedDispatcher = + OnBackPressedDispatcher().apply { + setOnBackInvokedDispatcher(it.viewRootImpl.onBackInvokedDispatcher) + } + + override val lifecycle: Lifecycle = this@repeatWhenAttached.lifecycle + } + ) + } + } +} + +private suspend inline fun <Listener : Any, Data> setListenerJob( + listenerFlow: MutableStateFlow<Listener?>, + dataFlow: Flow<Data>, + crossinline onCollect: suspend Listener.(Data) -> Unit +) { + coroutineScope { + try { + listenerFlow.collectLatest { listenerOrNull -> + listenerOrNull?.let { currentListener -> + launch { + // Called when editing mode changes + dataFlow.collect { currentListener.onCollect(it) } + } + } + } + awaitCancellation() + } finally { + listenerFlow.value = null + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt new file mode 100644 index 000000000000..9e109e436226 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt @@ -0,0 +1,196 @@ +/* + * 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.qs.composefragment.viewmodel + +import android.content.res.Resources +import android.graphics.Rect +import androidx.lifecycle.LifecycleCoroutineScope +import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.qs.FooterActionsController +import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel +import com.android.systemui.qs.ui.viewmodel.QuickSettingsContainerViewModel +import com.android.systemui.shade.LargeScreenHeaderHelper +import com.android.systemui.shade.transition.LargeScreenShadeInterpolator +import com.android.systemui.statusbar.SysuiStatusBarStateController +import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository +import com.android.systemui.statusbar.phone.KeyguardBypassController +import com.android.systemui.util.LargeScreenUtils +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch + +class QSFragmentComposeViewModel +@AssistedInject +constructor( + val containerViewModel: QuickSettingsContainerViewModel, + @Main private val resources: Resources, + private val footerActionsViewModelFactory: FooterActionsViewModel.Factory, + private val footerActionsController: FooterActionsController, + private val sysuiStatusBarStateController: SysuiStatusBarStateController, + private val keyguardBypassController: KeyguardBypassController, + private val disableFlagsRepository: DisableFlagsRepository, + private val largeScreenShadeInterpolator: LargeScreenShadeInterpolator, + private val configurationInteractor: ConfigurationInteractor, + private val largeScreenHeaderHelper: LargeScreenHeaderHelper, + @Assisted private val lifecycleScope: LifecycleCoroutineScope, +) { + val footerActionsViewModel = + footerActionsViewModelFactory.create(lifecycleScope).also { + lifecycleScope.launch { footerActionsController.init() } + } + + private val _qsBounds = MutableStateFlow(Rect()) + + private val _qsExpanded = MutableStateFlow(false) + var isQSExpanded: Boolean + get() = _qsExpanded.value + set(value) { + _qsExpanded.value = value + } + + private val _qsVisible = MutableStateFlow(false) + val qsVisible = _qsVisible.asStateFlow() + var isQSVisible: Boolean + get() = qsVisible.value + set(value) { + _qsVisible.value = value + } + + private val _qsExpansion = MutableStateFlow(0f) + var qsExpansionValue: Float + get() = _qsExpansion.value + set(value) { + _qsExpansion.value = value + } + + private val _panelFraction = MutableStateFlow(0f) + var panelExpansionFractionValue: Float + get() = _panelFraction.value + set(value) { + _panelFraction.value = value + } + + private val _squishinessFraction = MutableStateFlow(0f) + var squishinessFractionValue: Float + get() = _squishinessFraction.value + set(value) { + _squishinessFraction.value = value + } + + val qqsHeaderHeight = + configurationInteractor.onAnyConfigurationChange + .map { + if (LargeScreenUtils.shouldUseLargeScreenShadeHeader(resources)) { + 0 + } else { + largeScreenHeaderHelper.getLargeScreenHeaderHeight() + } + } + .stateIn(lifecycleScope, SharingStarted.WhileSubscribed(), 0) + + private val _headerAnimating = MutableStateFlow(false) + + private val _stackScrollerOverscrolling = MutableStateFlow(false) + var stackScrollerOverscrollingValue: Boolean + get() = _stackScrollerOverscrolling.value + set(value) { + _stackScrollerOverscrolling.value = value + } + + private val qsDisabled = + disableFlagsRepository.disableFlags + .map { !it.isQuickSettingsEnabled() } + .stateIn( + lifecycleScope, + SharingStarted.WhileSubscribed(), + !disableFlagsRepository.disableFlags.value.isQuickSettingsEnabled() + ) + + private val _showCollapsedOnKeyguard = MutableStateFlow(false) + + private val _keyguardAndExpanded = MutableStateFlow(false) + + private val _statusBarState = MutableStateFlow(-1) + + private val _viewHeight = MutableStateFlow(0) + + private val _headerTranslation = MutableStateFlow(0f) + + private val _inSplitShade = MutableStateFlow(false) + + private val _transitioningToFullShade = MutableStateFlow(false) + + private val _lockscreenToShadeProgress = MutableStateFlow(false) + + private val _overscrolling = MutableStateFlow(false) + + private val _isSmallScreen = MutableStateFlow(false) + var isSmallScreenValue: Boolean + get() = _isSmallScreen.value + set(value) { + _isSmallScreen.value = value + } + + private val _shouldUpdateMediaSquishiness = MutableStateFlow(false) + + private val _heightOverride = MutableStateFlow(-1) + val heightOverride = _heightOverride.asStateFlow() + var heightOverrideValue: Int + get() = heightOverride.value + set(value) { + _heightOverride.value = value + } + + val expansionState: StateFlow<QSExpansionState> = + combine( + _stackScrollerOverscrolling, + _qsExpanded, + _qsExpansion, + ) { args: Array<Any> -> + val expansion = args[2] as Float + if (expansion > 0.5f) { + QSExpansionState.QS + } else { + QSExpansionState.QQS + } + } + .stateIn(lifecycleScope, SharingStarted.WhileSubscribed(), QSExpansionState.QQS) + + @AssistedFactory + interface Factory { + fun create(lifecycleScope: LifecycleCoroutineScope): QSFragmentComposeViewModel + } + + sealed interface QSExpansionState { + data object QQS : QSExpansionState + + data object QS : QSExpansionState + + @JvmInline value class Expanding(val progress: Float) : QSExpansionState + + @JvmInline value class Collapsing(val progress: Float) : QSExpansionState + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt index 496a6f830e8c..a947d361a432 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt @@ -18,17 +18,14 @@ package com.android.systemui.qs.dagger import com.android.systemui.qs.QSHost import com.android.systemui.qs.QSHostAdapter -import com.android.systemui.qs.QSTileHost import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.QsEventLoggerImpl import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedSharedPrefsRepository import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractorImpl -import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository import dagger.Binds import dagger.Module -import dagger.Provides @Module interface QSHostModule { @@ -37,36 +34,10 @@ interface QSHostModule { @Binds fun provideEventLogger(impl: QsEventLoggerImpl): QsEventLogger - @Module - companion object { - private const val MAX_QS_INSTANCE_ID = 1 shl 20 + @Binds fun providePanelInteractor(impl: PanelInteractorImpl): PanelInteractor - @Provides - @JvmStatic - fun providePanelInteractor( - featureFlags: QSPipelineFlagsRepository, - qsHost: QSTileHost, - panelInteractorImpl: PanelInteractorImpl - ): PanelInteractor { - return if (featureFlags.pipelineEnabled) { - panelInteractorImpl - } else { - qsHost - } - } - - @Provides - @JvmStatic - fun provideCustomTileAddedRepository( - featureFlags: QSPipelineFlagsRepository, - qsHost: QSTileHost, - customTileAddedRepository: CustomTileAddedSharedPrefsRepository - ): CustomTileAddedRepository { - return if (featureFlags.pipelineEnabled) { - customTileAddedRepository - } else { - qsHost - } - } - } + @Binds + fun provideCustomTileAddedRepository( + impl: CustomTileAddedSharedPrefsRepository + ): CustomTileAddedRepository } diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java index b705a0389300..29bcad4e0e0c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java +++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java @@ -16,17 +16,7 @@ package com.android.systemui.qs.dagger; -import static com.android.systemui.qs.dagger.QSFlagsModule.RBC_AVAILABLE; - -import android.content.Context; -import android.os.Handler; - -import com.android.systemui.dagger.NightDisplayListenerModule; -import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.media.dagger.MediaModule; -import com.android.systemui.qs.AutoAddTracker; -import com.android.systemui.qs.QSHost; import com.android.systemui.qs.ReduceBrightColorsController; import com.android.systemui.qs.ReduceBrightColorsControllerImpl; import com.android.systemui.qs.external.QSExternalModule; @@ -36,25 +26,13 @@ import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.qs.tiles.di.QSTilesModule; import com.android.systemui.qs.ui.adapter.QSSceneAdapter; import com.android.systemui.qs.ui.adapter.QSSceneAdapterImpl; -import com.android.systemui.statusbar.phone.AutoTileManager; -import com.android.systemui.statusbar.phone.ManagedProfileController; -import com.android.systemui.statusbar.policy.CastController; -import com.android.systemui.statusbar.policy.DataSaverController; -import com.android.systemui.statusbar.policy.DeviceControlsController; -import com.android.systemui.statusbar.policy.HotspotController; -import com.android.systemui.statusbar.policy.SafetyController; -import com.android.systemui.statusbar.policy.WalletController; -import com.android.systemui.util.settings.SecureSettings; + +import java.util.Map; import dagger.Binds; import dagger.Module; -import dagger.Provides; import dagger.multibindings.Multibinds; -import java.util.Map; - -import javax.inject.Named; - /** * Module for QS dependencies */ @@ -78,45 +56,6 @@ public interface QSModule { @Multibinds Map<String, QSTileImpl<?>> tileMap(); - @Provides - @SysUISingleton - static AutoTileManager provideAutoTileManager( - Context context, - AutoAddTracker.Builder autoAddTrackerBuilder, - QSHost host, - @Background Handler handler, - SecureSettings secureSettings, - HotspotController hotspotController, - DataSaverController dataSaverController, - ManagedProfileController managedProfileController, - NightDisplayListenerModule.Builder nightDisplayListenerBuilder, - CastController castController, - ReduceBrightColorsController reduceBrightColorsController, - DeviceControlsController deviceControlsController, - WalletController walletController, - SafetyController safetyController, - @Named(RBC_AVAILABLE) boolean isReduceBrightColorsAvailable) { - AutoTileManager manager = new AutoTileManager( - context, - autoAddTrackerBuilder, - host, - handler, - secureSettings, - hotspotController, - dataSaverController, - managedProfileController, - nightDisplayListenerBuilder, - castController, - reduceBrightColorsController, - deviceControlsController, - walletController, - safetyController, - isReduceBrightColorsAvailable - ); - manager.init(); - return manager; - } - @Binds QSSceneAdapter bindsQsSceneInteractor(QSSceneAdapterImpl impl); diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt index ba45d172b082..6dc101a63f09 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt @@ -21,6 +21,7 @@ import android.util.Log import android.view.ContextThemeWrapper import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleCoroutineScope import androidx.lifecycle.LifecycleOwner import com.android.settingslib.Utils import com.android.systemui.animation.Expandable @@ -41,6 +42,7 @@ import javax.inject.Inject import javax.inject.Named import javax.inject.Provider import kotlin.math.max +import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -48,6 +50,8 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch private const val TAG = "FooterActionsViewModel" @@ -140,6 +144,30 @@ class FooterActionsViewModel( showPowerButton, ) } + + fun create(lifecycleCoroutineScope: LifecycleCoroutineScope): FooterActionsViewModel { + val globalActionsDialogLite = globalActionsDialogLiteProvider.get() + if (lifecycleCoroutineScope.isActive) { + lifecycleCoroutineScope.launch { + try { + awaitCancellation() + } finally { + globalActionsDialogLite.destroy() + } + } + } else { + globalActionsDialogLite.destroy() + } + + return FooterActionsViewModel( + context, + footerActionsInteractor, + falsingManager, + globalActionsDialogLite, + activityStarter, + showPowerButton, + ) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt index 2ee957e89f48..08a56bf29f66 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt @@ -39,6 +39,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.qs.panels.dagger.PaginatedBaseLayoutType import com.android.systemui.qs.panels.ui.compose.PaginatedGridLayout.Dimensions.FooterHeight import com.android.systemui.qs.panels.ui.compose.PaginatedGridLayout.Dimensions.InterPageSpacing @@ -77,7 +78,7 @@ constructor( Column { HorizontalPager( state = pagerState, - modifier = Modifier, + modifier = Modifier.sysuiResTag("qs_pager"), pageSpacing = if (pages.size > 1) InterPageSpacing else 0.dp, beyondViewportPageCount = 1, verticalAlignment = Alignment.Top, diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt index af3803b6ff34..a9027ff92996 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt @@ -25,6 +25,7 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.dimensionResource import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.qs.panels.ui.viewmodel.QuickQuickSettingsViewModel import com.android.systemui.res.R @@ -44,7 +45,10 @@ fun QuickQuickSettings( } val columns by viewModel.columns.collectAsStateWithLifecycle() - TileLazyGrid(modifier = modifier, columns = GridCells.Fixed(columns)) { + TileLazyGrid( + modifier = modifier.sysuiResTag("qqs_tile_layout"), + columns = GridCells.Fixed(columns) + ) { items( tiles.size, key = { index -> sizedTiles[index].tile.spec.spec }, diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt index 7e6ccd635a96..9c0701e974ec 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt @@ -22,7 +22,6 @@ import android.graphics.drawable.Animatable import android.service.quicksettings.Tile.STATE_ACTIVE import android.service.quicksettings.Tile.STATE_INACTIVE import android.text.TextUtils -import androidx.appcompat.content.res.AppCompatResources import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn @@ -593,15 +592,15 @@ enum class ClickAction { } @Composable -private fun getTileIcon(icon: Supplier<QSTile.Icon>): Icon { +private fun getTileIcon(icon: Supplier<QSTile.Icon?>): Icon { val context = LocalContext.current - return icon.get().let { + return icon.get()?.let { if (it is QSTileImpl.ResourceIcon) { Icon.Resource(it.resId, null) } else { Icon.Loaded(it.getDrawable(context), null) } - } + } ?: Icon.Resource(R.drawable.ic_error_outline, null) } @OptIn(ExperimentalAnimationGraphicsApi::class) @@ -618,7 +617,7 @@ private fun TileIcon( remember(icon, context) { when (icon) { is Icon.Loaded -> icon.drawable - is Icon.Resource -> AppCompatResources.getDrawable(context, icon.res) + is Icon.Resource -> context.getDrawable(icon.res) } } if (loadedDrawable !is Animatable) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt index 4ec59c969a59..c83e3b2a0e06 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt @@ -25,7 +25,7 @@ data class TileUiState( val label: String, val secondaryLabel: String, val state: Int, - val icon: Supplier<QSTile.Icon>, + val icon: Supplier<QSTile.Icon?>, ) fun QSTile.State.toUiState(): TileUiState { @@ -33,6 +33,6 @@ fun QSTile.State.toUiState(): TileUiState { label?.toString() ?: "", secondaryLabel?.toString() ?: "", state, - icon?.let { Supplier { icon } } ?: iconSupplier, + icon?.let { Supplier { icon } } ?: iconSupplier ?: Supplier { null }, ) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt index 02379e6efecc..4a96710f083c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt @@ -116,6 +116,8 @@ interface CurrentTilesInteractor : ProtoDumpable { */ fun setTiles(specs: List<TileSpec>) + fun createTileSync(spec: TileSpec): QSTile? + companion object { val POSITION_AT_END: Int = TileSpecRepository.POSITION_AT_END } @@ -190,9 +192,7 @@ constructor( } init { - if (featureFlags.pipelineEnabled) { - startTileCollection() - } + startTileCollection() } private fun startTileCollection() { @@ -342,15 +342,16 @@ constructor( lifecycleManager.flushMessagesAndUnbind() } + override fun createTileSync(spec: TileSpec): QSTile? { + return if (featureFlags.tilesEnabled) { + newQSTileFactory.get().createTile(spec.spec) + } else { + null + } ?: tileFactory.createTile(spec.spec) + } + private suspend fun createTile(spec: TileSpec): QSTile? { - val tile = - withContext(mainDispatcher) { - if (featureFlags.tilesEnabled) { - newQSTileFactory.get().createTile(spec.spec) - } else { - null - } ?: tileFactory.createTile(spec.spec) - } + val tile = withContext(mainDispatcher) { createTileSync(spec) } if (tile == null) { logger.logTileNotFoundInFactory(spec) return null diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt index c8fbeb50b039..0bcb6b7e7874 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt @@ -40,14 +40,12 @@ constructor( ) : CoreStartable { override fun start() { - if (featureFlags.pipelineEnabled) { - accessibilityTilesInteractor.init(currentTilesInteractor) - autoAddInteractor.init(currentTilesInteractor) - restoreReconciliationInteractor.start() + accessibilityTilesInteractor.init(currentTilesInteractor) + autoAddInteractor.init(currentTilesInteractor) + restoreReconciliationInteractor.start() - if (NewQsUI.isEnabled) { - gridConsistencyInteractor.start() - } + if (NewQsUI.isEnabled) { + gridConsistencyInteractor.start() } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt index 42bee3c8f877..5dc8d1bd4643 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt @@ -9,19 +9,10 @@ import javax.inject.Inject @SysUISingleton class QSPipelineFlagsRepository @Inject constructor() { - val pipelineEnabled: Boolean - get() = AconfigFlags.qsNewPipeline() - val tilesEnabled: Boolean get() = AconfigFlags.qsNewTiles() companion object Utils { - fun assertInLegacyMode() = - RefactorFlagUtils.assertInLegacyMode( - AconfigFlags.qsNewPipeline(), - AconfigFlags.FLAG_QS_NEW_PIPELINE - ) - fun assertNewTiles() = RefactorFlagUtils.assertInNewMode( AconfigFlags.qsNewTiles(), diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt index f6924f222e11..8aa601f3ecf0 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt @@ -26,13 +26,12 @@ class SceneWindowRootView( attrs, ) { - private lateinit var viewModel: SceneContainerViewModel - + private var motionEventHandler: SceneContainerViewModel.MotionEventHandler? = null // TODO(b/298525212): remove once Compose exposes window inset bounds. private val windowInsets: MutableStateFlow<WindowInsets?> = MutableStateFlow(null) fun init( - viewModel: SceneContainerViewModel, + viewModelFactory: SceneContainerViewModel.Factory, containerConfig: SceneContainerConfig, sharedNotificationContainer: SharedNotificationContainer, scenes: Set<Scene>, @@ -40,11 +39,13 @@ class SceneWindowRootView( sceneDataSourceDelegator: SceneDataSourceDelegator, alternateBouncerDependencies: AlternateBouncerDependencies, ) { - this.viewModel = viewModel setLayoutInsetsController(layoutInsetController) SceneWindowRootViewBinder.bind( view = this@SceneWindowRootView, - viewModel = viewModel, + viewModelFactory = viewModelFactory, + motionEventHandlerReceiver = { motionEventHandler -> + this.motionEventHandler = motionEventHandler + }, windowInsets = windowInsets, containerConfig = containerConfig, sharedNotificationContainer = sharedNotificationContainer, @@ -69,10 +70,10 @@ class SceneWindowRootView( } override fun dispatchTouchEvent(ev: MotionEvent): Boolean { - viewModel.onMotionEvent(ev) + motionEventHandler?.onMotionEvent(ev) return super.dispatchTouchEvent(ev).also { TouchLogger.logDispatchTouch(TAG, ev, it) - viewModel.onMotionEventComplete() + motionEventHandler?.onMotionEventComplete() } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt index 73a8e4c24578..ad68f17720c5 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt @@ -29,8 +29,6 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.core.view.isVisible import androidx.lifecycle.Lifecycle -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle import com.android.compose.animation.scene.SceneKey import com.android.compose.theme.PlatformTheme import com.android.internal.policy.ScreenDecorationsUtils @@ -39,7 +37,9 @@ import com.android.systemui.common.ui.compose.windowinsets.DisplayCutout import com.android.systemui.common.ui.compose.windowinsets.ScreenDecorProvider import com.android.systemui.keyguard.ui.composable.AlternateBouncer import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerDependencies +import com.android.systemui.lifecycle.WindowLifecycleState import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.lifecycle.viewModel import com.android.systemui.res.R import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.model.Scene @@ -51,6 +51,7 @@ import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.map @@ -63,7 +64,8 @@ object SceneWindowRootViewBinder { /** Binds between the view and view-model pertaining to a specific scene container. */ fun bind( view: ViewGroup, - viewModel: SceneContainerViewModel, + viewModelFactory: SceneContainerViewModel.Factory, + motionEventHandlerReceiver: (SceneContainerViewModel.MotionEventHandler?) -> Unit, windowInsets: StateFlow<WindowInsets?>, containerConfig: SceneContainerConfig, sharedNotificationContainer: SharedNotificationContainer, @@ -85,8 +87,11 @@ object SceneWindowRootViewBinder { } view.repeatWhenAttached { - lifecycleScope.launch { - repeatOnLifecycle(Lifecycle.State.CREATED) { + view.viewModel( + minWindowLifecycleState = WindowLifecycleState.ATTACHED, + factory = { viewModelFactory.create(motionEventHandlerReceiver) }, + ) { viewModel -> + try { view.setViewTreeOnBackPressedDispatcherOwner( object : OnBackPressedDispatcherOwner { override val onBackPressedDispatcher = @@ -140,10 +145,11 @@ object SceneWindowRootViewBinder { onVisibilityChangedInternal(isVisible) } } + awaitCancellation() + } finally { + // Here when destroyed. + view.removeAllViews() } - - // Here when destroyed. - view.removeAllViews() } } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt index a28222e9cea0..2d02f5a5b79d 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt @@ -23,25 +23,26 @@ import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.systemui.classifier.Classifier import com.android.systemui.classifier.domain.interactor.FalsingInteractor -import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.lifecycle.SysUiViewModel import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.model.Scenes -import javax.inject.Inject +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.map /** Models UI state for the scene container. */ -@SysUISingleton class SceneContainerViewModel -@Inject +@AssistedInject constructor( private val sceneInteractor: SceneInteractor, private val falsingInteractor: FalsingInteractor, private val powerInteractor: PowerInteractor, -) { + @Assisted private val motionEventHandlerReceiver: (MotionEventHandler?) -> Unit, +) : SysUiViewModel() { /** * Keys of all scenes in the container. * @@ -56,6 +57,29 @@ constructor( /** Whether the container is visible. */ val isVisible: StateFlow<Boolean> = sceneInteractor.isVisible + override suspend fun onActivated() { + try { + // Sends a MotionEventHandler to the owner of the view-model so they can report + // MotionEvents into the view-model. + motionEventHandlerReceiver( + object : MotionEventHandler { + override fun onMotionEvent(motionEvent: MotionEvent) { + this@SceneContainerViewModel.onMotionEvent(motionEvent) + } + + override fun onMotionEventComplete() { + this@SceneContainerViewModel.onMotionEventComplete() + } + } + ) + awaitCancellation() + } finally { + // Clears the previously-sent MotionEventHandler so the owner of the view-model releases + // their reference to it. + motionEventHandlerReceiver(null) + } + } + /** * Binds the given flow so the system remembers it. * @@ -136,21 +160,22 @@ constructor( } } - private fun replaceSceneFamilies( - destinationScenes: Map<UserAction, UserActionResult>, - ): Flow<Map<UserAction, UserActionResult>> { - return destinationScenes - .mapValues { (_, actionResult) -> - sceneInteractor.resolveSceneFamily(actionResult.toScene).map { scene -> - actionResult.copy(toScene = scene) - } - } - .combineValueFlows() + /** Defines interface for classes that can handle externally-reported [MotionEvent]s. */ + interface MotionEventHandler { + /** Notifies that a [MotionEvent] has occurred. */ + fun onMotionEvent(motionEvent: MotionEvent) + + /** + * Notifies that the previous [MotionEvent] reported by [onMotionEvent] has finished + * processing. + */ + fun onMotionEventComplete() } -} -private fun <K, V> Map<K, Flow<V>>.combineValueFlows(): Flow<Map<K, V>> = - combine( - asIterable().map { (k, fv) -> fv.map { k to it } }, - transform = Array<Pair<K, V>>::toMap, - ) + @AssistedFactory + interface Factory { + fun create( + motionEventHandlerReceiver: (MotionEventHandler?) -> Unit, + ): SceneContainerViewModel + } +} diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt index 21bbaa5a41f2..606fef0bff62 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt @@ -79,7 +79,7 @@ abstract class ShadeViewProviderModule { @SysUISingleton fun providesWindowRootView( layoutInflater: LayoutInflater, - viewModelProvider: Provider<SceneContainerViewModel>, + viewModelFactory: SceneContainerViewModel.Factory, containerConfigProvider: Provider<SceneContainerConfig>, scenesProvider: Provider<Set<@JvmSuppressWildcards Scene>>, layoutInsetController: NotificationInsetsController, @@ -91,7 +91,7 @@ abstract class ShadeViewProviderModule { val sceneWindowRootView = layoutInflater.inflate(R.layout.scene_window_root, null) as SceneWindowRootView sceneWindowRootView.init( - viewModel = viewModelProvider.get(), + viewModelFactory = viewModelFactory, containerConfig = containerConfigProvider.get(), sharedNotificationContainer = sceneWindowRootView.requireViewById(R.id.shared_notification_container), diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorController.kt index 2b7df7dc937d..67c53d46b4d0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorController.kt @@ -142,14 +142,15 @@ class NotificationTransitionAnimatorController( } override fun onIntentStarted(willAnimate: Boolean) { + val reason = "onIntentStarted(willAnimate=$willAnimate)" if (ActivityTransitionAnimator.DEBUG_TRANSITION_ANIMATION) { - Log.d(TAG, "onIntentStarted(willAnimate=$willAnimate)") + Log.d(TAG, reason) } notificationLaunchAnimationInteractor.setIsLaunchAnimationRunning(willAnimate) notificationEntry.isExpandAnimationRunning = willAnimate if (!willAnimate) { - removeHun(animate = true) + removeHun(animate = true, reason) onFinishAnimationCallback?.run() } } @@ -166,13 +167,18 @@ class NotificationTransitionAnimatorController( } } - private fun removeHun(animate: Boolean) { + private fun removeHun(animate: Boolean, reason: String) { val row = headsUpNotificationRow ?: return // TODO: b/297247841 - Call on the row we're removing, which may differ from notification. HeadsUpUtil.setNeedsHeadsUpDisappearAnimationAfterClick(notification, animate) - headsUpManager.removeNotification(row.entry.key, true /* releaseImmediately */, animate) + headsUpManager.removeNotification( + row.entry.key, + true /* releaseImmediately */, + animate, + reason + ) } override fun onTransitionAnimationCancelled(newKeyguardOccludedState: Boolean?) { @@ -184,7 +190,7 @@ class NotificationTransitionAnimatorController( // here? notificationLaunchAnimationInteractor.setIsLaunchAnimationRunning(false) notificationEntry.isExpandAnimationRunning = false - removeHun(animate = true) + removeHun(animate = true, "onLaunchAnimationCancelled()") onFinishAnimationCallback?.run() } @@ -206,7 +212,7 @@ class NotificationTransitionAnimatorController( notificationEntry.isExpandAnimationRunning = false notificationListContainer.setExpandingNotification(null) applyParams(null) - removeHun(animate = false) + removeHun(animate = false, "onLaunchAnimationEnd()") onFinishAnimationCallback?.run() } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt index e50d64bcb8f9..ec8566b82aea 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt @@ -496,7 +496,11 @@ constructor( if (posted?.shouldHeadsUpEver == false) { if (posted.isHeadsUpEntry) { // We don't want this to be interrupting anymore, let's remove it - mHeadsUpManager.removeNotification(posted.key, false /*removeImmediately*/) + mHeadsUpManager.removeNotification( + posted.key, + /* removeImmediately= */ false, + "onEntryUpdated" + ) } else if (posted.isBinding) { // Don't let the bind finish cancelHeadsUpBind(posted.entry) @@ -520,7 +524,11 @@ constructor( val removeImmediatelyForRemoteInput = (mRemoteInputManager.isSpinning(entryKey) && !NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY) - mHeadsUpManager.removeNotification(entry.key, removeImmediatelyForRemoteInput) + mHeadsUpManager.removeNotification( + entry.key, + removeImmediatelyForRemoteInput, + "onEntryRemoved, reason: $reason" + ) } } @@ -721,7 +729,9 @@ constructor( { mHeadsUpManager.removeNotification( entry.key, /* releaseImmediately */ - true + true, + "cancel lifetime extension - extended for reason: " + + "$reason, isSticky: true" ) }, removeAfterMillis @@ -730,7 +740,9 @@ constructor( mExecutor.execute { mHeadsUpManager.removeNotification( entry.key, /* releaseImmediately */ - false + false, + "lifetime extension - extended for reason: $reason" + + ", isSticky: false" ) } mNotifsExtendingLifetime[entry] = null @@ -902,7 +914,7 @@ private class HunMutatorImpl(private val headsUpManager: HeadsUpManager) : HunMu fun commitModifications() { deferred.forEach { (key, releaseImmediately) -> - headsUpManager.removeNotification(key, releaseImmediately) + headsUpManager.removeNotification(key, releaseImmediately, "commitModifications") } deferred.clear() } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index 41195aa0f72e..fa12bb99eac2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -638,8 +638,11 @@ public class NotificationStackScrollLayoutController implements Dumpable { if (row.isPinned() && !canChildBeDismissed(row) && row.getEntry().getSbn().getNotification().fullScreenIntent == null) { - mHeadsUpManager.removeNotification(row.getEntry().getSbn().getKey(), - true /* removeImmediately */); + mHeadsUpManager.removeNotification( + row.getEntry().getSbn().getKey(), + /* removeImmediately= */ true , + /* reason= */ "onChildSnappedBack" + ); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt index 99f7a75676e1..f63ee7b9520d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt @@ -511,7 +511,7 @@ constructor( occludedToAodTransitionViewModel.lockscreenAlpha, occludedToGoneTransitionViewModel.notificationAlpha(viewState), occludedToLockscreenTransitionViewModel.lockscreenAlpha, - primaryBouncerToLockscreenTransitionViewModel.lockscreenAlpha, + primaryBouncerToLockscreenTransitionViewModel.lockscreenAlpha(viewState), glanceableHubToLockscreenTransitionViewModel.keyguardAlpha, lockscreenToGlanceableHubTransitionViewModel.keyguardAlpha, ) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt index 107bf1edb74f..d4ef42c687e5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt @@ -613,8 +613,9 @@ constructor( super.onTransitionAnimationStart(isExpandingFullyAbove) if (Flags.communalHub()) { communalSceneInteractor.snapToScene( - CommunalScenes.Blank, - ActivityTransitionAnimator.TIMINGS.totalDuration + newScene = CommunalScenes.Blank, + loggingReason = "ActivityStarterInternalImpl", + delayMillis = ActivityTransitionAnimator.TIMINGS.totalDuration ) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java deleted file mode 100644 index a5388564d5fd..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java +++ /dev/null @@ -1,520 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file - * except in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the - * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -package com.android.systemui.statusbar.phone; - -import static com.android.systemui.qs.dagger.QSFlagsModule.RBC_AVAILABLE; - -import android.annotation.Nullable; -import android.content.ComponentName; -import android.content.Context; -import android.content.res.Resources; -import android.database.ContentObserver; -import android.hardware.display.ColorDisplayManager; -import android.hardware.display.NightDisplayListener; -import android.os.Handler; -import android.os.UserHandle; -import android.util.Log; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.systemui.dagger.NightDisplayListenerModule; -import com.android.systemui.dagger.qualifiers.Background; -import com.android.systemui.plugins.qs.QSTile; -import com.android.systemui.qs.AutoAddTracker; -import com.android.systemui.qs.QSHost; -import com.android.systemui.qs.ReduceBrightColorsController; -import com.android.systemui.qs.UserSettingObserver; -import com.android.systemui.qs.external.CustomTile; -import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository; -import com.android.systemui.res.R; -import com.android.systemui.statusbar.policy.CastController; -import com.android.systemui.statusbar.policy.CastDevice; -import com.android.systemui.statusbar.policy.DataSaverController; -import com.android.systemui.statusbar.policy.DataSaverController.Listener; -import com.android.systemui.statusbar.policy.DeviceControlsController; -import com.android.systemui.statusbar.policy.HotspotController; -import com.android.systemui.statusbar.policy.HotspotController.Callback; -import com.android.systemui.statusbar.policy.SafetyController; -import com.android.systemui.statusbar.policy.WalletController; -import com.android.systemui.util.UserAwareController; -import com.android.systemui.util.settings.SecureSettings; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Objects; - -import javax.inject.Named; - -/** - * Manages which tiles should be automatically added to QS. - */ -public class AutoTileManager implements UserAwareController { - private static final String TAG = "AutoTileManager"; - - public static final String HOTSPOT = "hotspot"; - public static final String SAVER = "saver"; - public static final String INVERSION = "inversion"; - public static final String WORK = "work"; - public static final String NIGHT = "night"; - public static final String CAST = "cast"; - public static final String DEVICE_CONTROLS = "controls"; - public static final String WALLET = "wallet"; - public static final String BRIGHTNESS = "reduce_brightness"; - static final String SETTING_SEPARATOR = ":"; - - private UserHandle mCurrentUser; - private boolean mInitialized; - private final String mSafetySpec; - - protected final Context mContext; - protected final QSHost mHost; - protected final Handler mHandler; - protected final SecureSettings mSecureSettings; - protected final AutoAddTracker mAutoTracker; - private final HotspotController mHotspotController; - private final DataSaverController mDataSaverController; - private final ManagedProfileController mManagedProfileController; - private final NightDisplayListenerModule.Builder mNightDisplayListenerBuilder; - private NightDisplayListener mNightDisplayListener; - private final CastController mCastController; - private final DeviceControlsController mDeviceControlsController; - private final WalletController mWalletController; - private final ReduceBrightColorsController mReduceBrightColorsController; - private final SafetyController mSafetyController; - private final boolean mIsReduceBrightColorsAvailable; - private final ArrayList<AutoAddSetting> mAutoAddSettingList = new ArrayList<>(); - - public AutoTileManager(Context context, AutoAddTracker.Builder autoAddTrackerBuilder, - QSHost host, - @Background Handler handler, - SecureSettings secureSettings, - HotspotController hotspotController, - DataSaverController dataSaverController, - ManagedProfileController managedProfileController, - NightDisplayListenerModule.Builder nightDisplayListenerBuilder, - CastController castController, - ReduceBrightColorsController reduceBrightColorsController, - DeviceControlsController deviceControlsController, - WalletController walletController, - SafetyController safetyController, - @Named(RBC_AVAILABLE) boolean isReduceBrightColorsAvailable) { - mContext = context; - mHost = host; - mSecureSettings = secureSettings; - mCurrentUser = mHost.getUserContext().getUser(); - mAutoTracker = autoAddTrackerBuilder.setUserId(mCurrentUser.getIdentifier()).build(); - mHandler = handler; - mHotspotController = hotspotController; - mDataSaverController = dataSaverController; - mManagedProfileController = managedProfileController; - mNightDisplayListenerBuilder = nightDisplayListenerBuilder; - mCastController = castController; - mReduceBrightColorsController = reduceBrightColorsController; - mIsReduceBrightColorsAvailable = isReduceBrightColorsAvailable; - mDeviceControlsController = deviceControlsController; - mWalletController = walletController; - mSafetyController = safetyController; - String safetySpecClass; - try { - safetySpecClass = - context.getResources().getString(R.string.safety_quick_settings_tile_class); - if (safetySpecClass.length() == 0) { - safetySpecClass = null; - } - } catch (Resources.NotFoundException | NullPointerException e) { - safetySpecClass = null; - } - mSafetySpec = safetySpecClass != null ? CustomTile.toSpec(new ComponentName(mContext - .getPackageManager().getPermissionControllerPackageName(), safetySpecClass)) : null; - } - - /** - * Init method must be called after construction to start listening - */ - public void init() { - QSPipelineFlagsRepository.Utils.assertInLegacyMode(); - if (mInitialized) { - Log.w(TAG, "Trying to re-initialize"); - return; - } - mAutoTracker.initialize(); - populateSettingsList(); - startControllersAndSettingsListeners(); - mInitialized = true; - } - - protected void startControllersAndSettingsListeners() { - if (!mAutoTracker.isAdded(HOTSPOT)) { - mHotspotController.addCallback(mHotspotCallback); - } - if (!mAutoTracker.isAdded(SAVER)) { - mDataSaverController.addCallback(mDataSaverListener); - } - mManagedProfileController.addCallback(mProfileCallback); - - mNightDisplayListener = mNightDisplayListenerBuilder - .setUser(mCurrentUser.getIdentifier()) - .build(); - if (!mAutoTracker.isAdded(NIGHT) - && ColorDisplayManager.isNightDisplayAvailable(mContext)) { - mNightDisplayListener.setCallback(mNightDisplayCallback); - } - if (!mAutoTracker.isAdded(CAST)) { - mCastController.addCallback(mCastCallback); - } - if (!mAutoTracker.isAdded(BRIGHTNESS) && mIsReduceBrightColorsAvailable) { - mReduceBrightColorsController.addCallback(mReduceBrightColorsCallback); - } - // We always want this callback, because if the feature stops being supported, - // we want to remove the tile from AutoAddTracker. That way it will be re-added when the - // feature is reenabled (similar to work tile). - mDeviceControlsController.setCallback(mDeviceControlsCallback); - if (!mAutoTracker.isAdded(WALLET)) { - initWalletController(); - } - if (mSafetySpec != null) { - if (!mAutoTracker.isAdded(mSafetySpec)) { - initSafetyTile(); - } - mSafetyController.addCallback(mSafetyCallback); - } - - int settingsN = mAutoAddSettingList.size(); - for (int i = 0; i < settingsN; i++) { - if (!mAutoTracker.isAdded(mAutoAddSettingList.get(i).mSpec)) { - mAutoAddSettingList.get(i).setListening(true); - } - } - } - - protected void stopListening() { - mHotspotController.removeCallback(mHotspotCallback); - mDataSaverController.removeCallback(mDataSaverListener); - mManagedProfileController.removeCallback(mProfileCallback); - if (ColorDisplayManager.isNightDisplayAvailable(mContext) - && mNightDisplayListener != null) { - mNightDisplayListener.setCallback(null); - } - if (mIsReduceBrightColorsAvailable) { - mReduceBrightColorsController.removeCallback(mReduceBrightColorsCallback); - } - mCastController.removeCallback(mCastCallback); - mDeviceControlsController.removeCallback(); - if (mSafetySpec != null) { - mSafetyController.removeCallback(mSafetyCallback); - } - int settingsN = mAutoAddSettingList.size(); - for (int i = 0; i < settingsN; i++) { - mAutoAddSettingList.get(i).setListening(false); - } - } - - public void destroy() { - stopListening(); - mAutoTracker.destroy(); - } - - /** - * Populates a list with the pairs setting:spec in the config resource. - * <p> - * This will only create {@link AutoAddSetting} objects for those tiles that have not been - * auto-added before, and set the corresponding {@link ContentObserver} to listening. - */ - private void populateSettingsList() { - String [] autoAddList; - try { - autoAddList = mContext.getResources().getStringArray( - R.array.config_quickSettingsAutoAdd); - } catch (Resources.NotFoundException e) { - Log.w(TAG, "Missing config resource"); - return; - } - // getStringArray returns @NotNull, so if we got here, autoAddList is not null - for (String tile : autoAddList) { - String[] split = tile.split(SETTING_SEPARATOR); - if (split.length == 2) { - String setting = split[0]; - String spec = split[1]; - // Populate all the settings. As they may not have been added in other users - AutoAddSetting s = new AutoAddSetting( - mSecureSettings, mHandler, setting, mCurrentUser.getIdentifier(), spec); - mAutoAddSettingList.add(s); - } else { - Log.w(TAG, "Malformed item in array: " + tile); - } - } - } - - /* - * This will be sent off the main thread if needed - */ - @Override - public void changeUser(UserHandle newUser) { - if (!mInitialized) { - throw new IllegalStateException("AutoTileManager not initialized"); - } - if (!Thread.currentThread().equals(mHandler.getLooper().getThread())) { - mHandler.post(() -> changeUser(newUser)); - return; - } - if (newUser.getIdentifier() == mCurrentUser.getIdentifier()) { - return; - } - stopListening(); - mCurrentUser = newUser; - int settingsN = mAutoAddSettingList.size(); - for (int i = 0; i < settingsN; i++) { - mAutoAddSettingList.get(i).setUserId(newUser.getIdentifier()); - } - mAutoTracker.changeUser(newUser); - startControllersAndSettingsListeners(); - } - - @Override - public int getCurrentUserId() { - return mCurrentUser.getIdentifier(); - } - - private final ManagedProfileController.Callback mProfileCallback = - new ManagedProfileController.Callback() { - @Override - public void onManagedProfileChanged() { - if (mManagedProfileController.hasActiveProfile()) { - if (mAutoTracker.isAdded(WORK)) return; - final int position = mAutoTracker.getRestoredTilePosition(WORK); - mHost.addTile(WORK, position); - mAutoTracker.setTileAdded(WORK); - } else { - if (!mAutoTracker.isAdded(WORK)) return; - mHost.removeTile(WORK); - mAutoTracker.setTileRemoved(WORK); - } - } - - @Override - public void onManagedProfileRemoved() { - } - }; - - private final DataSaverController.Listener mDataSaverListener = new Listener() { - @Override - public void onDataSaverChanged(boolean isDataSaving) { - if (mAutoTracker.isAdded(SAVER)) return; - if (isDataSaving) { - mHost.addTile(SAVER); - mAutoTracker.setTileAdded(SAVER); - mHandler.post(() -> mDataSaverController.removeCallback(mDataSaverListener)); - } - } - }; - - private final HotspotController.Callback mHotspotCallback = new Callback() { - @Override - public void onHotspotChanged(boolean enabled, int numDevices) { - if (mAutoTracker.isAdded(HOTSPOT)) return; - if (enabled) { - mHost.addTile(HOTSPOT); - mAutoTracker.setTileAdded(HOTSPOT); - mHandler.post(() -> mHotspotController.removeCallback(mHotspotCallback)); - } - } - }; - - private final DeviceControlsController.Callback mDeviceControlsCallback = - new DeviceControlsController.Callback() { - @Override - public void onControlsUpdate(@Nullable Integer position) { - if (mAutoTracker.isAdded(DEVICE_CONTROLS)) return; - if (position != null && !hasTile(DEVICE_CONTROLS)) { - mHost.addTile(DEVICE_CONTROLS, position); - mAutoTracker.setTileAdded(DEVICE_CONTROLS); - } - mHandler.post(() -> mDeviceControlsController.removeCallback()); - } - - @Override - public void removeControlsAutoTracker() { - mAutoTracker.setTileRemoved(DEVICE_CONTROLS); - } - }; - - private boolean hasTile(String tileSpec) { - if (tileSpec == null) return false; - Collection<QSTile> tiles = mHost.getTiles(); - for (QSTile tile : tiles) { - if (tileSpec.equals(tile.getTileSpec())) { - return true; - } - } - return false; - } - - private void initWalletController() { - if (mAutoTracker.isAdded(WALLET)) return; - Integer position = mWalletController.getWalletPosition(); - - if (position != null) { - mHost.addTile(WALLET, position); - mAutoTracker.setTileAdded(WALLET); - } - } - - private void initSafetyTile() { - if (mSafetySpec == null || mAutoTracker.isAdded(mSafetySpec)) { - return; - } - mHost.addTile(CustomTile.getComponentFromSpec(mSafetySpec), true); - mAutoTracker.setTileAdded(mSafetySpec); - } - - @VisibleForTesting - final NightDisplayListener.Callback mNightDisplayCallback = - new NightDisplayListener.Callback() { - @Override - public void onActivated(boolean activated) { - if (activated) { - addNightTile(); - } - } - - @Override - public void onAutoModeChanged(int autoMode) { - if (autoMode == ColorDisplayManager.AUTO_MODE_CUSTOM_TIME - || autoMode == ColorDisplayManager.AUTO_MODE_TWILIGHT) { - addNightTile(); - } - } - - private void addNightTile() { - if (mAutoTracker.isAdded(NIGHT)) return; - mHost.addTile(NIGHT); - mAutoTracker.setTileAdded(NIGHT); - mHandler.post(() -> mNightDisplayListener.setCallback(null)); - } - }; - - @VisibleForTesting - final ReduceBrightColorsController.Listener mReduceBrightColorsCallback = - new ReduceBrightColorsController.Listener() { - @Override - public void onActivated(boolean activated) { - if (activated) { - addReduceBrightColorsTile(); - } - } - - @Override - public void onFeatureEnabledChanged(boolean enabled) { - if (!enabled) { - mHost.removeTile(BRIGHTNESS); - mHandler.post(() -> mReduceBrightColorsController.removeCallback(this)); - } - } - - private void addReduceBrightColorsTile() { - if (mAutoTracker.isAdded(BRIGHTNESS)) return; - mHost.addTile(BRIGHTNESS); - mAutoTracker.setTileAdded(BRIGHTNESS); - mHandler.post(() -> mReduceBrightColorsController.removeCallback(this)); - } - }; - - @VisibleForTesting - final CastController.Callback mCastCallback = new CastController.Callback() { - @Override - public void onCastDevicesChanged() { - if (mAutoTracker.isAdded(CAST)) return; - - boolean isCasting = false; - for (CastDevice device : mCastController.getCastDevices()) { - if (device.isCasting()) { - isCasting = true; - break; - } - } - - if (isCasting) { - mHost.addTile(CAST); - mAutoTracker.setTileAdded(CAST); - mHandler.post(() -> mCastController.removeCallback(mCastCallback)); - } - } - }; - - @VisibleForTesting - final SafetyController.Listener mSafetyCallback = new SafetyController.Listener() { - @Override - public void onSafetyCenterEnableChanged(boolean isSafetyCenterEnabled) { - if (mSafetySpec == null) { - return; - } - - if (isSafetyCenterEnabled && !mAutoTracker.isAdded(mSafetySpec)) { - initSafetyTile(); - } else if (!isSafetyCenterEnabled && mAutoTracker.isAdded(mSafetySpec)) { - mHost.removeTile(mSafetySpec); - mAutoTracker.setTileRemoved(mSafetySpec); - } - } - }; - - @VisibleForTesting - protected UserSettingObserver getSecureSettingForKey(String key) { - for (UserSettingObserver s : mAutoAddSettingList) { - if (Objects.equals(key, s.getKey())) { - return s; - } - } - return null; - } - - /** - * Tracks tiles that should be auto added when a setting changes. - * <p> - * When the setting changes to a value different from 0, if the tile has not been auto added - * before, it will be added and the listener will be stopped. - */ - private class AutoAddSetting extends UserSettingObserver { - private final String mSpec; - - AutoAddSetting( - SecureSettings secureSettings, - Handler handler, - String setting, - int userId, - String tileSpec - ) { - super(secureSettings, handler, setting, userId); - mSpec = tileSpec; - } - - @Override - protected void handleValueChanged(int value, boolean observedChange) { - if (mAutoTracker.isAdded(mSpec)) { - // This should not be listening anymore - mHandler.post(() -> setListening(false)); - return; - } - if (value != 0) { - if (mSpec.startsWith(CustomTile.PREFIX)) { - mHost.addTile(CustomTile.getComponentFromSpec(mSpec), /* end */ true); - } else { - mHost.addTile(mSpec); - } - mAutoTracker.setTileAdded(mSpec); - mHandler.post(() -> setListening(false)); - } - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index c4fbc37b2dd5..94dd9bbefaa3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -160,6 +160,8 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.qs.QSFragmentLegacy; import com.android.systemui.qs.QSPanelController; +import com.android.systemui.qs.composefragment.QSFragmentCompose; +import com.android.systemui.qs.flags.QSComposeFragment; import com.android.systemui.res.R; import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor; import com.android.systemui.scene.shared.flag.SceneContainerFlag; @@ -1432,9 +1434,15 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } protected QS createDefaultQSFragment() { + Class<? extends QS> klass; + if (QSComposeFragment.isEnabled()) { + klass = QSFragmentCompose.class; + } else { + klass = QSFragmentLegacy.class; + } return mFragmentService .getFragmentHostManager(getNotificationShadeWindowView()) - .create(QSFragmentLegacy.class); + .create(klass); } private void setUpPresenter() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java index ac1015521502..ec92990441ce 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java @@ -193,7 +193,11 @@ public final class DozeServiceHost implements DozeHost { void fireNotificationPulse(NotificationEntry entry) { Runnable pulseSuppressedListener = () -> { mHeadsUpManager.removeNotification( - entry.getKey(), /* releaseImmediately= */ true, /* animate= */ false); + entry.getKey(), + /* releaseImmediately= */ true, + /* animate= */ false, + "fireNotificationPulse" + ); }; Assert.isMainThread(); for (Callback callback : mCallbacks) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java index 25d9cc76fe3d..544a8a5e5c67 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java @@ -60,11 +60,6 @@ import com.android.systemui.util.kotlin.JavaAdapter; import com.android.systemui.util.settings.GlobalSettings; import com.android.systemui.util.time.SystemClock; -import kotlinx.coroutines.flow.Flow; -import kotlinx.coroutines.flow.MutableStateFlow; -import kotlinx.coroutines.flow.StateFlow; -import kotlinx.coroutines.flow.StateFlowKt; - import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashSet; @@ -75,6 +70,11 @@ import java.util.Stack; import javax.inject.Inject; +import kotlinx.coroutines.flow.Flow; +import kotlinx.coroutines.flow.MutableStateFlow; +import kotlinx.coroutines.flow.StateFlow; +import kotlinx.coroutines.flow.StateFlowKt; + /** A implementation of HeadsUpManager for phone. */ @SysUISingleton public class HeadsUpManagerPhone extends BaseHeadsUpManager implements @@ -365,12 +365,14 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements @Override public boolean removeNotification(@NonNull String key, boolean releaseImmediately, - boolean animate) { + boolean animate, @NonNull String reason) { if (animate) { - return removeNotification(key, releaseImmediately); + return removeNotification(key, releaseImmediately, + "removeNotification(animate: true), reason: " + reason); } else { mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(false); - boolean removed = removeNotification(key, releaseImmediately); + final boolean removed = removeNotification(key, releaseImmediately, + "removeNotification(animate: false), reason: " + reason); mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(true); return removed; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java index f1787088ab98..dda02db3f2ef 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java @@ -16,8 +16,6 @@ package com.android.systemui.statusbar.phone; - - import android.annotation.Nullable; import android.content.Context; import android.content.res.Configuration; @@ -34,15 +32,14 @@ import android.view.accessibility.AccessibilityEvent; import android.widget.FrameLayout; import android.widget.LinearLayout; +import androidx.annotation.NonNull; + import com.android.internal.policy.SystemBarUtils; import com.android.systemui.Dependency; import com.android.systemui.Flags; import com.android.systemui.Gefingerpoken; -import com.android.systemui.plugins.DarkIconDispatcher; -import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver; import com.android.systemui.res.R; import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer; -import com.android.systemui.statusbar.policy.Clock; import com.android.systemui.statusbar.window.StatusBarWindowController; import com.android.systemui.user.ui.binder.StatusBarUserChipViewBinder; import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel; @@ -52,11 +49,8 @@ import java.util.Objects; public class PhoneStatusBarView extends FrameLayout { private static final String TAG = "PhoneStatusBarView"; - private final StatusBarContentInsetsProvider mContentInsetsProvider; private final StatusBarWindowController mStatusBarWindowController; - private DarkReceiver mBattery; - private Clock mClock; private int mRotationOrientation = -1; @Nullable private View mCutoutSpace; @@ -67,6 +61,10 @@ public class PhoneStatusBarView extends FrameLayout { private int mStatusBarHeight; @Nullable private Gefingerpoken mTouchEventHandler; + @Nullable + private HasCornerCutoutFetcher mHasCornerCutoutFetcher; + @Nullable + private InsetsFetcher mInsetsFetcher; private int mDensity; private float mFontScale; @@ -77,7 +75,6 @@ public class PhoneStatusBarView extends FrameLayout { public PhoneStatusBarView(Context context, AttributeSet attrs) { super(context, attrs); - mContentInsetsProvider = Dependency.get(StatusBarContentInsetsProvider.class); mStatusBarWindowController = Dependency.get(StatusBarWindowController.class); } @@ -85,6 +82,14 @@ public class PhoneStatusBarView extends FrameLayout { mTouchEventHandler = handler; } + void setHasCornerCutoutFetcher(@NonNull HasCornerCutoutFetcher cornerCutoutFetcher) { + mHasCornerCutoutFetcher = cornerCutoutFetcher; + } + + void setInsetsFetcher(@NonNull InsetsFetcher insetsFetcher) { + mInsetsFetcher = insetsFetcher; + } + void init(StatusBarUserChipViewModel viewModel) { StatusBarUserSwitcherContainer container = findViewById(R.id.user_switcher_container); StatusBarUserChipViewBinder.bind(container, viewModel); @@ -93,8 +98,6 @@ public class PhoneStatusBarView extends FrameLayout { @Override public void onFinishInflate() { super.onFinishInflate(); - mBattery = findViewById(R.id.battery); - mClock = findViewById(R.id.clock); mCutoutSpace = findViewById(R.id.cutout_space_view); updateResources(); @@ -103,9 +106,6 @@ public class PhoneStatusBarView extends FrameLayout { @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); - // Always have Battery meters in the status bar observe the dark/light modes. - Dependency.get(DarkIconDispatcher.class).addDarkReceiver(mBattery); - Dependency.get(DarkIconDispatcher.class).addDarkReceiver(mClock); if (updateDisplayParameters()) { updateLayoutForCutout(); updateWindowHeight(); @@ -115,8 +115,6 @@ public class PhoneStatusBarView extends FrameLayout { @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); - Dependency.get(DarkIconDispatcher.class).removeDarkReceiver(mBattery); - Dependency.get(DarkIconDispatcher.class).removeDarkReceiver(mClock); mDisplayCutout = null; } @@ -136,10 +134,6 @@ public class PhoneStatusBarView extends FrameLayout { updateWindowHeight(); } - void onDensityOrFontScaleChanged() { - mClock.onDensityOrFontScaleChanged(); - } - @Override public WindowInsets onApplyWindowInsets(WindowInsets insets) { if (updateDisplayParameters()) { @@ -288,7 +282,14 @@ public class PhoneStatusBarView extends FrameLayout { return; } - boolean hasCornerCutout = mContentInsetsProvider.currentRotationHasCornerCutout(); + boolean hasCornerCutout; + if (mHasCornerCutoutFetcher != null) { + hasCornerCutout = mHasCornerCutoutFetcher.fetchHasCornerCutout(); + } else { + Log.e(TAG, "mHasCornerCutoutFetcher unexpectedly null"); + hasCornerCutout = true; + } + if (mDisplayCutout == null || mDisplayCutout.isEmpty() || hasCornerCutout) { mCutoutSpace.setVisibility(View.GONE); return; @@ -306,8 +307,12 @@ public class PhoneStatusBarView extends FrameLayout { } private void updateSafeInsets() { - Insets insets = mContentInsetsProvider - .getStatusBarContentInsetsForCurrentRotation(); + if (mInsetsFetcher == null) { + Log.e(TAG, "mInsetsFetcher unexpectedly null"); + return; + } + + Insets insets = mInsetsFetcher.fetchInsets(); setPadding( insets.left, insets.top, @@ -316,6 +321,17 @@ public class PhoneStatusBarView extends FrameLayout { } private void updateWindowHeight() { + if (Flags.statusBarStopUpdatingWindowHeight()) { + return; + } mStatusBarWindowController.refreshStatusBarHeight(); } + + interface HasCornerCutoutFetcher { + boolean fetchHasCornerCutout(); + } + + interface InsetsFetcher { + Insets fetchInsets(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt index a818c05b1666..456265b27004 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt @@ -23,10 +23,13 @@ import android.view.MotionEvent import android.view.View import android.view.ViewGroup import android.view.ViewTreeObserver +import androidx.annotation.VisibleForTesting import com.android.systemui.Flags import com.android.systemui.Gefingerpoken +import com.android.systemui.battery.BatteryMeterView import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags.ENABLE_UNFOLD_STATUS_BAR_ANIMATIONS +import com.android.systemui.plugins.DarkIconDispatcher import com.android.systemui.res.R import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.ui.view.WindowRootView @@ -35,6 +38,7 @@ import com.android.systemui.shade.ShadeLogger import com.android.systemui.shade.ShadeViewController import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator +import com.android.systemui.statusbar.policy.Clock import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.window.StatusBarWindowStateController import com.android.systemui.unfold.SysUIUnfoldComponent @@ -68,19 +72,28 @@ private constructor( private val viewUtil: ViewUtil, private val configurationController: ConfigurationController, private val statusOverlayHoverListenerFactory: StatusOverlayHoverListenerFactory, + private val darkIconDispatcher: DarkIconDispatcher, + private val statusBarContentInsetsProvider: StatusBarContentInsetsProvider, ) : ViewController<PhoneStatusBarView>(view) { + private lateinit var battery: BatteryMeterView + private lateinit var clock: Clock private lateinit var statusContainer: View private val configurationListener = object : ConfigurationController.ConfigurationListener { override fun onDensityOrFontScaleChanged() { - mView.onDensityOrFontScaleChanged() + clock.onDensityOrFontScaleChanged() } } override fun onViewAttached() { statusContainer = mView.requireViewById(R.id.system_icons) + clock = mView.requireViewById(R.id.clock) + battery = mView.requireViewById(R.id.battery) + + addDarkReceivers() + statusContainer.setOnHoverListener( statusOverlayHoverListenerFactory.createDarkAwareListener(statusContainer) ) @@ -133,7 +146,9 @@ private constructor( } } - override fun onViewDetached() { + @VisibleForTesting + public override fun onViewDetached() { + removeDarkReceivers() statusContainer.setOnHoverListener(null) progressProvider?.setReadyToHandleTransition(false) moveFromCenterAnimationController?.onViewDetached() @@ -141,7 +156,14 @@ private constructor( } init { + // These should likely be done in `onInit`, not `init`. mView.setTouchEventHandler(PhoneStatusBarViewTouchHandler()) + mView.setHasCornerCutoutFetcher { + statusBarContentInsetsProvider.currentRotationHasCornerCutout() + } + mView.setInsetsFetcher { + statusBarContentInsetsProvider.getStatusBarContentInsetsForCurrentRotation() + } mView.init(userChipViewModel) } @@ -182,6 +204,16 @@ private constructor( } } + private fun addDarkReceivers() { + darkIconDispatcher.addDarkReceiver(battery) + darkIconDispatcher.addDarkReceiver(clock) + } + + private fun removeDarkReceivers() { + darkIconDispatcher.removeDarkReceiver(battery) + darkIconDispatcher.removeDarkReceiver(clock) + } + inner class PhoneStatusBarViewTouchHandler : Gefingerpoken { override fun onInterceptTouchEvent(event: MotionEvent): Boolean { return if (Flags.statusBarSwipeOverChip()) { @@ -285,6 +317,8 @@ private constructor( private val viewUtil: ViewUtil, private val configurationController: ConfigurationController, private val statusOverlayHoverListenerFactory: StatusOverlayHoverListenerFactory, + private val darkIconDispatcher: DarkIconDispatcher, + private val statusBarContentInsetsProvider: StatusBarContentInsetsProvider, ) { fun create(view: PhoneStatusBarView): PhoneStatusBarViewController { val statusBarMoveFromCenterAnimationController = @@ -309,6 +343,8 @@ private constructor( viewUtil, configurationController, statusOverlayHoverListenerFactory, + darkIconDispatcher, + statusBarContentInsetsProvider, ) } } 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 5486abba9987..de4d14d31da3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -1007,7 +1007,9 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mKeyguardMessageAreaController.setIsVisible(isShowingAlternateBouncer); mKeyguardMessageAreaController.setMessage(""); } - mKeyguardUpdateManager.setAlternateBouncerShowing(isShowingAlternateBouncer); + if (!SceneContainerFlag.isEnabled()) { + mKeyguardUpdateManager.setAlternateBouncerShowing(isShowingAlternateBouncer); + } if (updateScrim) { mCentralSurfaces.updateScrimController(); @@ -1449,10 +1451,13 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mNotificationShadeWindowController.setBouncerShowing(primaryBouncerShowing); mCentralSurfaces.setBouncerShowing(primaryBouncerShowing); } - if (primaryBouncerIsOrWillBeShowing != mLastPrimaryBouncerIsOrWillBeShowing || mFirstUpdate - || isPrimaryBouncerShowingChanged) { - mKeyguardUpdateManager.sendPrimaryBouncerChanged(primaryBouncerIsOrWillBeShowing, - primaryBouncerShowing); + if (!SceneContainerFlag.isEnabled()) { + if (primaryBouncerIsOrWillBeShowing != mLastPrimaryBouncerIsOrWillBeShowing + || mFirstUpdate + || isPrimaryBouncerShowingChanged) { + mKeyguardUpdateManager.sendPrimaryBouncerChanged(primaryBouncerIsOrWillBeShowing, + primaryBouncerShowing); + } } mFirstUpdate = false; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java index e92058bf034a..0a6e7f59e24e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java @@ -230,7 +230,8 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit Runnable action = () -> { mBubblesManagerOptional.ifPresent(bubblesManager -> bubblesManager.onUserChangedBubble(entry, !entry.isBubble())); - mHeadsUpManager.removeNotification(entry.getKey(), /* releaseImmediately= */ true); + mHeadsUpManager.removeNotification(entry.getKey(), /* releaseImmediately= */ true, + /* reason= */ "onNotificationBubbleIconClicked"); }; if (entry.isBubble()) { // entry is being un-bubbled, no need to unlock @@ -621,7 +622,8 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit // In most cases, when FLAG_AUTO_CANCEL is set, the notification will // become canceled shortly by NoMan, but we can't assume that. - mHeadsUpManager.removeNotification(key, true /* releaseImmediately */); + mHeadsUpManager.removeNotification(key, /* releaseImmediately= */ true, + "removeHunAfterClick"); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java index 37869587528e..f37393ac6729 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java @@ -40,13 +40,14 @@ import com.android.systemui.res.R; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun; -import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor; import com.android.systemui.statusbar.phone.ExpandHeadsUpOnInlineReply; import com.android.systemui.util.ListenerSet; import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.settings.GlobalSettings; import com.android.systemui.util.time.SystemClock; +import org.jetbrains.annotations.NotNull; + import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; @@ -191,12 +192,14 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { * enough and needs to be kept around. * @param key the key of the notification to remove * @param releaseImmediately force a remove regardless of earliest removal time + * @param reason reason for removing the notification * @return true if notification is removed, false otherwise */ @Override - public boolean removeNotification(@NonNull String key, boolean releaseImmediately) { + public boolean removeNotification(@NotNull String key, boolean releaseImmediately, + @NonNull String reason) { final boolean isWaiting = mAvalancheController.isWaiting(key); - mLogger.logRemoveNotification(key, releaseImmediately, isWaiting); + mLogger.logRemoveNotification(key, releaseImmediately, isWaiting, reason); if (mAvalancheController.isWaiting(key)) { removeEntry(key, "removeNotification (isWaiting)"); @@ -204,6 +207,7 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { } HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key); if (headsUpEntry == null) { + mLogger.logNullEntry(key, reason); return true; } if (releaseImmediately) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java index 775f34d54e3f..d281920ab1c4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java @@ -174,15 +174,24 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC IndentingPrintWriter ipw = asIndenting(pw); ipw.println("BatteryController state:"); ipw.increaseIndent(); - ipw.print("mHasReceivedBattery="); ipw.println(mHasReceivedBattery); - ipw.print("mLevel="); ipw.println(mLevel); - ipw.print("mPluggedIn="); ipw.println(mPluggedIn); - ipw.print("mCharging="); ipw.println(mCharging); - ipw.print("mCharged="); ipw.println(mCharged); - ipw.print("mIsBatteryDefender="); ipw.println(mIsBatteryDefender); - ipw.print("mIsIncompatibleCharging="); ipw.println(mIsIncompatibleCharging); - ipw.print("mPowerSave="); ipw.println(mPowerSave); - ipw.print("mStateUnknown="); ipw.println(mStateUnknown); + ipw.print("mHasReceivedBattery="); + ipw.println(mHasReceivedBattery); + ipw.print("mLevel="); + ipw.println(mLevel); + ipw.print("mPluggedIn="); + ipw.println(mPluggedIn); + ipw.print("mCharging="); + ipw.println(mCharging); + ipw.print("mCharged="); + ipw.println(mCharged); + ipw.print("mIsBatteryDefender="); + ipw.println(mIsBatteryDefender); + ipw.print("mIsIncompatibleCharging="); + ipw.println(mIsIncompatibleCharging); + ipw.print("mPowerSave="); + ipw.println(mPowerSave); + ipw.print("mStateUnknown="); + ipw.println(mStateUnknown); ipw.println("Callbacks:------------------"); // Since the above lines are already indented, we need to indent twice for the callbacks. ipw.increaseIndent(); @@ -272,7 +281,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC } int chargingStatus = intent.getIntExtra(EXTRA_CHARGING_STATUS, CHARGING_POLICY_DEFAULT); - boolean isBatteryDefender = chargingStatus == CHARGING_POLICY_ADAPTIVE_LONGLIFE; + boolean isBatteryDefender = isBatteryDefenderMode(chargingStatus); if (isBatteryDefender != mIsBatteryDefender) { mIsBatteryDefender = isBatteryDefender; fireIsBatteryDefenderChanged(); @@ -359,11 +368,24 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC return mPluggedChargingSource == BatteryManager.BATTERY_PLUGGED_WIRELESS; } - public boolean isBatteryDefender() { + /** + * This method is used for tests only. Returns whether the device is in battery defender + * mode. + */ + @VisibleForTesting + protected boolean isBatteryDefender() { return mIsBatteryDefender; } /** + * Checks whether the device is in battery defender mode based on the current charging + * status. This method can be overridden to have a different definition for its subclasses. + */ + protected boolean isBatteryDefenderMode(int chargingStatus) { + return chargingStatus == CHARGING_POLICY_ADAPTIVE_LONGLIFE; + } + + /** * Returns whether the charging adapter is incompatible. */ public boolean isIncompatibleCharging() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt index 1224275aaf93..e29e069d18e2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt @@ -21,13 +21,12 @@ import android.content.Context import android.content.SharedPreferences import android.provider.Settings import android.util.Log -import com.android.systemui.res.R import com.android.systemui.controls.ControlsServiceInfo import com.android.systemui.controls.dagger.ControlsComponent import com.android.systemui.controls.management.ControlsListingController import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.res.R import com.android.systemui.settings.UserContextProvider -import com.android.systemui.statusbar.phone.AutoTileManager import com.android.systemui.statusbar.policy.DeviceControlsController.Callback import com.android.systemui.util.settings.SecureSettings import javax.inject.Inject @@ -35,14 +34,16 @@ import javax.inject.Inject /** * Watches for Device Controls QS Tile activation, which can happen in two ways: * <ol> - * <li>Migration from Power Menu - For existing Android 11 users, create a tile in a high - * priority position. - * <li>Device controls service becomes available - For non-migrated users, create a tile and - * place at the end of active tiles, and initiate seeding where possible. + * <li>Migration from Power Menu - For existing Android 11 users, create a tile in a high priority + * position. + * <li>Device controls service becomes available - For non-migrated users, create a tile and place + * at the end of active tiles, and initiate seeding where possible. * </ol> */ @SysUISingleton -public class DeviceControlsControllerImpl @Inject constructor( +public class DeviceControlsControllerImpl +@Inject +constructor( private val context: Context, private val controlsComponent: ControlsComponent, private val userContextProvider: UserContextProvider, @@ -52,13 +53,14 @@ public class DeviceControlsControllerImpl @Inject constructor( private var callback: Callback? = null internal var position: Int? = null - private val listingCallback = object : ControlsListingController.ControlsListingCallback { - override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) { - if (!serviceInfos.isEmpty()) { - seedFavorites(serviceInfos) + private val listingCallback = + object : ControlsListingController.ControlsListingCallback { + override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) { + if (!serviceInfos.isEmpty()) { + seedFavorites(serviceInfos) + } } } - } companion object { private const val TAG = "DeviceControlsControllerImpl" @@ -80,7 +82,7 @@ public class DeviceControlsControllerImpl @Inject constructor( } /** - * This migration logic assumes that something like [AutoTileManager] is tracking state + * This migration logic assumes that something like [AutoAddTracker] is tracking state * externally, and won't call this method after receiving a response via * [Callback#onControlsUpdate], once per user. Otherwise the calculated position may be * incorrect. @@ -118,16 +120,19 @@ public class DeviceControlsControllerImpl @Inject constructor( } /** - * See if any available control service providers match one of the preferred components. If - * they do, and there are no current favorites for that component, query the preferred - * component for a limited number of suggested controls. + * See if any available control service providers match one of the preferred components. If they + * do, and there are no current favorites for that component, query the preferred component for + * a limited number of suggested controls. */ private fun seedFavorites(serviceInfos: List<ControlsServiceInfo>) { - val preferredControlsPackages = context.getResources().getStringArray( - R.array.config_controlsPreferredPackages) - - val prefs = userContextProvider.userContext.getSharedPreferences( - PREFS_CONTROLS_FILE, Context.MODE_PRIVATE) + val preferredControlsPackages = + context.getResources().getStringArray(R.array.config_controlsPreferredPackages) + + val prefs = + userContextProvider.userContext.getSharedPreferences( + PREFS_CONTROLS_FILE, + Context.MODE_PRIVATE + ) val seededPackages = prefs.getStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, emptySet()) ?: emptySet() @@ -157,21 +162,22 @@ public class DeviceControlsControllerImpl @Inject constructor( if (componentsToSeed.isEmpty()) return controlsController.seedFavoritesForComponents( - componentsToSeed, - { response -> - Log.d(TAG, "Controls seeded: $response") - if (response.accepted) { - addPackageToSeededSet(prefs, response.packageName) - if (position == null) { - position = QS_DEFAULT_POSITION - } - fireControlsUpdate() - - controlsComponent.getControlsListingController().ifPresent { - it.removeCallback(listingCallback) - } + componentsToSeed, + { response -> + Log.d(TAG, "Controls seeded: $response") + if (response.accepted) { + addPackageToSeededSet(prefs, response.packageName) + if (position == null) { + position = QS_DEFAULT_POSITION } - }) + fireControlsUpdate() + + controlsComponent.getControlsListingController().ifPresent { + it.removeCallback(listingCallback) + } + } + } + ) } private fun addPackageToSeededSet(prefs: SharedPreferences, pkg: String) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt index fcf77d5526d4..04fe6b3e9eb7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt @@ -96,9 +96,10 @@ interface HeadsUpManager : Dumpable { * * @param key the key of the notification to remove * @param releaseImmediately force a remove regardless of earliest removal time + * @param reason reason for removing the notification * @return true if notification is removed, false otherwise */ - fun removeNotification(key: String, releaseImmediately: Boolean): Boolean + fun removeNotification(key: String, releaseImmediately: Boolean, reason: String): Boolean /** * Try to remove the notification. May not succeed if the notification has not been shown long @@ -107,9 +108,15 @@ interface HeadsUpManager : Dumpable { * @param key the key of the notification to remove * @param releaseImmediately force a remove regardless of earliest removal time * @param animate if true, animate the removal + * @param reason reason for removing the notification * @return true if notification is removed, false otherwise */ - fun removeNotification(key: String, releaseImmediately: Boolean, animate: Boolean): Boolean + fun removeNotification( + key: String, + releaseImmediately: Boolean, + animate: Boolean, + reason: String + ): Boolean /** Clears all managed notifications. */ fun releaseAllImmediately() @@ -246,11 +253,16 @@ class HeadsUpManagerEmptyImpl @Inject constructor() : HeadsUpManager { override fun removeListener(listener: OnHeadsUpChangedListener) {} - override fun removeNotification(key: String, releaseImmediately: Boolean) = false - - override fun removeNotification(key: String, releaseImmediately: Boolean, animate: Boolean) = + override fun removeNotification(key: String, releaseImmediately: Boolean, reason: String) = false + override fun removeNotification( + key: String, + releaseImmediately: Boolean, + animate: Boolean, + reason: String + ) = false + override fun setAnimationStateHandler(handler: AnimationStateHandler) {} override fun setExpanded(entry: NotificationEntry, expanded: Boolean) {} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt index 80c595fb638b..c6fc54748cbd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt @@ -16,244 +16,283 @@ package com.android.systemui.statusbar.policy -import com.android.systemui.log.dagger.NotificationHeadsUpLog import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.LogLevel.INFO import com.android.systemui.log.core.LogLevel.VERBOSE +import com.android.systemui.log.dagger.NotificationHeadsUpLog import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.logKey import javax.inject.Inject /** Logger for [HeadsUpManager]. */ -class HeadsUpManagerLogger @Inject constructor( - @NotificationHeadsUpLog private val buffer: LogBuffer -) { +class HeadsUpManagerLogger +@Inject +constructor(@NotificationHeadsUpLog private val buffer: LogBuffer) { fun logPackageSnoozed(snoozeKey: String) { - buffer.log(TAG, INFO, { - str1 = snoozeKey - }, { - "package snoozed $str1" - }) + buffer.log(TAG, INFO, { str1 = snoozeKey }, { "package snoozed $str1" }) } fun logPackageUnsnoozed(snoozeKey: String) { - buffer.log(TAG, INFO, { - str1 = snoozeKey - }, { - "package unsnoozed $str1" - }) + buffer.log(TAG, INFO, { str1 = snoozeKey }, { "package unsnoozed $str1" }) } fun logIsSnoozedReturned(snoozeKey: String) { - buffer.log(TAG, INFO, { - str1 = snoozeKey - }, { - "package snoozed when queried $str1" - }) + buffer.log(TAG, INFO, { str1 = snoozeKey }, { "package snoozed when queried $str1" }) } fun logReleaseAllImmediately() { - buffer.log(TAG, INFO, { }, { - "release all immediately" - }) + buffer.log(TAG, INFO, {}, { "release all immediately" }) } fun logShowNotificationRequest(entry: NotificationEntry) { - buffer.log(TAG, INFO, { - str1 = entry.logKey - }, { - "request: show notification $str1" - }) + buffer.log(TAG, INFO, { str1 = entry.logKey }, { "request: show notification $str1" }) } - fun logAvalancheUpdate(caller: String, isEnabled: Boolean, notifEntryKey: String, - outcome: String) { - buffer.log(TAG, INFO, { - str1 = caller - str2 = notifEntryKey - str3 = outcome - bool1 = isEnabled - }, { - "$str1\n\t=> AC[isEnabled:$bool1] update: $str2\n\t=> $str3" - }) + fun logAvalancheUpdate( + caller: String, + isEnabled: Boolean, + notifEntryKey: String, + outcome: String + ) { + buffer.log( + TAG, + INFO, + { + str1 = caller + str2 = notifEntryKey + str3 = outcome + bool1 = isEnabled + }, + { "$str1\n\t=> AC[isEnabled:$bool1] update: $str2\n\t=> $str3" } + ) } - fun logAvalancheDelete(caller: String, isEnabled: Boolean, notifEntryKey: String, - outcome: String) { - buffer.log(TAG, INFO, { - str1 = caller - str2 = notifEntryKey - str3 = outcome - bool1 = isEnabled - }, { - "$str1\n\t=> AC[isEnabled:$bool1] delete: $str2\n\t=> $str3" - }) + fun logAvalancheDelete( + caller: String, + isEnabled: Boolean, + notifEntryKey: String, + outcome: String + ) { + buffer.log( + TAG, + INFO, + { + str1 = caller + str2 = notifEntryKey + str3 = outcome + bool1 = isEnabled + }, + { "$str1\n\t=> AC[isEnabled:$bool1] delete: $str2\n\t=> $str3" } + ) } fun logShowNotification(entry: NotificationEntry) { - buffer.log(TAG, INFO, { - str1 = entry.logKey - }, { - "show notification $str1" - }) + buffer.log(TAG, INFO, { str1 = entry.logKey }, { "show notification $str1" }) } fun logAutoRemoveScheduled(entry: NotificationEntry, delayMillis: Long, reason: String) { - buffer.log(TAG, INFO, { - str1 = entry.logKey - long1 = delayMillis - str2 = reason - }, { - "schedule auto remove of $str1 in $long1 ms reason: $str2" - }) + buffer.log( + TAG, + INFO, + { + str1 = entry.logKey + long1 = delayMillis + str2 = reason + }, + { "schedule auto remove of $str1 in $long1 ms reason: $str2" } + ) } fun logAutoRemoveRequest(entry: NotificationEntry, reason: String) { - buffer.log(TAG, INFO, { - str1 = entry.logKey - str2 = reason - }, { - "request: reschedule auto remove of $str1 reason: $str2" - }) + buffer.log( + TAG, + INFO, + { + str1 = entry.logKey + str2 = reason + }, + { "request: reschedule auto remove of $str1 reason: $str2" } + ) } fun logAutoRemoveRescheduled(entry: NotificationEntry, delayMillis: Long, reason: String) { - buffer.log(TAG, INFO, { - str1 = entry.logKey - long1 = delayMillis - str2 = reason - }, { - "reschedule auto remove of $str1 in $long1 ms reason: $str2" - }) + buffer.log( + TAG, + INFO, + { + str1 = entry.logKey + long1 = delayMillis + str2 = reason + }, + { "reschedule auto remove of $str1 in $long1 ms reason: $str2" } + ) } fun logAutoRemoveCancelRequest(entry: NotificationEntry, reason: String?) { - buffer.log(TAG, INFO, { - str1 = entry.logKey - str2 = reason ?: "unknown" - }, { - "request: cancel auto remove of $str1 reason: $str2" - }) + buffer.log( + TAG, + INFO, + { + str1 = entry.logKey + str2 = reason ?: "unknown" + }, + { "request: cancel auto remove of $str1 reason: $str2" } + ) } fun logAutoRemoveCanceled(entry: NotificationEntry, reason: String?) { - buffer.log(TAG, INFO, { - str1 = entry.logKey - str2 = reason ?: "unknown" - }, { - "cancel auto remove of $str1 reason: $str2" - }) + buffer.log( + TAG, + INFO, + { + str1 = entry.logKey + str2 = reason ?: "unknown" + }, + { "cancel auto remove of $str1 reason: $str2" } + ) } fun logRemoveEntryRequest(key: String, reason: String, isWaiting: Boolean) { - buffer.log(TAG, INFO, { - str1 = logKey(key) - str2 = reason - bool1 = isWaiting - }, { - "request: $str2 => remove entry $str1 isWaiting: $isWaiting" - }) + buffer.log( + TAG, + INFO, + { + str1 = logKey(key) + str2 = reason + bool1 = isWaiting + }, + { "request: $str2 => remove entry $str1 isWaiting: $isWaiting" } + ) } fun logRemoveEntry(key: String, reason: String, isWaiting: Boolean) { - buffer.log(TAG, INFO, { - str1 = logKey(key) - str2 = reason - bool1 = isWaiting - }, { - "$str2 => remove entry $str1 isWaiting: $isWaiting" - }) + buffer.log( + TAG, + INFO, + { + str1 = logKey(key) + str2 = reason + bool1 = isWaiting + }, + { "$str2 => remove entry $str1 isWaiting: $isWaiting" } + ) } fun logUnpinEntryRequest(key: String) { - buffer.log(TAG, INFO, { - str1 = logKey(key) - }, { - "request: unpin entry $str1" - }) + buffer.log(TAG, INFO, { str1 = logKey(key) }, { "request: unpin entry $str1" }) } fun logUnpinEntry(key: String) { - buffer.log(TAG, INFO, { - str1 = logKey(key) - }, { - "unpin entry $str1" - }) + buffer.log(TAG, INFO, { str1 = logKey(key) }, { "unpin entry $str1" }) + } + + fun logRemoveNotification( + key: String, + releaseImmediately: Boolean, + isWaiting: Boolean, + reason: String + ) { + buffer.log( + TAG, + INFO, + { + str1 = logKey(key) + bool1 = releaseImmediately + bool2 = isWaiting + str2 = reason + }, + { + "remove notification $str1 releaseImmediately: $bool1 isWaiting: $bool2 " + + "reason: $str2" + } + ) } - fun logRemoveNotification(key: String, releaseImmediately: Boolean, isWaiting: Boolean) { - buffer.log(TAG, INFO, { - str1 = logKey(key) - bool1 = releaseImmediately - bool2 = isWaiting - }, { - "remove notification $str1 releaseImmediately: $bool1 isWaiting: $bool2" - }) + fun logNullEntry(key: String, reason: String) { + buffer.log( + TAG, + INFO, + { + str1 = logKey(key) + str2 = reason + }, + { "remove notification $str1 when headsUpEntry is null, reason: $str2" } + ) } fun logNotificationActuallyRemoved(entry: NotificationEntry) { - buffer.log(TAG, INFO, { - str1 = entry.logKey - }, { - "notification removed $str1 " - }) + buffer.log(TAG, INFO, { str1 = entry.logKey }, { "notification removed $str1 " }) } fun logUpdateNotificationRequest(key: String, alert: Boolean, hasEntry: Boolean) { - buffer.log(TAG, INFO, { - str1 = logKey(key) - bool1 = alert - bool2 = hasEntry - }, { - "request: update notification $str1 alert: $bool1 hasEntry: $bool2" - }) + buffer.log( + TAG, + INFO, + { + str1 = logKey(key) + bool1 = alert + bool2 = hasEntry + }, + { "request: update notification $str1 alert: $bool1 hasEntry: $bool2" } + ) } fun logUpdateNotification(key: String, alert: Boolean, hasEntry: Boolean) { - buffer.log(TAG, INFO, { - str1 = logKey(key) - bool1 = alert - bool2 = hasEntry - }, { - "update notification $str1 alert: $bool1 hasEntry: $bool2" - }) + buffer.log( + TAG, + INFO, + { + str1 = logKey(key) + bool1 = alert + bool2 = hasEntry + }, + { "update notification $str1 alert: $bool1 hasEntry: $bool2" } + ) } fun logUpdateEntry(entry: NotificationEntry, updatePostTime: Boolean, reason: String?) { - buffer.log(TAG, INFO, { - str1 = entry.logKey - bool1 = updatePostTime - str2 = reason ?: "unknown" - }, { - "update entry $str1 updatePostTime: $bool1 reason: $str2" - }) + buffer.log( + TAG, + INFO, + { + str1 = entry.logKey + bool1 = updatePostTime + str2 = reason ?: "unknown" + }, + { "update entry $str1 updatePostTime: $bool1 reason: $str2" } + ) } fun logSnoozeLengthChange(packageSnoozeLengthMs: Int) { - buffer.log(TAG, INFO, { - int1 = packageSnoozeLengthMs - }, { - "snooze length changed: ${int1}ms" - }) + buffer.log( + TAG, + INFO, + { int1 = packageSnoozeLengthMs }, + { "snooze length changed: ${int1}ms" } + ) } fun logSetEntryPinned(entry: NotificationEntry, isPinned: Boolean, reason: String) { - buffer.log(TAG, VERBOSE, { - str1 = entry.logKey - bool1 = isPinned - str2 = reason - }, { - "$str2 => set entry pinned $str1 pinned: $bool1" - }) + buffer.log( + TAG, + VERBOSE, + { + str1 = entry.logKey + bool1 = isPinned + str2 = reason + }, + { "$str2 => set entry pinned $str1 pinned: $bool1" } + ) } fun logUpdatePinnedMode(hasPinnedNotification: Boolean) { - buffer.log(TAG, INFO, { - bool1 = hasPinnedNotification - }, { - "has pinned notification changed to $bool1" - }) + buffer.log( + TAG, + INFO, + { bool1 = hasPinnedNotification }, + { "has pinned notification changed to $bool1" } + ) } } -private const val TAG = "HeadsUpManager"
\ No newline at end of file +private const val TAG = "HeadsUpManager" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt index 8aa989ff390f..4f7749b9bd57 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt @@ -57,6 +57,7 @@ constructor( private val activityStarter: ActivityStarter, // Using a provider to avoid a circular dependency. private val viewModel: Provider<ModesDialogViewModel>, + private val dialogEventLogger: ModesDialogEventLogger, @Main private val mainCoroutineContext: CoroutineContext, ) : SystemUIDialog.Delegate { // NOTE: This should only be accessed/written from the main thread. @@ -102,7 +103,9 @@ constructor( ) } - private fun openSettings(dialog: SystemUIDialog) { + @VisibleForTesting + fun openSettings(dialog: SystemUIDialog) { + dialogEventLogger.logDialogSettings() val animationController = dialogTransitionAnimator.createActivityTransitionController(dialog) if (animationController == null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLogger.kt new file mode 100644 index 000000000000..33ed419afef2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLogger.kt @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.policy.ui.dialog + +import com.android.internal.logging.UiEventLogger +import com.android.settingslib.notification.modes.ZenMode +import com.android.systemui.qs.QSModesEvent +import javax.inject.Inject + +class ModesDialogEventLogger +@Inject +constructor( + private val uiEventLogger: UiEventLogger, +) { + + fun logModeOn(mode: ZenMode) { + val id = + if (mode.isManualDnd) QSModesEvent.QS_MODES_DND_ON else QSModesEvent.QS_MODES_MODE_ON + uiEventLogger.log(id, /* uid= */ 0, mode.rule.packageName) + } + + fun logModeOff(mode: ZenMode) { + val id = + if (mode.isManualDnd) QSModesEvent.QS_MODES_DND_OFF else QSModesEvent.QS_MODES_MODE_OFF + uiEventLogger.log(id, /* uid= */ 0, mode.rule.packageName) + } + + fun logModeSettings(mode: ZenMode) { + val id = + if (mode.isManualDnd) QSModesEvent.QS_MODES_DND_SETTINGS + else QSModesEvent.QS_MODES_MODE_SETTINGS + uiEventLogger.log(id, /* uid= */ 0, mode.rule.packageName) + } + + fun logOpenDurationDialog(mode: ZenMode) { + // should only occur for manual Do Not Disturb. + if (!mode.isManualDnd) { + return + } + uiEventLogger.log(QSModesEvent.QS_MODES_DURATION_DIALOG) + } + + fun logDialogSettings() { + uiEventLogger.log(QSModesEvent.QS_MODES_SETTINGS) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt index 44b692fcb786..5772099706b5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt @@ -30,6 +30,7 @@ import com.android.systemui.res.R import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogDelegate +import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogEventLogger import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow @@ -49,6 +50,7 @@ constructor( zenModeInteractor: ZenModeInteractor, @Background val bgDispatcher: CoroutineDispatcher, private val dialogDelegate: ModesDialogDelegate, + private val dialogEventLogger: ModesDialogEventLogger, ) { private val zenDialogMetricsLogger = QSZenModeDialogMetricsLogger(context) @@ -94,14 +96,17 @@ constructor( if (!mode.rule.isEnabled) { openSettings(mode) } else if (mode.isActive) { + dialogEventLogger.logModeOff(mode) zenModeInteractor.deactivateMode(mode) } else { if (mode.rule.isManualInvocationAllowed) { if (zenModeInteractor.shouldAskForZenDuration(mode)) { + dialogEventLogger.logOpenDurationDialog(mode) // NOTE: The dialog handles turning on the mode itself. val dialog = makeZenModeDialog() dialog.show() } else { + dialogEventLogger.logModeOn(mode) zenModeInteractor.activateMode(mode) } } @@ -114,6 +119,7 @@ constructor( .flowOn(bgDispatcher) private fun openSettings(mode: ZenMode) { + dialogEventLogger.logModeSettings(mode) val intent: Intent = Intent(ACTION_AUTOMATIC_ZEN_RULE_SETTINGS) .putExtra(EXTRA_AUTOMATIC_ZEN_RULE_ID, mode.id) diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java index 066bfc5c588d..1522cc490b43 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java @@ -165,6 +165,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa private boolean mShowSafetyWarning; private long mLastToggledRingerOn; private boolean mDeviceInteractive = true; + boolean mInAudioSharing = false; private VolumePolicy mVolumePolicy; @GuardedBy("this") @@ -295,6 +296,9 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa mJavaAdapter.alwaysCollectFlow( mAudioSharingInteractor.getVolume(), this::handleAudioSharingStreamVolumeChanges); + mJavaAdapter.alwaysCollectFlow( + mAudioSharingInteractor.isInAudioSharing(), + inSharing -> mInAudioSharing = inSharing); } } @@ -510,11 +514,18 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa // Since their values overlap with DEVICE_OUT_EARPIECE and DEVICE_OUT_SPEAKER. // Anyway, we can check BLE devices by using just DEVICE_OUT_BLE_HEADSET. final boolean routedToBluetooth = - (mAudio.getDevicesForStream(AudioManager.STREAM_MUSIC) & - (AudioManager.DEVICE_OUT_BLUETOOTH_A2DP | - AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES | - AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER | - AudioManager.DEVICE_OUT_BLE_HEADSET)) != 0; + // TODO(b/359737651): Need audio support to return broadcast mask. + // For now, mAudio.getDevicesForStream(AudioManager.STREAM_MUSIC) will return + // AudioManager.DEVICE_NONE, so we also need to check if the device is in audio + // sharing here. + mInAudioSharing + || (mAudio.getDevicesForStream(AudioManager.STREAM_MUSIC) + & (AudioManager.DEVICE_OUT_BLUETOOTH_A2DP + | AudioManager + .DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES + | AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER + | AudioManager.DEVICE_OUT_BLE_HEADSET)) + != 0; changed |= updateStreamRoutedToBluetoothW(stream, routedToBluetooth); } else if (stream == AudioManager.STREAM_VOICE_CALL) { final boolean routedToBluetooth = @@ -813,6 +824,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa ss.dynamic = true; ss.levelMin = mAudioSharingInteractor.getVolumeMin(); ss.levelMax = mAudioSharingInteractor.getVolumeMax(); + ss.routedToBluetooth = true; if (ss.level != volume) { ss.level = volume; } diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index e56f6b32c085..2468449fb859 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -1892,8 +1892,8 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, .equals(ss.remoteLabel)) { addRow( stream, - R.drawable.ic_volume_media, - R.drawable.ic_volume_media_mute, + R.drawable.ic_volume_media_bt, + R.drawable.ic_volume_media_bt_mute, true, false, true); diff --git a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt index 154737c71b9e..4f77cd04f5fa 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt @@ -26,7 +26,6 @@ import com.android.settingslib.media.MediaDevice import com.android.settingslib.media.MediaDevice.MediaDeviceType import com.android.settingslib.media.PhoneMediaDevice import com.android.settingslib.volume.data.repository.AudioRepository -import com.android.settingslib.volume.data.repository.AudioSharingRepository import com.android.settingslib.volume.domain.interactor.AudioModeInteractor import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background @@ -37,7 +36,6 @@ import javax.inject.Inject import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.flatMapLatest @@ -60,7 +58,6 @@ constructor( private val bluetoothAdapter: BluetoothAdapter?, private val deviceIconInteractor: DeviceIconInteractor, private val mediaOutputInteractor: MediaOutputInteractor, - audioSharingRepository: AudioSharingRepository, ) { val currentAudioDevice: StateFlow<AudioOutputDevice> = @@ -80,9 +77,6 @@ constructor( .flowOn(backgroundCoroutineContext) .stateIn(scope, SharingStarted.Eagerly, AudioOutputDevice.Unknown) - /** Whether the device is in audio sharing */ - val isInAudioSharing: Flow<Boolean> = audioSharingRepository.inAudioSharing - private fun AudioDeviceInfo.toAudioOutputDevice(): AudioOutputDevice { if ( BluetoothAdapter.checkBluetoothAddress(address) && diff --git a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt index 2170c36ec019..9aed8ab8f2e2 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt @@ -36,11 +36,15 @@ import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import kotlinx.coroutines.withContext interface AudioSharingInteractor { + /** Audio sharing state on the device. */ + val isInAudioSharing: Flow<Boolean> + /** Audio sharing secondary headset volume changes. */ val volume: Flow<Int?> @@ -76,6 +80,7 @@ constructor( private val audioVolumeInteractor: AudioVolumeInteractor, private val audioSharingRepository: AudioSharingRepository ) : AudioSharingInteractor { + override val isInAudioSharing: Flow<Boolean> = audioSharingRepository.inAudioSharing override val volume: Flow<Int?> = combine(audioSharingRepository.secondaryGroupId, audioSharingRepository.volumeMap) { @@ -125,13 +130,13 @@ constructor( } private companion object { - const val TAG = "AudioSharingInteractor" const val DEFAULT_VOLUME = 20 } } @SysUISingleton class AudioSharingInteractorEmptyImpl @Inject constructor() : AudioSharingInteractor { + override val isInAudioSharing: Flow<Boolean> = flowOf(false) override val volume: Flow<Int?> = emptyFlow() override val volumeMin: Int = EMPTY_VOLUME override val volumeMax: Int = EMPTY_VOLUME diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractor.kt index ed25129ff082..a270d5ffa9de 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractor.kt @@ -18,6 +18,7 @@ package com.android.systemui.volume.panel.component.mediaoutput.domain.interacto import com.android.settingslib.volume.domain.interactor.AudioModeInteractor import com.android.systemui.volume.domain.interactor.AudioOutputInteractor +import com.android.systemui.volume.domain.interactor.AudioSharingInteractor import com.android.systemui.volume.domain.model.AudioOutputDevice import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaOutputComponentModel import com.android.systemui.volume.panel.component.mediaoutput.shared.model.SessionWithPlaybackState @@ -49,11 +50,12 @@ constructor( private val mediaDeviceSessionInteractor: MediaDeviceSessionInteractor, audioOutputInteractor: AudioOutputInteractor, audioModeInteractor: AudioModeInteractor, - interactor: MediaOutputInteractor, + mediaOutputInteractor: MediaOutputInteractor, + audioSharingInteractor: AudioSharingInteractor, ) { private val sessionWithPlaybackState: StateFlow<Result<SessionWithPlaybackState?>> = - interactor.defaultActiveMediaSession + mediaOutputInteractor.defaultActiveMediaSession .filterData() .flatMapLatest { session -> if (session == null) { @@ -77,7 +79,7 @@ constructor( val mediaOutputModel: StateFlow<Result<MediaOutputComponentModel>> = audioModeInteractor.isOngoingCall .flatMapLatest { isOngoingCall -> - audioOutputInteractor.isInAudioSharing.flatMapLatest { isInAudioSharing -> + audioSharingInteractor.isInAudioSharing.flatMapLatest { isInAudioSharing -> if (isOngoingCall) { currentAudioDevice.map { MediaOutputComponentModel.Calling(it, isInAudioSharing) diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 075d8ae0adac..7aa415b64316 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -102,6 +102,7 @@ import android.os.PowerManager; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; +import android.platform.test.flag.junit.FlagsParameterization; import android.service.dreams.IDreamManager; import android.service.trust.TrustAgentService; import android.telephony.ServiceState; @@ -111,9 +112,9 @@ import android.telephony.TelephonyManager; import android.testing.TestableLooper; import android.text.TextUtils; -import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; +import com.android.compose.animation.scene.ObservableTransitionState; import com.android.dx.mockito.inline.extended.ExtendedMockito; import com.android.internal.foldables.FoldGracePeriodProvider; import com.android.internal.jank.InteractionJankMonitor; @@ -129,6 +130,7 @@ import com.android.settingslib.fuelgauge.BatteryStatus; import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.AuthController; import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider; +import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfig; import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfigImpl; @@ -138,9 +140,13 @@ import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStat import com.android.systemui.deviceentry.shared.model.FaceDetectionStatus; import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.SceneContainerFlagParameterizationKt; import com.android.systemui.kosmos.KosmosJavaAdapter; import com.android.systemui.log.SessionTracker; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.scene.domain.interactor.SceneInteractor; +import com.android.systemui.scene.shared.flag.SceneContainerFlag; +import com.android.systemui.scene.shared.model.Scenes; import com.android.systemui.settings.UserTracker; import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.shared.system.TaskStackChangeListeners; @@ -149,6 +155,7 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.policy.DevicePostureController; import com.android.systemui.telephony.TelephonyListenerManager; import com.android.systemui.user.domain.interactor.SelectedUserInteractor; +import com.android.systemui.util.kotlin.JavaAdapter; import com.android.systemui.util.settings.GlobalSettings; import org.junit.After; @@ -175,8 +182,11 @@ import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import platform.test.runner.parameterized.ParameterizedAndroidJunit4; +import platform.test.runner.parameterized.Parameters; + @SmallTest -@RunWith(AndroidJUnit4.class) +@RunWith(ParameterizedAndroidJunit4.class) @TestableLooper.RunWithLooper public class KeyguardUpdateMonitorTest extends SysuiTestCase { private static final String PKG_ALLOWING_FP_LISTEN_ON_OCCLUDING_ACTIVITY = @@ -277,6 +287,12 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { private SelectedUserInteractor mSelectedUserInteractor; @Mock private DeviceEntryFaceAuthInteractor mFaceAuthInteractor; + @Mock + private AlternateBouncerInteractor mAlternateBouncerInteractor; + @Mock + private JavaAdapter mJavaAdapter; + @Mock + private SceneInteractor mSceneInteractor; @Captor private ArgumentCaptor<FaceAuthenticationListener> mFaceAuthenticationListener; @@ -301,6 +317,16 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mFingerprintAuthenticatorsRegisteredCallback; private final InstanceId mKeyguardInstanceId = InstanceId.fakeInstanceId(999); + @Parameters(name = "{0}") + public static List<FlagsParameterization> getParams() { + return SceneContainerFlagParameterizationKt.parameterizeSceneContainerFlag(); + } + + public KeyguardUpdateMonitorTest(FlagsParameterization flags) { + super(); + mSetFlagsRule.setFlagsParameterization(flags); + } + @Before public void setup() throws RemoteException { mKosmos = new KosmosJavaAdapter(this); @@ -993,7 +1019,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { verifyFingerprintAuthenticateNeverCalled(); // WHEN alternate bouncer is shown mKeyguardUpdateMonitor.setKeyguardShowing(true, true); - mKeyguardUpdateMonitor.setAlternateBouncerShowing(true); + mKeyguardUpdateMonitor.setAlternateBouncerVisibility(true); // THEN make sure FP listening begins verifyFingerprintAuthenticateCall(); @@ -1489,7 +1515,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void testShouldNotListenForUdfps_whenInLockDown() { // GIVEN a "we should listen for udfps" state - setKeyguardBouncerVisibility(false /* isVisible */); + mKeyguardUpdateMonitor.setPrimaryBouncerVisibility(false /* isVisible */); mStatusBarStateListener.onStateChanged(StatusBarState.KEYGUARD); when(mStrongAuthTracker.hasUserAuthenticatedSinceBoot()).thenReturn(true); @@ -2124,7 +2150,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { verifyFingerprintAuthenticateNeverCalled(); mKeyguardUpdateMonitor.setKeyguardShowing(true, true); - mKeyguardUpdateMonitor.setAlternateBouncerShowing(true); + mKeyguardUpdateMonitor.setAlternateBouncerVisibility(true); verifyFingerprintAuthenticateCall(); } @@ -2323,12 +2349,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } private void bouncerFullyVisible() { - setKeyguardBouncerVisibility(true); - } - - private void setKeyguardBouncerVisibility(boolean isVisible) { - mKeyguardUpdateMonitor.sendPrimaryBouncerChanged(isVisible, isVisible); - mTestableLooper.processAllMessages(); + mKeyguardUpdateMonitor.setPrimaryBouncerVisibility(true); } private void setBroadcastReceiverPendingResult(BroadcastReceiver receiver) { @@ -2434,7 +2455,12 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mPackageManager, mFingerprintManager, mBiometricManager, mFaceWakeUpTriggersConfig, mDevicePostureController, Optional.of(mInteractiveToAuthProvider), - mTaskStackChangeListeners, mSelectedUserInteractor, mActivityTaskManager); + mTaskStackChangeListeners, mSelectedUserInteractor, mActivityTaskManager, + () -> mAlternateBouncerInteractor, + () -> mJavaAdapter, + () -> mSceneInteractor); + setAlternateBouncerVisibility(false); + setPrimaryBouncerVisibility(false); setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker); start(); } @@ -2458,5 +2484,25 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { protected int getBiometricLockoutDelay() { return 0; } + + private void setPrimaryBouncerVisibility(boolean isVisible) { + if (SceneContainerFlag.isEnabled()) { + ObservableTransitionState transitionState = new ObservableTransitionState.Idle( + isVisible ? Scenes.Bouncer : Scenes.Lockscreen); + when(mSceneInteractor.getTransitionState()).thenReturn( + MutableStateFlow(transitionState)); + onTransitionStateChanged(transitionState); + } else { + sendPrimaryBouncerChanged(isVisible, isVisible); + mTestableLooper.processAllMessages(); + } + } + + private void setAlternateBouncerVisibility(boolean isVisible) { + if (SceneContainerFlag.isEnabled()) { + when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(isVisible); + } + onAlternateBouncerVisibilityChange(isVisible); + } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java index ff47fd1106bb..c74d340ee325 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java @@ -32,10 +32,6 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.hardware.display.DisplayManager; import android.os.RemoteException; -import android.platform.test.annotations.RequiresFlagsDisabled; -import android.platform.test.annotations.RequiresFlagsEnabled; -import android.platform.test.flag.junit.CheckFlagsRule; -import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.provider.Settings; import android.testing.TestableLooper; import android.view.Display; @@ -49,7 +45,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.app.viewcapture.ViewCaptureAwareWindowManager; -import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import com.android.systemui.model.SysUiState; import com.android.systemui.recents.OverviewProxyService; @@ -58,7 +53,6 @@ import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.util.settings.SecureSettings; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -73,9 +67,6 @@ import org.mockito.MockitoAnnotations; @TestableLooper.RunWithLooper(setAsMainLooper = true) public class IMagnificationConnectionTest extends SysuiTestCase { - @Rule - public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); - private static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY; @Mock private AccessibilityManager mAccessibilityManager; @@ -198,22 +189,7 @@ public class IMagnificationConnectionTest extends SysuiTestCase { } @Test - @RequiresFlagsDisabled(Flags.FLAG_DELAY_SHOW_MAGNIFICATION_BUTTON) - public void showMagnificationButton_flagOff_directlyShowButton() throws RemoteException { - // magnification settings panel should not be showing - assertFalse(mMagnification.isMagnificationSettingsPanelShowing(TEST_DISPLAY)); - - mIMagnificationConnection.showMagnificationButton(TEST_DISPLAY, - Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); - processAllPendingMessages(); - - verify(mModeSwitchesController).showButton(TEST_DISPLAY, - Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); - } - - @Test - @RequiresFlagsEnabled(Flags.FLAG_DELAY_SHOW_MAGNIFICATION_BUTTON) - public void showMagnificationButton_flagOn_delayedShowButton() throws RemoteException { + public void showMagnificationButton_delayedShowButton() throws RemoteException { // magnification settings panel should not be showing assertFalse(mMagnification.isMagnificationSettingsPanelShowing(TEST_DISPLAY)); @@ -243,12 +219,9 @@ public class IMagnificationConnectionTest extends SysuiTestCase { // showMagnificationButton request to Magnification. processAllPendingMessages(); - // If the flag is on, the isMagnificationSettingsShowing will be checked after timeout, so + // The isMagnificationSettingsShowing will be checked after timeout, so // process all message after a timeout here to verify the showButton will not be called. - int timeout = Flags.delayShowMagnificationButton() - ? DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS + 100 - : 0; - processAllPendingMessages(timeout); + processAllPendingMessages(DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS + 100); verify(mModeSwitchesController, never()).showButton(TEST_DISPLAY, Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); } @@ -262,7 +235,6 @@ public class IMagnificationConnectionTest extends SysuiTestCase { } @Test - @RequiresFlagsEnabled(Flags.FLAG_DELAY_SHOW_MAGNIFICATION_BUTTON) public void removeMagnificationButton_delayingShowButton_doNotShowButtonAfterTimeout() throws RemoteException { // magnification settings panel should not be showing diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java index 9507077a89ae..7c0c5c209363 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java @@ -22,6 +22,9 @@ import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW; import static android.view.WindowInsets.Type.systemBars; +import static com.android.internal.accessibility.common.MagnificationConstants.SCALE_MAX_VALUE; +import static com.android.internal.accessibility.common.MagnificationConstants.SCALE_MIN_VALUE; + import static com.google.common.truth.Truth.assertThat; import static junit.framework.Assert.assertEquals; @@ -429,7 +432,7 @@ public class WindowMagnificationSettingsTest extends SysuiTestCase { mSettingView = mWindowMagnificationSettings.getSettingView(); mZoomSeekbar = mSettingView.findViewById(R.id.magnifier_zoom_slider); assertThat(mZoomSeekbar.getProgress()).isEqualTo(10); - assertThat(mZoomSeekbar.getMax()).isEqualTo(70); + assertThat(mZoomSeekbar.getMax()).isEqualTo(getSeekBarMax()); } @Test @@ -473,29 +476,26 @@ public class WindowMagnificationSettingsTest extends SysuiTestCase { @Test public void seekbarProgress_maxMagnificationBefore_seekbarProgressIsMax() { - mWindowMagnificationSettings.setMagnificationScale(8f); + mWindowMagnificationSettings.setMagnificationScale(SCALE_MAX_VALUE); setupMagnificationCapabilityAndMode( /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW, /* mode= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW); mWindowMagnificationSettings.showSettingPanel(); - // 8.0f is max magnification {@link MagnificationScaleProvider#MAX_SCALE}. - // Max zoom seek bar is 70. - assertThat(mZoomSeekbar.getProgress()).isEqualTo(70); + assertThat(mZoomSeekbar.getProgress()).isEqualTo(getSeekBarMax()); } @Test public void seekbarProgress_aboveMaxMagnificationBefore_seekbarProgressIsMax() { - mWindowMagnificationSettings.setMagnificationScale(9f); + mWindowMagnificationSettings.setMagnificationScale(SCALE_MAX_VALUE + 1f); setupMagnificationCapabilityAndMode( /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW, /* mode= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW); mWindowMagnificationSettings.showSettingPanel(); - // Max zoom seek bar is 70. - assertThat(mZoomSeekbar.getProgress()).isEqualTo(70); + assertThat(mZoomSeekbar.getProgress()).isEqualTo(getSeekBarMax()); } @Test @@ -589,4 +589,11 @@ public class WindowMagnificationSettingsTest extends SysuiTestCase { anyInt(), eq(UserHandle.USER_CURRENT))).thenReturn(mode); } + + private int getSeekBarMax() { + // Calculates the maximum index (or positions) the seekbar can have. + // This is achieved by multiplying the range of possible scales with the magnitude of + // change per each movement on the seekbar. + return (int) ((SCALE_MAX_VALUE - SCALE_MIN_VALUE) * mZoomSeekbar.getChangeMagnitude()); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt index a4936e63df8f..8e215f994e4d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt @@ -33,9 +33,10 @@ import com.android.systemui.authentication.data.repository.fakeAuthenticationRep import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.bouncer.ui.BouncerDialogFactory import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout -import com.android.systemui.bouncer.ui.viewmodel.bouncerViewModel +import com.android.systemui.bouncer.ui.viewmodel.bouncerSceneContentViewModelFactory import com.android.systemui.flags.Flags import com.android.systemui.flags.fakeFeatureFlagsClassic +import com.android.systemui.lifecycle.rememberViewModel import com.android.systemui.motion.createSysUiComposeMotionTestRule import com.android.systemui.scene.domain.startable.sceneContainerStartable import com.android.systemui.testKosmos @@ -81,7 +82,8 @@ class BouncerContentTest : SysuiTestCase() { private fun BouncerContentUnderTest() { PlatformTheme { BouncerContent( - viewModel = kosmos.bouncerViewModel, + viewModel = + rememberViewModel { kosmos.bouncerSceneContentViewModelFactory.create() }, layout = BouncerSceneLayout.BESIDE_USER_SWITCHER, modifier = Modifier.fillMaxSize().testTag("BouncerContent"), dialogFactory = bouncerDialogFactory diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt index 2948c0274525..4b61a0d02f1e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt @@ -24,14 +24,14 @@ import androidx.compose.ui.unit.dp import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.LargeTest import com.android.systemui.SysuiTestCase -import com.android.systemui.bouncer.domain.interactor.bouncerInteractor -import com.android.systemui.bouncer.ui.viewmodel.PatternBouncerViewModel -import com.android.systemui.kosmos.testScope +import com.android.systemui.bouncer.ui.viewmodel.patternBouncerViewModelFactory +import com.android.systemui.lifecycle.activateIn import com.android.systemui.motion.createSysUiComposeMotionTestRule import com.android.systemui.testKosmos import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.takeWhile +import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -51,15 +51,15 @@ class PatternBouncerTest : SysuiTestCase() { @get:Rule val motionTestRule = createSysUiComposeMotionTestRule(kosmos) - private val bouncerInteractor by lazy { kosmos.bouncerInteractor } - private val viewModel by lazy { - PatternBouncerViewModel( - applicationContext = context, - viewModelScope = kosmos.testScope.backgroundScope, - interactor = bouncerInteractor, + private val viewModel = + kosmos.patternBouncerViewModelFactory.create( isInputEnabled = MutableStateFlow(true).asStateFlow(), onIntentionalUserInput = {}, ) + + @Before + fun setUp() { + viewModel.activateIn(motionTestRule.toolkit.testScope) } @Composable diff --git a/packages/SystemUI/tests/src/com/android/systemui/inputdevice/data/repository/TutorialSchedulerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/inputdevice/data/repository/TutorialSchedulerRepositoryTest.kt new file mode 100644 index 000000000000..7583399c784a --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/inputdevice/data/repository/TutorialSchedulerRepositoryTest.kt @@ -0,0 +1,87 @@ +/* + * 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.inputdevice.data.repository + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType.KEYBOARD +import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType.TOUCHPAD +import com.android.systemui.inputdevice.tutorial.data.repository.TutorialSchedulerRepository +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope +import com.google.common.truth.Truth.assertThat +import java.time.Instant +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.runTest +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class TutorialSchedulerRepositoryTest : SysuiTestCase() { + + private lateinit var underTest: TutorialSchedulerRepository + private val kosmos = Kosmos() + private val testScope = kosmos.testScope + + @Before + fun setup() { + underTest = + TutorialSchedulerRepository( + context, + testScope.backgroundScope, + "TutorialSchedulerRepositoryTest" + ) + } + + @After + fun clear() { + testScope.launch { underTest.clearDataStore() } + } + + @Test + fun initialState() = + testScope.runTest { + assertThat(underTest.wasEverConnected(KEYBOARD)).isFalse() + assertThat(underTest.wasEverConnected(TOUCHPAD)).isFalse() + assertThat(underTest.isLaunched(KEYBOARD)).isFalse() + assertThat(underTest.isLaunched(TOUCHPAD)).isFalse() + } + + @Test + fun connectKeyboard() = + testScope.runTest { + val now = Instant.now().toEpochMilli() + underTest.updateConnectTime(KEYBOARD, now) + + assertThat(underTest.wasEverConnected(KEYBOARD)).isTrue() + assertThat(underTest.connectTime(KEYBOARD)).isEqualTo(now) + assertThat(underTest.wasEverConnected(TOUCHPAD)).isFalse() + } + + @Test + fun launchKeyboard() = + testScope.runTest { + underTest.updateLaunch(KEYBOARD) + + assertThat(underTest.isLaunched(KEYBOARD)).isTrue() + assertThat(underTest.isLaunched(TOUCHPAD)).isFalse() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java deleted file mode 100644 index 1eeaef773689..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java +++ /dev/null @@ -1,311 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file - * except in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the - * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -package com.android.systemui.qs; - -import static com.android.systemui.statusbar.phone.AutoTileManager.SAVER; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.anyInt; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; - -import android.content.BroadcastReceiver; -import android.content.Intent; -import android.content.IntentFilter; -import android.os.UserHandle; -import android.provider.Settings.Secure; -import android.testing.TestableLooper.RunWithLooper; - -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.filters.SmallTest; - -import com.android.systemui.SysuiTestCase; -import com.android.systemui.broadcast.BroadcastDispatcher; -import com.android.systemui.dump.DumpManager; -import com.android.systemui.util.settings.FakeSettings; -import com.android.systemui.util.settings.SecureSettings; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.InOrder; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; - -import java.util.List; -import java.util.concurrent.Executor; - -@RunWith(AndroidJUnit4.class) -@RunWithLooper -@SmallTest -public class AutoAddTrackerTest extends SysuiTestCase { - - private static final int END_POSITION = -1; - private static final int USER = 0; - - @Mock - private BroadcastDispatcher mBroadcastDispatcher; - @Mock - private QSHost mQSHost; - @Mock - private DumpManager mDumpManager; - @Captor - private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverArgumentCaptor; - @Captor - private ArgumentCaptor<IntentFilter> mIntentFilterArgumentCaptor; - - private Executor mBackgroundExecutor = Runnable::run; // Direct executor - private AutoAddTracker mAutoTracker; - private SecureSettings mSecureSettings; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - - mSecureSettings = new FakeSettings(); - - mSecureSettings.putStringForUser(Secure.QS_AUTO_ADDED_TILES, null, USER); - - mAutoTracker = createAutoAddTracker(USER); - mAutoTracker.initialize(); - } - - @Test - public void testChangeFromBackup() { - assertFalse(mAutoTracker.isAdded(SAVER)); - - mSecureSettings.putStringForUser(Secure.QS_AUTO_ADDED_TILES, SAVER, USER); - - assertTrue(mAutoTracker.isAdded(SAVER)); - - mAutoTracker.destroy(); - } - - @Test - public void testSetAdded() { - assertFalse(mAutoTracker.isAdded(SAVER)); - mAutoTracker.setTileAdded(SAVER); - - assertTrue(mAutoTracker.isAdded(SAVER)); - - mAutoTracker.destroy(); - } - - @Test - public void testPersist() { - assertFalse(mAutoTracker.isAdded(SAVER)); - mAutoTracker.setTileAdded(SAVER); - - mAutoTracker.destroy(); - mAutoTracker = createAutoAddTracker(USER); - mAutoTracker.initialize(); - - assertTrue(mAutoTracker.isAdded(SAVER)); - - mAutoTracker.destroy(); - } - - @Test - public void testIndependentUsers() { - mAutoTracker.setTileAdded(SAVER); - - mAutoTracker = createAutoAddTracker(USER + 1); - mAutoTracker.initialize(); - assertFalse(mAutoTracker.isAdded(SAVER)); - } - - @Test - public void testChangeUser() { - mAutoTracker.setTileAdded(SAVER); - - mAutoTracker = createAutoAddTracker(USER + 1); - mAutoTracker.changeUser(UserHandle.of(USER)); - assertTrue(mAutoTracker.isAdded(SAVER)); - } - - @Test - public void testRestoredTilePositionPreserved() { - verify(mBroadcastDispatcher).registerReceiver( - mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any(), anyInt(), any()); - String restoredTiles = "saver,internet,work,cast"; - Intent restoreTilesIntent = makeRestoreIntent(Secure.QS_TILES, null, restoredTiles); - - mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreTilesIntent); - - assertEquals(2, mAutoTracker.getRestoredTilePosition("work")); - } - - @Test - public void testNoRestoredTileReturnsEndPosition() { - verify(mBroadcastDispatcher).registerReceiver( - mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any(), anyInt(), any()); - Intent restoreTilesIntent = makeRestoreIntent(Secure.QS_TILES, null, null); - - mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreTilesIntent); - - assertEquals(END_POSITION, mAutoTracker.getRestoredTilePosition("work")); - } - - @Test - public void testBroadcastReceiverRegistered() { - verify(mBroadcastDispatcher).registerReceiver( - any(), mIntentFilterArgumentCaptor.capture(), any(), eq(UserHandle.of(USER)), - anyInt(), any()); - - assertTrue( - mIntentFilterArgumentCaptor.getValue().hasAction(Intent.ACTION_SETTING_RESTORED)); - } - - @Test - public void testBroadcastReceiverChangesWithUser() { - mAutoTracker.changeUser(UserHandle.of(USER + 1)); - - InOrder inOrder = Mockito.inOrder(mBroadcastDispatcher); - inOrder.verify(mBroadcastDispatcher).unregisterReceiver(any()); - inOrder.verify(mBroadcastDispatcher) - .registerReceiver(any(), any(), any(), eq(UserHandle.of(USER + 1)), anyInt(), - any()); - } - - @Test - public void testSettingRestoredWithTilesNotRemovedInSource_noAutoAddedInTarget() { - verify(mBroadcastDispatcher).registerReceiver( - mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any(), anyInt(), any()); - - // These tiles were present in the original device - String restoredTiles = "saver,work,internet,cast"; - Intent restoreTilesIntent = makeRestoreIntent(Secure.QS_TILES, null, restoredTiles); - mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreTilesIntent); - - // And these tiles have been auto-added in the original device - // (no auto-added before restore) - String restoredAutoAddTiles = "work"; - Intent restoreAutoAddTilesIntent = - makeRestoreIntent(Secure.QS_AUTO_ADDED_TILES, null, restoredAutoAddTiles); - mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreAutoAddTilesIntent); - - // Then, don't remove any current tiles - verify(mQSHost, never()).removeTiles(any()); - assertEquals(restoredAutoAddTiles, - mSecureSettings.getStringForUser(Secure.QS_AUTO_ADDED_TILES, USER)); - } - - @Test - public void testSettingRestoredWithTilesRemovedInSource_noAutoAddedInTarget() { - verify(mBroadcastDispatcher) - .registerReceiver(mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any(), - anyInt(), any()); - - // These tiles were present in the original device - String restoredTiles = "saver,internet,cast"; - Intent restoreTilesIntent = makeRestoreIntent(Secure.QS_TILES, null, restoredTiles); - mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreTilesIntent); - - // And these tiles have been auto-added in the original device - // (no auto-added before restore) - String restoredAutoAddTiles = "work"; - Intent restoreAutoAddTilesIntent = - makeRestoreIntent(Secure.QS_AUTO_ADDED_TILES, null, restoredAutoAddTiles); - mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreAutoAddTilesIntent); - - // Then, remove work tile - verify(mQSHost).removeTiles(List.of("work")); - assertEquals(restoredAutoAddTiles, - mSecureSettings.getStringForUser(Secure.QS_AUTO_ADDED_TILES, USER)); - } - - @Test - public void testSettingRestoredWithTilesRemovedInSource_sameAutoAddedinTarget() { - verify(mBroadcastDispatcher) - .registerReceiver(mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any(), - anyInt(), any()); - - // These tiles were present in the original device - String restoredTiles = "saver,internet,cast"; - Intent restoreTilesIntent = - makeRestoreIntent(Secure.QS_TILES, "saver, internet, cast, work", restoredTiles); - mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreTilesIntent); - - // And these tiles have been auto-added in the original device - // (no auto-added before restore) - String restoredAutoAddTiles = "work"; - Intent restoreAutoAddTilesIntent = - makeRestoreIntent(Secure.QS_AUTO_ADDED_TILES, "work", restoredAutoAddTiles); - mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreAutoAddTilesIntent); - - // Then, remove work tile - verify(mQSHost).removeTiles(List.of("work")); - assertEquals(restoredAutoAddTiles, - mSecureSettings.getStringForUser(Secure.QS_AUTO_ADDED_TILES, USER)); - } - - @Test - public void testSettingRestoredWithTilesRemovedInSource_othersAutoAddedinTarget() { - verify(mBroadcastDispatcher) - .registerReceiver(mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any(), - anyInt(), any()); - - // These tiles were present in the original device - String restoredTiles = "saver,internet,cast"; - Intent restoreTilesIntent = - makeRestoreIntent(Secure.QS_TILES, "saver, internet, cast, work", restoredTiles); - mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreTilesIntent); - - // And these tiles have been auto-added in the original device - // (no auto-added before restore) - String restoredAutoAddTiles = "work"; - Intent restoreAutoAddTilesIntent = - makeRestoreIntent(Secure.QS_AUTO_ADDED_TILES, "inversion", restoredAutoAddTiles); - mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreAutoAddTilesIntent); - - // Then, remove work tile - verify(mQSHost).removeTiles(List.of("work")); - - String setting = mSecureSettings.getStringForUser(Secure.QS_AUTO_ADDED_TILES, USER); - assertEquals(2, setting.split(",").length); - assertTrue(setting.contains("work")); - assertTrue(setting.contains("inversion")); - } - - - private Intent makeRestoreIntent( - String settingName, String previousValue, String restoredValue) { - Intent intent = new Intent(Intent.ACTION_SETTING_RESTORED); - intent.putExtra(Intent.EXTRA_SETTING_NAME, settingName); - intent.putExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE, previousValue); - intent.putExtra(Intent.EXTRA_SETTING_NEW_VALUE, restoredValue); - return intent; - } - - private AutoAddTracker createAutoAddTracker(int user) { - // Null handler wil dispatch sync. - return new AutoAddTracker( - mSecureSettings, - mBroadcastDispatcher, - mQSHost, - mDumpManager, - null, - mBackgroundExecutor, - user - ); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java index 206bbbfba753..4ce2d7c6d78b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java @@ -51,6 +51,7 @@ import android.widget.FrameLayout; import androidx.compose.ui.platform.ComposeView; import androidx.lifecycle.Lifecycle; +import androidx.lifecycle.LifecycleOwner; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; @@ -611,7 +612,8 @@ public class QSImplTest extends SysuiTestCase { when(mQSContainerImplController.getView()).thenReturn(mContainer); when(mQSPanelController.getTileLayout()).thenReturn(mQQsTileLayout); when(mQuickQSPanelController.getTileLayout()).thenReturn(mQsTileLayout); - when(mFooterActionsViewModelFactory.create(any())).thenReturn(mFooterActionsViewModel); + when(mFooterActionsViewModelFactory.create(any(LifecycleOwner.class))) + .thenReturn(mFooterActionsViewModel); } private void setUpMedia() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java deleted file mode 100644 index 6d1bc824c3c1..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java +++ /dev/null @@ -1,786 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.qs; - - -import static com.android.systemui.Flags.FLAG_QS_NEW_PIPELINE; -import static com.android.systemui.Flags.FLAG_QS_NEW_TILES; - -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertFalse; -import static junit.framework.Assert.assertTrue; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.isNull; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.database.ContentObserver; -import android.os.Handler; -import android.os.Looper; -import android.os.UserHandle; -import android.util.SparseArray; - -import androidx.annotation.Nullable; -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.filters.SmallTest; - -import com.android.internal.logging.MetricsLogger; -import com.android.internal.util.CollectionUtils; -import com.android.systemui.SysuiTestCase; -import com.android.systemui.animation.Expandable; -import com.android.systemui.classifier.FalsingManagerFake; -import com.android.systemui.dump.nano.SystemUIProtoDump; -import com.android.systemui.plugins.ActivityStarter; -import com.android.systemui.plugins.PluginManager; -import com.android.systemui.plugins.qs.QSFactory; -import com.android.systemui.plugins.qs.QSTile; -import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.qs.external.CustomTile; -import com.android.systemui.qs.external.CustomTileStatePersister; -import com.android.systemui.qs.external.TileLifecycleManager; -import com.android.systemui.qs.external.TileServiceKey; -import com.android.systemui.qs.logging.QSLogger; -import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository; -import com.android.systemui.qs.tileimpl.QSTileImpl; -import com.android.systemui.qs.tiles.di.NewQSTileFactory; -import com.android.systemui.res.R; -import com.android.systemui.settings.UserFileManager; -import com.android.systemui.settings.UserTracker; -import com.android.systemui.shade.ShadeController; -import com.android.systemui.statusbar.phone.AutoTileManager; -import com.android.systemui.tuner.TunerService; -import com.android.systemui.util.FakeSharedPreferences; -import com.android.systemui.util.concurrency.FakeExecutor; -import com.android.systemui.util.settings.FakeSettings; -import com.android.systemui.util.settings.SecureSettings; -import com.android.systemui.util.time.FakeSystemClock; - -import dagger.Lazy; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.mockito.stubbing.Answer; - -import java.io.PrintWriter; -import java.io.StringWriter; -import java.util.List; -import java.util.concurrent.Executor; - -import javax.inject.Provider; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public class QSTileHostTest extends SysuiTestCase { - - private static String MOCK_STATE_STRING = "MockState"; - private static ComponentName CUSTOM_TILE = - ComponentName.unflattenFromString("TEST_PKG/.TEST_CLS"); - private static final String CUSTOM_TILE_SPEC = CustomTile.toSpec(CUSTOM_TILE); - private static final String SETTING = QSHost.TILES_SETTING; - @Mock - private PluginManager mPluginManager; - @Mock - private TunerService mTunerService; - @Mock - private AutoTileManager mAutoTiles; - @Mock - private ShadeController mShadeController; - @Mock - private QSLogger mQSLogger; - @Mock - private CustomTile mCustomTile; - @Mock - private UserTracker mUserTracker; - @Mock - private CustomTileStatePersister mCustomTileStatePersister; - @Mock - private TileLifecycleManager.Factory mTileLifecycleManagerFactory; - @Mock - private TileLifecycleManager mTileLifecycleManager; - @Mock - private UserFileManager mUserFileManager; - - private SecureSettings mSecureSettings; - - private QSFactory mDefaultFactory; - - private SparseArray<SharedPreferences> mSharedPreferencesByUser; - - private QSPipelineFlagsRepository mQSPipelineFlagsRepository; - - private FakeExecutor mMainExecutor; - - private QSTileHost mQSTileHost; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - - mSetFlagsRule.disableFlags(FLAG_QS_NEW_PIPELINE); - mSetFlagsRule.disableFlags(FLAG_QS_NEW_TILES); - mQSPipelineFlagsRepository = new QSPipelineFlagsRepository(); - - mMainExecutor = new FakeExecutor(new FakeSystemClock()); - - mSharedPreferencesByUser = new SparseArray<>(); - when(mTileLifecycleManagerFactory - .create(any(Intent.class), any(UserHandle.class))) - .thenReturn(mTileLifecycleManager); - when(mUserFileManager.getSharedPreferences(anyString(), anyInt(), anyInt())) - .thenAnswer((Answer<SharedPreferences>) invocation -> { - assertEquals(QSTileHost.TILES, invocation.getArgument(0)); - int userId = invocation.getArgument(2); - if (!mSharedPreferencesByUser.contains(userId)) { - mSharedPreferencesByUser.put(userId, new FakeSharedPreferences()); - } - return mSharedPreferencesByUser.get(userId); - }); - - mSecureSettings = new FakeSettings(); - saveSetting(""); - setUpTileFactory(); - mQSTileHost = new TestQSTileHost(mContext, () -> null, mDefaultFactory, mMainExecutor, - mPluginManager, mTunerService, () -> mAutoTiles, () -> mShadeController, - mQSLogger, mUserTracker, mSecureSettings, mCustomTileStatePersister, - mTileLifecycleManagerFactory, mUserFileManager, mQSPipelineFlagsRepository); - mMainExecutor.runAllReady(); - - mSecureSettings.registerContentObserverForUserSync(SETTING, new ContentObserver(null) { - @Override - public void onChange(boolean selfChange) { - super.onChange(selfChange); - mMainExecutor.execute(() -> mQSTileHost.onTuningChanged(SETTING, getSetting())); - mMainExecutor.runAllReady(); - } - }, mUserTracker.getUserId()); - } - - private void saveSetting(String value) { - mSecureSettings.putStringForUser( - SETTING, value, "", false, mUserTracker.getUserId(), false); - } - - private String getSetting() { - return mSecureSettings.getStringForUser(SETTING, mUserTracker.getUserId()); - } - - private void setUpTileFactory() { - mDefaultFactory = new FakeQSFactory(spec -> { - if ("spec1".equals(spec)) { - return new TestTile1(mQSTileHost); - } else if ("spec2".equals(spec)) { - return new TestTile2(mQSTileHost); - } else if ("spec3".equals(spec)) { - return new TestTile3(mQSTileHost); - } else if ("na".equals(spec)) { - return new NotAvailableTile(mQSTileHost); - } else if (CUSTOM_TILE_SPEC.equals(spec)) { - QSTile tile = mCustomTile; - QSTile.State s = mock(QSTile.State.class); - s.spec = spec; - when(mCustomTile.getState()).thenReturn(s); - return tile; - } else if ("internet".equals(spec) - || "wifi".equals(spec) - || "cell".equals(spec)) { - return new TestTile1(mQSTileHost); - } else { - return null; - } - }); - when(mCustomTile.isAvailable()).thenReturn(true); - } - - @Test - public void testLoadTileSpecs_emptySetting() { - List<String> tiles = QSTileHost.loadTileSpecs(mContext, ""); - assertFalse(tiles.isEmpty()); - } - - @Test - public void testLoadTileSpecs_nullSetting() { - List<String> tiles = QSTileHost.loadTileSpecs(mContext, null); - assertFalse(tiles.isEmpty()); - } - - @Test - public void testInvalidSpecUsesDefault() { - mContext.getOrCreateTestableResources() - .addOverride(R.string.quick_settings_tiles, "spec1,spec2"); - saveSetting("not-valid"); - - assertEquals(2, mQSTileHost.getTiles().size()); - } - - @Test - public void testRemoveWifiAndCellularWithoutInternet() { - saveSetting("wifi, spec1, cell, spec2"); - - assertEquals("internet", mQSTileHost.getSpecs().get(0)); - assertEquals("spec1", mQSTileHost.getSpecs().get(1)); - assertEquals("spec2", mQSTileHost.getSpecs().get(2)); - } - - @Test - public void testRemoveWifiAndCellularWithInternet() { - saveSetting("wifi, spec1, cell, spec2, internet"); - - assertEquals("spec1", mQSTileHost.getSpecs().get(0)); - assertEquals("spec2", mQSTileHost.getSpecs().get(1)); - assertEquals("internet", mQSTileHost.getSpecs().get(2)); - } - - @Test - public void testRemoveWifiWithoutInternet() { - saveSetting("spec1, wifi, spec2"); - - assertEquals("spec1", mQSTileHost.getSpecs().get(0)); - assertEquals("internet", mQSTileHost.getSpecs().get(1)); - assertEquals("spec2", mQSTileHost.getSpecs().get(2)); - } - - @Test - public void testRemoveCellWithInternet() { - saveSetting("spec1, spec2, cell, internet"); - - assertEquals("spec1", mQSTileHost.getSpecs().get(0)); - assertEquals("spec2", mQSTileHost.getSpecs().get(1)); - assertEquals("internet", mQSTileHost.getSpecs().get(2)); - } - - @Test - public void testNoWifiNoCellularNoInternet() { - saveSetting("spec1,spec2"); - - assertEquals("spec1", mQSTileHost.getSpecs().get(0)); - assertEquals("spec2", mQSTileHost.getSpecs().get(1)); - } - - @Test - public void testSpecWithInvalidDoesNotUseDefault() { - mContext.getOrCreateTestableResources() - .addOverride(R.string.quick_settings_tiles, "spec1,spec2"); - saveSetting("spec2,not-valid"); - - assertEquals(1, mQSTileHost.getTiles().size()); - QSTile element = CollectionUtils.firstOrNull(mQSTileHost.getTiles()); - assertTrue(element instanceof TestTile2); - } - - @Test - public void testDump() { - saveSetting("spec1,spec2"); - StringWriter w = new StringWriter(); - PrintWriter pw = new PrintWriter(w); - mQSTileHost.dump(pw, new String[]{}); - - String output = "QSTileHost:" + "\n" - + "tile specs: [spec1, spec2]" + "\n" - + "current user: 0" + "\n" - + "is dirty: false" + "\n" - + "tiles:" + "\n" - + "TestTile1:" + "\n" - + " MockState" + "\n" - + "TestTile2:" + "\n" - + " MockState" + "\n"; - - System.out.println(output); - System.out.println(w.getBuffer().toString()); - - assertEquals(output, w.getBuffer().toString()); - } - - @Test - public void testDefault() { - mContext.getOrCreateTestableResources() - .addOverride(R.string.quick_settings_tiles_default, "spec1"); - saveSetting("default"); - assertEquals(1, mQSTileHost.getTiles().size()); - QSTile element = CollectionUtils.firstOrNull(mQSTileHost.getTiles()); - assertTrue(element instanceof TestTile1); - verify(mQSLogger).logTileAdded("spec1"); - } - - @Test - public void testNoRepeatedSpecs_addTile() { - mContext.getOrCreateTestableResources() - .addOverride(R.string.quick_settings_tiles, "spec1,spec2"); - saveSetting("spec1,spec2"); - - mQSTileHost.addTile("spec1"); - - assertEquals(2, mQSTileHost.getSpecs().size()); - assertEquals("spec1", mQSTileHost.getSpecs().get(0)); - assertEquals("spec2", mQSTileHost.getSpecs().get(1)); - } - - @Test - public void testAddTileAtValidPosition() { - mContext.getOrCreateTestableResources() - .addOverride(R.string.quick_settings_tiles, "spec1,spec3"); - saveSetting("spec1,spec3"); - - mQSTileHost.addTile("spec2", 1); - mMainExecutor.runAllReady(); - - assertEquals(3, mQSTileHost.getSpecs().size()); - assertEquals("spec1", mQSTileHost.getSpecs().get(0)); - assertEquals("spec2", mQSTileHost.getSpecs().get(1)); - assertEquals("spec3", mQSTileHost.getSpecs().get(2)); - } - - @Test - public void testAddTileAtInvalidPositionAddsToEnd() { - mContext.getOrCreateTestableResources() - .addOverride(R.string.quick_settings_tiles, "spec1,spec3"); - saveSetting("spec1,spec3"); - - mQSTileHost.addTile("spec2", 100); - mMainExecutor.runAllReady(); - - assertEquals(3, mQSTileHost.getSpecs().size()); - assertEquals("spec1", mQSTileHost.getSpecs().get(0)); - assertEquals("spec3", mQSTileHost.getSpecs().get(1)); - assertEquals("spec2", mQSTileHost.getSpecs().get(2)); - } - - @Test - public void testAddTileAtEnd() { - mContext.getOrCreateTestableResources() - .addOverride(R.string.quick_settings_tiles, "spec1,spec3"); - saveSetting("spec1,spec3"); - - mQSTileHost.addTile("spec2", QSTileHost.POSITION_AT_END); - mMainExecutor.runAllReady(); - - assertEquals(3, mQSTileHost.getSpecs().size()); - assertEquals("spec1", mQSTileHost.getSpecs().get(0)); - assertEquals("spec3", mQSTileHost.getSpecs().get(1)); - assertEquals("spec2", mQSTileHost.getSpecs().get(2)); - } - - @Test - public void testNoRepeatedSpecs_customTile() { - saveSetting(CUSTOM_TILE_SPEC); - - mQSTileHost.addTile(CUSTOM_TILE, /* end */ false); - mMainExecutor.runAllReady(); - - assertEquals(1, mQSTileHost.getSpecs().size()); - assertEquals(CUSTOM_TILE_SPEC, mQSTileHost.getSpecs().get(0)); - } - - @Test - public void testAddedAtBeginningOnDefault_customTile() { - saveSetting("spec1"); // seed - - mQSTileHost.addTile(CUSTOM_TILE); - mMainExecutor.runAllReady(); - - assertEquals(2, mQSTileHost.getSpecs().size()); - assertEquals(CUSTOM_TILE_SPEC, mQSTileHost.getSpecs().get(0)); - } - - @Test - public void testAddedAtBeginning_customTile() { - saveSetting("spec1"); // seed - - mQSTileHost.addTile(CUSTOM_TILE, /* end */ false); - mMainExecutor.runAllReady(); - - assertEquals(2, mQSTileHost.getSpecs().size()); - assertEquals(CUSTOM_TILE_SPEC, mQSTileHost.getSpecs().get(0)); - } - - @Test - public void testAddedAtEnd_customTile() { - saveSetting("spec1"); // seed - - mQSTileHost.addTile(CUSTOM_TILE, /* end */ true); - mMainExecutor.runAllReady(); - - assertEquals(2, mQSTileHost.getSpecs().size()); - assertEquals(CUSTOM_TILE_SPEC, mQSTileHost.getSpecs().get(1)); - } - - @Test - public void testLoadTileSpec_repeated() { - List<String> specs = QSTileHost.loadTileSpecs(mContext, "spec1,spec1,spec2"); - - assertEquals(2, specs.size()); - assertEquals("spec1", specs.get(0)); - assertEquals("spec2", specs.get(1)); - } - - @Test - public void testLoadTileSpec_repeatedInDefault() { - mContext.getOrCreateTestableResources() - .addOverride(R.string.quick_settings_tiles_default, "spec1,spec1"); - List<String> specs = QSTileHost.loadTileSpecs(mContext, "default"); - } - - @Test - public void testLoadTileSpec_repeatedDefaultAndSetting() { - mContext.getOrCreateTestableResources() - .addOverride(R.string.quick_settings_tiles_default, "spec1"); - List<String> specs = QSTileHost.loadTileSpecs(mContext, "default,spec1"); - } - - @Test - public void testNotAvailableTile_specNotNull() { - saveSetting("na"); - verify(mQSLogger, never()).logTileDestroyed(isNull(), anyString()); - } - - @Test - public void testCustomTileRemoved_stateDeleted() { - mQSTileHost.changeTilesByUser(List.of(CUSTOM_TILE_SPEC), List.of()); - - verify(mCustomTileStatePersister) - .removeState(new TileServiceKey(CUSTOM_TILE, mQSTileHost.getUserId())); - } - - @Test - public void testRemoveTiles() { - saveSetting("spec1,spec2,spec3"); - - mQSTileHost.removeTiles(List.of("spec1", "spec2")); - - mMainExecutor.runAllReady(); - assertEquals(List.of("spec3"), mQSTileHost.getSpecs()); - } - - @Test - public void testTilesRemovedInQuickSuccession() { - saveSetting("spec1,spec2,spec3"); - mQSTileHost.removeTile("spec1"); - mQSTileHost.removeTile("spec3"); - - mMainExecutor.runAllReady(); - assertEquals(List.of("spec2"), mQSTileHost.getSpecs()); - assertEquals("spec2", getSetting()); - } - - @Test - public void testAddTileInMainThread() { - saveSetting("spec1,spec2"); - - mQSTileHost.addTile("spec3"); - assertEquals(List.of("spec1", "spec2"), mQSTileHost.getSpecs()); - - mMainExecutor.runAllReady(); - assertEquals(List.of("spec1", "spec2", "spec3"), mQSTileHost.getSpecs()); - } - - @Test - public void testRemoveTileInMainThread() { - saveSetting("spec1,spec2"); - - mQSTileHost.removeTile("spec1"); - assertEquals(List.of("spec1", "spec2"), mQSTileHost.getSpecs()); - - mMainExecutor.runAllReady(); - assertEquals(List.of("spec2"), mQSTileHost.getSpecs()); - } - - @Test - public void testRemoveTilesInMainThread() { - saveSetting("spec1,spec2,spec3"); - - mQSTileHost.removeTiles(List.of("spec3", "spec1")); - assertEquals(List.of("spec1", "spec2", "spec3"), mQSTileHost.getSpecs()); - - mMainExecutor.runAllReady(); - assertEquals(List.of("spec2"), mQSTileHost.getSpecs()); - } - - @Test - public void testRemoveTileByUserInMainThread() { - saveSetting("spec1," + CUSTOM_TILE_SPEC); - - mQSTileHost.removeTileByUser(CUSTOM_TILE); - assertEquals(List.of("spec1", CUSTOM_TILE_SPEC), mQSTileHost.getSpecs()); - - mMainExecutor.runAllReady(); - assertEquals(List.of("spec1"), mQSTileHost.getSpecs()); - } - - @Test - public void testNonValidTileNotStoredInSettings() { - saveSetting("spec1,not-valid"); - - assertEquals(List.of("spec1"), mQSTileHost.getSpecs()); - assertEquals("spec1", getSetting()); - } - - @Test - public void testNotAvailableTileNotStoredInSettings() { - saveSetting("spec1,na"); - - assertEquals(List.of("spec1"), mQSTileHost.getSpecs()); - assertEquals("spec1", getSetting()); - } - - @Test - public void testIsTileAdded_true() { - int user = mUserTracker.getUserId(); - getSharedPreferencesForUser(user) - .edit() - .putBoolean(CUSTOM_TILE.flattenToString(), true) - .apply(); - - assertTrue(mQSTileHost.isTileAdded(CUSTOM_TILE, user)); - } - - @Test - public void testIsTileAdded_false() { - int user = mUserTracker.getUserId(); - getSharedPreferencesForUser(user) - .edit() - .putBoolean(CUSTOM_TILE.flattenToString(), false) - .apply(); - - assertFalse(mQSTileHost.isTileAdded(CUSTOM_TILE, user)); - } - - @Test - public void testIsTileAdded_notSet() { - int user = mUserTracker.getUserId(); - - assertFalse(mQSTileHost.isTileAdded(CUSTOM_TILE, user)); - } - - @Test - public void testIsTileAdded_differentUser() { - int user = mUserTracker.getUserId(); - mUserFileManager.getSharedPreferences(QSTileHost.TILES, 0, user) - .edit() - .putBoolean(CUSTOM_TILE.flattenToString(), true) - .apply(); - - assertFalse(mQSTileHost.isTileAdded(CUSTOM_TILE, user + 1)); - } - - @Test - public void testSetTileAdded_true() { - int user = mUserTracker.getUserId(); - mQSTileHost.setTileAdded(CUSTOM_TILE, user, true); - - assertTrue(getSharedPreferencesForUser(user) - .getBoolean(CUSTOM_TILE.flattenToString(), false)); - } - - @Test - public void testSetTileAdded_false() { - int user = mUserTracker.getUserId(); - mQSTileHost.setTileAdded(CUSTOM_TILE, user, false); - - assertFalse(getSharedPreferencesForUser(user) - .getBoolean(CUSTOM_TILE.flattenToString(), false)); - } - - @Test - public void testSetTileAdded_differentUser() { - int user = mUserTracker.getUserId(); - mQSTileHost.setTileAdded(CUSTOM_TILE, user, true); - - assertFalse(getSharedPreferencesForUser(user + 1) - .getBoolean(CUSTOM_TILE.flattenToString(), false)); - } - - @Test - public void testSetTileRemoved_afterCustomTileChangedByUser() { - int user = mUserTracker.getUserId(); - saveSetting(CUSTOM_TILE_SPEC); - - // This will be done by TileServiceManager - mQSTileHost.setTileAdded(CUSTOM_TILE, user, true); - - mQSTileHost.changeTilesByUser(mQSTileHost.getSpecs(), List.of("spec1")); - assertFalse(getSharedPreferencesForUser(user) - .getBoolean(CUSTOM_TILE.flattenToString(), false)); - } - - @Test - public void testSetTileRemoved_removedByUser() { - int user = mUserTracker.getUserId(); - saveSetting(CUSTOM_TILE_SPEC); - - // This will be done by TileServiceManager - mQSTileHost.setTileAdded(CUSTOM_TILE, user, true); - - mQSTileHost.removeTileByUser(CUSTOM_TILE); - mMainExecutor.runAllReady(); - assertFalse(getSharedPreferencesForUser(user) - .getBoolean(CUSTOM_TILE.flattenToString(), false)); - } - - @Test - public void testSetTileRemoved_removedBySystem() { - int user = mUserTracker.getUserId(); - saveSetting("spec1," + CUSTOM_TILE_SPEC); - - // This will be done by TileServiceManager - mQSTileHost.setTileAdded(CUSTOM_TILE, user, true); - - mQSTileHost.removeTile(CUSTOM_TILE_SPEC); - mMainExecutor.runAllReady(); - assertFalse(getSharedPreferencesForUser(user) - .getBoolean(CUSTOM_TILE.flattenToString(), false)); - } - - @Test - public void testProtoDump_noTiles() { - SystemUIProtoDump proto = new SystemUIProtoDump(); - mQSTileHost.dumpProto(proto, new String[0]); - - assertEquals(0, proto.tiles.length); - } - - @Test - public void testTilesInOrder() { - saveSetting("spec1," + CUSTOM_TILE_SPEC); - - SystemUIProtoDump proto = new SystemUIProtoDump(); - mQSTileHost.dumpProto(proto, new String[0]); - - assertEquals(2, proto.tiles.length); - assertEquals("spec1", proto.tiles[0].getSpec()); - assertEquals(CUSTOM_TILE.getPackageName(), proto.tiles[1].getComponentName().packageName); - assertEquals(CUSTOM_TILE.getClassName(), proto.tiles[1].getComponentName().className); - } - - private SharedPreferences getSharedPreferencesForUser(int user) { - return mUserFileManager.getSharedPreferences(QSTileHost.TILES, 0, user); - } - - private class TestQSTileHost extends QSTileHost { - TestQSTileHost(Context context, Lazy<NewQSTileFactory> newQSTileFactoryProvider, - QSFactory defaultFactory, Executor mainExecutor, - PluginManager pluginManager, TunerService tunerService, - Provider<AutoTileManager> autoTiles, - Lazy<ShadeController> shadeController, QSLogger qsLogger, - UserTracker userTracker, SecureSettings secureSettings, - CustomTileStatePersister customTileStatePersister, - TileLifecycleManager.Factory tileLifecycleManagerFactory, - UserFileManager userFileManager, QSPipelineFlagsRepository featureFlags) { - super(context, newQSTileFactoryProvider, defaultFactory, mainExecutor, pluginManager, - tunerService, autoTiles, shadeController, qsLogger, - userTracker, secureSettings, customTileStatePersister, - tileLifecycleManagerFactory, userFileManager, featureFlags); - } - - @Override - public void onPluginConnected(QSFactory plugin, Context pluginContext) { - } - - @Override - public void onPluginDisconnected(QSFactory plugin) { - } - } - - - private class TestTile extends QSTileImpl<QSTile.State> { - - protected TestTile(QSHost host) { - super( - host, - mock(QsEventLogger.class), - mock(Looper.class), - mock(Handler.class), - new FalsingManagerFake(), - mock(MetricsLogger.class), - mock(StatusBarStateController.class), - mock(ActivityStarter.class), - QSTileHostTest.this.mQSLogger - ); - } - - @Override - public State newTileState() { - State s = mock(QSTile.State.class); - when(s.toString()).thenReturn(MOCK_STATE_STRING); - return s; - } - - @Override - protected void handleClick(@Nullable Expandable expandable) {} - - @Override - protected void handleUpdateState(State state, Object arg) {} - - @Override - public int getMetricsCategory() { - return 0; - } - - @Override - public Intent getLongClickIntent() { - return null; - } - - @Override - public CharSequence getTileLabel() { - return null; - } - } - - private class TestTile1 extends TestTile { - - protected TestTile1(QSHost host) { - super(host); - } - } - - private class TestTile2 extends TestTile { - - protected TestTile2(QSHost host) { - super(host); - } - } - - private class TestTile3 extends TestTile { - - protected TestTile3(QSHost host) { - super(host); - } - } - - private class NotAvailableTile extends TestTile { - - protected NotAvailableTile(QSHost host) { - super(host); - } - - @Override - public boolean isAvailable() { - return false; - } - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepositoryTest.kt index 970cd17a731a..090a85b0d3f9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepositoryTest.kt @@ -15,20 +15,6 @@ class QSPipelineFlagsRepositoryTest : SysuiTestCase() { private val underTest = QSPipelineFlagsRepository() @Test - fun pipelineFlagDisabled() { - mSetFlagsRule.disableFlags(Flags.FLAG_QS_NEW_PIPELINE) - - assertThat(underTest.pipelineEnabled).isFalse() - } - - @Test - fun pipelineFlagEnabled() { - mSetFlagsRule.enableFlags(Flags.FLAG_QS_NEW_PIPELINE) - - assertThat(underTest.pipelineEnabled).isTrue() - } - - @Test fun tilesFlagDisabled() { mSetFlagsRule.disableFlags(Flags.FLAG_QS_NEW_TILES) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt index 3abdf6212f22..cb92b7745961 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt @@ -91,7 +91,12 @@ class NotificationTransitionAnimatorControllerTest : SysuiTestCase() { assertFalse(isExpandAnimationRunning!!) verify(headsUpManager) - .removeNotification(notificationKey, true /* releaseImmediately */, true /* animate */) + .removeNotification( + notificationKey, + /* releaseImmediately= */ true, + /* animate= */ true, + /* reason= */ "onIntentStarted(willAnimate=false)" + ) verify(onFinishAnimationCallback).run() } @@ -109,7 +114,12 @@ class NotificationTransitionAnimatorControllerTest : SysuiTestCase() { assertFalse(isExpandAnimationRunning!!) verify(headsUpManager) - .removeNotification(notificationKey, true /* releaseImmediately */, true /* animate */) + .removeNotification( + notificationKey, + /* releaseImmediately= */ true, + /* animate= */ true, + /* reason= */ "onLaunchAnimationCancelled()" + ) verify(onFinishAnimationCallback).run() } @@ -127,7 +137,12 @@ class NotificationTransitionAnimatorControllerTest : SysuiTestCase() { assertFalse(isExpandAnimationRunning!!) verify(headsUpManager) - .removeNotification(notificationKey, true /* releaseImmediately */, false /* animate */) + .removeNotification( + notificationKey, + /* releaseImmediately= */ true, + /* animate= */ false, + /* reason= */ "onLaunchAnimationEnd()" + ) verify(onFinishAnimationCallback).run() } @@ -161,12 +176,18 @@ class NotificationTransitionAnimatorControllerTest : SysuiTestCase() { controller.onTransitionAnimationEnd(isExpandingFullyAbove = true) verify(headsUpManager) - .removeNotification(summary.key, true /* releaseImmediately */, false /* animate */) + .removeNotification( + summary.key, + /* releaseImmediately= */ true, + /* animate= */ false, + /* reason= */ "onLaunchAnimationEnd()" + ) verify(headsUpManager, never()) .removeNotification( notification.entry.key, - true /* releaseImmediately */, - false /* animate */ + /* releaseImmediately= */ true, + /* animate= */ false, + /* reason= */ "onLaunchAnimationEnd()" ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt index 8e9323fead92..b4f4138fd409 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt @@ -108,30 +108,31 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { private val executor = FakeExecutor(systemClock) private val huns: ArrayList<NotificationEntry> = ArrayList() private lateinit var helper: NotificationGroupTestHelper + @Before fun setUp() { MockitoAnnotations.initMocks(this) helper = NotificationGroupTestHelper(mContext) - coordinator = HeadsUpCoordinator( - logger, - systemClock, - headsUpManager, - headsUpViewBinder, - visualInterruptionDecisionProvider, - remoteInputManager, - launchFullScreenIntentProvider, - flags, - headerController, - executor) + coordinator = + HeadsUpCoordinator( + logger, + systemClock, + headsUpManager, + headsUpViewBinder, + visualInterruptionDecisionProvider, + remoteInputManager, + launchFullScreenIntentProvider, + flags, + headerController, + executor + ) coordinator.attach(notifPipeline) // capture arguments: collectionListener = withArgCaptor { verify(notifPipeline).addCollectionListener(capture()) } - notifPromoter = withArgCaptor { - verify(notifPipeline).addPromoter(capture()) - } + notifPromoter = withArgCaptor { verify(notifPipeline).addPromoter(capture()) } notifLifetimeExtender = withArgCaptor { verify(notifPipeline).addNotificationLifetimeExtender(capture()) } @@ -141,9 +142,7 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { beforeFinalizeFilterListener = withArgCaptor { verify(notifPipeline).addOnBeforeFinalizeFilterListener(capture()) } - onHeadsUpChangedListener = withArgCaptor { - verify(headsUpManager).addListener(capture()) - } + onHeadsUpChangedListener = withArgCaptor { verify(headsUpManager).addListener(capture()) } actionPressListener = withArgCaptor { verify(remoteInputManager).addActionPressListener(capture()) } @@ -187,8 +186,8 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { assertTrue(notifLifetimeExtender.maybeExtendLifetime(entry, 0)) executor.advanceClockToLast() executor.runAllReady() - verify(headsUpManager, times(0)).removeNotification(anyString(), eq(false)) - verify(headsUpManager, times(1)).removeNotification(anyString(), eq(true)) + verify(headsUpManager, times(0)).removeNotification(anyString(), eq(false), anyString()) + verify(headsUpManager, times(1)).removeNotification(anyString(), eq(true), anyString()) } @Test @@ -203,8 +202,8 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { executor.advanceClockToLast() executor.runAllReady() assertTrue(notifLifetimeExtender.maybeExtendLifetime(entry, 0)) - verify(headsUpManager, times(0)).removeNotification(anyString(), eq(false)) - verify(headsUpManager, times(0)).removeNotification(anyString(), eq(true)) + verify(headsUpManager, times(0)).removeNotification(anyString(), eq(false), anyString()) + verify(headsUpManager, times(0)).removeNotification(anyString(), eq(true), anyString()) } @Test @@ -217,7 +216,7 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { notifLifetimeExtender.cancelLifetimeExtension(entry) executor.advanceClockToLast() executor.runAllReady() - verify(headsUpManager, times(0)).removeNotification(anyString(), any()) + verify(headsUpManager, never()).removeNotification(anyString(), any(), anyString()) } @Test @@ -227,14 +226,14 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { whenever(headsUpManager.canRemoveImmediately(anyString())).thenReturn(false) whenever(headsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L) - assertTrue(notifLifetimeExtender.maybeExtendLifetime(entry, /* reason = */ 0)) + assertTrue(notifLifetimeExtender.maybeExtendLifetime(entry, /* reason= */ 0)) actionPressListener.accept(entry) executor.runAllReady() verify(endLifetimeExtension, times(1)).onEndLifetimeExtension(notifLifetimeExtender, entry) - collectionListener.onEntryRemoved(entry, /* reason = */ 0) - verify(headsUpManager, times(1)).removeNotification(eq(entry.key), any()) + collectionListener.onEntryRemoved(entry, /* reason= */ 0) + verify(headsUpManager, times(1)).removeNotification(eq(entry.key), any(), anyString()) } @Test @@ -248,8 +247,8 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { whenever(headsUpManager.canRemoveImmediately(anyString())).thenReturn(true) assertFalse(notifLifetimeExtender.maybeExtendLifetime(entry, 0)) - collectionListener.onEntryRemoved(entry, /* reason = */ 0) - verify(headsUpManager, times(1)).removeNotification(eq(entry.key), any()) + collectionListener.onEntryRemoved(entry, /* reason= */ 0) + verify(headsUpManager, times(1)).removeNotification(eq(entry.key), any(), anyString()) } @Test @@ -261,8 +260,8 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { addHUN(entry) executor.advanceClockToLast() executor.runAllReady() - verify(headsUpManager, times(0)).removeNotification(anyString(), eq(false)) - verify(headsUpManager, times(0)).removeNotification(anyString(), eq(true)) + verify(headsUpManager, never()).removeNotification(anyString(), eq(false), anyString()) + verify(headsUpManager, never()).removeNotification(anyString(), eq(true), anyString()) } @Test @@ -273,8 +272,8 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { assertTrue(notifLifetimeExtender.maybeExtendLifetime(entry, 0)) executor.advanceClockToLast() executor.runAllReady() - verify(headsUpManager, times(1)).removeNotification(anyString(), eq(false)) - verify(headsUpManager, times(0)).removeNotification(anyString(), eq(true)) + verify(headsUpManager, times(1)).removeNotification(anyString(), eq(false), anyString()) + verify(headsUpManager, never()).removeNotification(anyString(), eq(true), anyString()) } @Test @@ -326,9 +325,8 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { // THEN only promote the current HUN, mEntry assertTrue(notifPromoter.shouldPromoteToTopLevel(entry)) - assertFalse(notifPromoter.shouldPromoteToTopLevel(NotificationEntryBuilder() - .setPkg("test-package2") - .build())) + val testPackage2 = NotificationEntryBuilder().setPkg("test-package2").build() + assertFalse(notifPromoter.shouldPromoteToTopLevel(testPackage2)) } @Test @@ -338,9 +336,9 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { // THEN only section the current HUN, mEntry assertTrue(notifSectioner.isInSection(entry)) - assertFalse(notifSectioner.isInSection(NotificationEntryBuilder() - .setPkg("test-package") - .build())) + assertFalse( + notifSectioner.isInSection(NotificationEntryBuilder().setPkg("test-package").build()) + ) } @Test @@ -350,10 +348,12 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { // THEN only the current HUN, mEntry, should be lifetimeExtended assertTrue(notifLifetimeExtender.maybeExtendLifetime(entry, /* cancellationReason */ 0)) - assertFalse(notifLifetimeExtender.maybeExtendLifetime( - NotificationEntryBuilder() - .setPkg("test-package") - .build(), /* cancellationReason */ 0)) + assertFalse( + notifLifetimeExtender.maybeExtendLifetime( + NotificationEntryBuilder().setPkg("test-package").build(), + /* reason= */ 0 + ) + ) } @Test @@ -366,8 +366,9 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(entry)) verify(headsUpManager, never()).showNotification(entry) withArgCaptor<BindCallback> { - verify(headsUpViewBinder).bindHeadsUpView(eq(entry), capture()) - }.onBindFinished(entry) + verify(headsUpViewBinder).bindHeadsUpView(eq(entry), capture()) + } + .onBindFinished(entry) // THEN we tell the HeadsUpManager to show the notification verify(headsUpManager).showNotification(entry) @@ -430,7 +431,7 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { whenever(remoteInputManager.isSpinning(any())).thenReturn(false) // THEN heads up manager should remove the entry - verify(headsUpManager).removeNotification(entry.key, false) + verify(headsUpManager).removeNotification(eq(entry.key), eq(false), anyString()) } private fun addHUN(entry: NotificationEntry) { @@ -545,19 +546,22 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { collectionListener.onEntryAdded(groupSibling1) collectionListener.onEntryAdded(groupSibling2) - val beforeTransformGroup = GroupEntryBuilder() - .setSummary(groupSummary) - .setChildren(listOf(groupSibling1, groupPriority, groupSibling2)) - .build() + val beforeTransformGroup = + GroupEntryBuilder() + .setSummary(groupSummary) + .setChildren(listOf(groupSibling1, groupPriority, groupSibling2)) + .build() beforeTransformGroupsListener.onBeforeTransformGroups(listOf(beforeTransformGroup)) verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any()) - val afterTransformGroup = GroupEntryBuilder() - .setSummary(groupSummary) - .setChildren(listOf(groupSibling1, groupSibling2)) - .build() - beforeFinalizeFilterListener - .onBeforeFinalizeFilter(listOf(groupPriority, afterTransformGroup)) + val afterTransformGroup = + GroupEntryBuilder() + .setSummary(groupSummary) + .setChildren(listOf(groupSibling1, groupSibling2)) + .build() + beforeFinalizeFilterListener.onBeforeFinalizeFilter( + listOf(groupPriority, afterTransformGroup) + ) verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSummary), any()) finishBind(groupPriority) @@ -583,19 +587,22 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { collectionListener.onEntryUpdated(groupSibling1) collectionListener.onEntryUpdated(groupSibling2) - val beforeTransformGroup = GroupEntryBuilder() - .setSummary(groupSummary) - .setChildren(listOf(groupSibling1, groupPriority, groupSibling2)) - .build() + val beforeTransformGroup = + GroupEntryBuilder() + .setSummary(groupSummary) + .setChildren(listOf(groupSibling1, groupPriority, groupSibling2)) + .build() beforeTransformGroupsListener.onBeforeTransformGroups(listOf(beforeTransformGroup)) verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any()) - val afterTransformGroup = GroupEntryBuilder() - .setSummary(groupSummary) - .setChildren(listOf(groupSibling1, groupSibling2)) - .build() - beforeFinalizeFilterListener - .onBeforeFinalizeFilter(listOf(groupPriority, afterTransformGroup)) + val afterTransformGroup = + GroupEntryBuilder() + .setSummary(groupSummary) + .setChildren(listOf(groupSibling1, groupSibling2)) + .build() + beforeFinalizeFilterListener.onBeforeFinalizeFilter( + listOf(groupPriority, afterTransformGroup) + ) verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSummary), any()) finishBind(groupPriority) @@ -618,19 +625,22 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { collectionListener.onEntryUpdated(groupSummary) collectionListener.onEntryUpdated(groupPriority) - val beforeTransformGroup = GroupEntryBuilder() - .setSummary(groupSummary) - .setChildren(listOf(groupSibling1, groupPriority, groupSibling2)) - .build() + val beforeTransformGroup = + GroupEntryBuilder() + .setSummary(groupSummary) + .setChildren(listOf(groupSibling1, groupPriority, groupSibling2)) + .build() beforeTransformGroupsListener.onBeforeTransformGroups(listOf(beforeTransformGroup)) verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any()) - val afterTransformGroup = GroupEntryBuilder() - .setSummary(groupSummary) - .setChildren(listOf(groupSibling1, groupSibling2)) - .build() - beforeFinalizeFilterListener - .onBeforeFinalizeFilter(listOf(groupPriority, afterTransformGroup)) + val afterTransformGroup = + GroupEntryBuilder() + .setSummary(groupSummary) + .setChildren(listOf(groupSibling1, groupSibling2)) + .build() + beforeFinalizeFilterListener.onBeforeFinalizeFilter( + listOf(groupPriority, afterTransformGroup) + ) verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSummary), any()) finishBind(groupPriority) @@ -654,19 +664,22 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { collectionListener.onEntryUpdated(groupSibling1) collectionListener.onEntryUpdated(groupSibling2) - val beforeTransformGroup = GroupEntryBuilder() - .setSummary(groupSummary) - .setChildren(listOf(groupSibling1, groupPriority, groupSibling2)) - .build() + val beforeTransformGroup = + GroupEntryBuilder() + .setSummary(groupSummary) + .setChildren(listOf(groupSibling1, groupPriority, groupSibling2)) + .build() beforeTransformGroupsListener.onBeforeTransformGroups(listOf(beforeTransformGroup)) verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any()) - val afterTransformGroup = GroupEntryBuilder() - .setSummary(groupSummary) - .setChildren(listOf(groupSibling1, groupSibling2)) - .build() - beforeFinalizeFilterListener - .onBeforeFinalizeFilter(listOf(groupPriority, afterTransformGroup)) + val afterTransformGroup = + GroupEntryBuilder() + .setSummary(groupSummary) + .setChildren(listOf(groupSibling1, groupSibling2)) + .build() + beforeFinalizeFilterListener.onBeforeFinalizeFilter( + listOf(groupPriority, afterTransformGroup) + ) finishBind(groupSummary) verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupPriority), any()) @@ -688,10 +701,11 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { collectionListener.onEntryAdded(groupSummary) collectionListener.onEntryAdded(groupSibling1) collectionListener.onEntryAdded(groupSibling2) - val groupEntry = GroupEntryBuilder() - .setSummary(groupSummary) - .setChildren(listOf(groupSibling1, groupSibling2)) - .build() + val groupEntry = + GroupEntryBuilder() + .setSummary(groupSummary) + .setChildren(listOf(groupSibling1, groupSibling2)) + .build() beforeTransformGroupsListener.onBeforeTransformGroups(listOf(groupEntry)) verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any()) beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry)) @@ -708,16 +722,16 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { @Test fun testNoTransferTwoChildAlert_withGroupAlertAll() { setShouldHeadsUp(groupSummary) - whenever(notifPipeline.allNotifs) - .thenReturn(listOf(groupSummary, groupChild1, groupChild2)) + whenever(notifPipeline.allNotifs).thenReturn(listOf(groupSummary, groupChild1, groupChild2)) collectionListener.onEntryAdded(groupSummary) collectionListener.onEntryAdded(groupChild1) collectionListener.onEntryAdded(groupChild2) - val groupEntry = GroupEntryBuilder() - .setSummary(groupSummary) - .setChildren(listOf(groupChild1, groupChild2)) - .build() + val groupEntry = + GroupEntryBuilder() + .setSummary(groupSummary) + .setChildren(listOf(groupChild1, groupChild2)) + .build() beforeTransformGroupsListener.onBeforeTransformGroups(listOf(groupEntry)) verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any()) beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry)) @@ -742,10 +756,11 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { collectionListener.onEntryAdded(groupSummary) collectionListener.onEntryAdded(groupChild1) collectionListener.onEntryAdded(groupChild2) - val groupEntry = GroupEntryBuilder() - .setSummary(groupSummary) - .setChildren(listOf(groupChild1, groupChild2)) - .build() + val groupEntry = + GroupEntryBuilder() + .setSummary(groupSummary) + .setChildren(listOf(groupChild1, groupChild2)) + .build() beforeTransformGroupsListener.onBeforeTransformGroups(listOf(groupEntry)) verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any()) beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry)) @@ -1045,9 +1060,7 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { .thenReturn(DecisionImpl.of(should)) } - private fun setDefaultShouldFullScreen( - originalDecision: FullScreenIntentDecision - ) { + private fun setDefaultShouldFullScreen(originalDecision: FullScreenIntentDecision) { val provider = visualInterruptionDecisionProvider whenever(provider.makeUnloggedFullScreenIntentDecision(any())).thenAnswer { val entry: NotificationEntry = it.getArgument(0) @@ -1059,11 +1072,8 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { entry: NotificationEntry, originalDecision: FullScreenIntentDecision ) { - whenever( - visualInterruptionDecisionProvider.makeUnloggedFullScreenIntentDecision(entry) - ).thenAnswer { - FullScreenIntentDecisionImpl(entry, originalDecision) - } + whenever(visualInterruptionDecisionProvider.makeUnloggedFullScreenIntentDecision(entry)) + .thenAnswer { FullScreenIntentDecisionImpl(entry, originalDecision) } } private fun verifyLoggedFullScreenIntentDecision( @@ -1089,7 +1099,8 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { private fun finishBind(entry: NotificationEntry) { verify(headsUpManager, never()).showNotification(entry) withArgCaptor<BindCallback> { - verify(headsUpViewBinder).bindHeadsUpView(eq(entry), capture()) - }.onBindFinished(entry) + verify(headsUpViewBinder).bindHeadsUpView(eq(entry), capture()) + } + .onBindFinished(entry) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java index 2d119174efff..63192f35ff40 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java @@ -36,6 +36,7 @@ import static org.mockito.Mockito.when; import android.animation.Animator; import android.animation.ValueAnimator.AnimatorUpdateListener; import android.os.Handler; +import android.platform.test.annotations.EnableFlags; import android.service.notification.StatusBarNotification; import android.testing.TestableLooper; import android.view.MotionEvent; @@ -52,6 +53,7 @@ import com.android.systemui.flags.FeatureFlags; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.shared.NotificationContentAlphaOptimization; import org.junit.Before; import org.junit.Rule; @@ -672,17 +674,31 @@ public class NotificationSwipeHelperTest extends SysuiTestCase { } @Test - public void testForceResetSwipeStateDoesNothingIfTranslationIsZero() { + public void testForceResetSwipeStateDoesNothingIfTranslationIsZeroAndAlphaIsOne() { doReturn(FAKE_ROW_WIDTH).when(mNotificationRow).getMeasuredWidth(); doReturn(0f).when(mNotificationRow).getTranslationX(); + doReturn(1f).when(mNotificationRow).getAlpha(); mSwipeHelper.forceResetSwipeState(mNotificationRow); verify(mNotificationRow).getTranslationX(); + verify(mNotificationRow).getAlpha(); verifyNoMoreInteractions(mNotificationRow); } @Test + @EnableFlags(NotificationContentAlphaOptimization.FLAG_NAME) + public void testForceResetSwipeStateResetsAlphaIfTranslationIsZeroAndAlphaNotOne() { + doReturn(FAKE_ROW_WIDTH).when(mNotificationRow).getMeasuredWidth(); + doReturn(0f).when(mNotificationRow).getTranslationX(); + doReturn(0.5f).when(mNotificationRow).getAlpha(); + + mSwipeHelper.forceResetSwipeState(mNotificationRow); + + verify(mNotificationRow).setContentAlpha(eq(1f)); + } + + @Test public void testForceResetSwipeStateResetsTranslationAndAlpha() { doReturn(FAKE_ROW_WIDTH).when(mNotificationRow).getMeasuredWidth(); doReturn(10f).when(mNotificationRow).getTranslationX(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java deleted file mode 100644 index 665544d094e4..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java +++ /dev/null @@ -1,648 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.systemui.statusbar.phone; - -import static com.android.systemui.Flags.FLAG_QS_NEW_PIPELINE; -import static com.android.systemui.qs.dagger.QSFlagsModule.RBC_AVAILABLE; -import static com.android.systemui.statusbar.phone.AutoTileManager.DEVICE_CONTROLS; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.isNotNull; -import static org.mockito.ArgumentMatchers.isNull; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; - -import android.content.ComponentName; -import android.content.Context; -import android.content.pm.PackageManager; -import android.hardware.display.ColorDisplayManager; -import android.hardware.display.NightDisplayListener; -import android.os.Handler; -import android.os.UserHandle; -import android.testing.TestableLooper; -import android.testing.TestableLooper.RunWithLooper; - -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.filters.SmallTest; - -import com.android.systemui.SysuiTestCase; -import com.android.systemui.dagger.NightDisplayListenerModule; -import com.android.systemui.plugins.qs.QSTile; -import com.android.systemui.qs.AutoAddTracker; -import com.android.systemui.qs.QSHost; -import com.android.systemui.qs.ReduceBrightColorsController; -import com.android.systemui.qs.UserSettingObserver; -import com.android.systemui.qs.external.CustomTile; -import com.android.systemui.res.R; -import com.android.systemui.statusbar.policy.CastController; -import com.android.systemui.statusbar.policy.CastDevice; -import com.android.systemui.statusbar.policy.DataSaverController; -import com.android.systemui.statusbar.policy.DeviceControlsController; -import com.android.systemui.statusbar.policy.HotspotController; -import com.android.systemui.statusbar.policy.SafetyController; -import com.android.systemui.statusbar.policy.WalletController; -import com.android.systemui.util.settings.FakeSettings; -import com.android.systemui.util.settings.SecureSettings; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Answers; -import org.mockito.ArgumentCaptor; -import org.mockito.InOrder; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; -import org.mockito.Spy; -import org.mockito.stubbing.Answer; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import javax.inject.Named; - -@RunWith(AndroidJUnit4.class) -@RunWithLooper -@SmallTest -public class AutoTileManagerTest extends SysuiTestCase { - - private static final String TEST_SETTING = "setting"; - private static final String TEST_SPEC = "spec"; - private static final String TEST_SETTING_COMPONENT = "setting_component"; - private static final String TEST_COMPONENT = "test_pkg/test_cls"; - private static final String TEST_CUSTOM_SPEC = "custom(" + TEST_COMPONENT + ")"; - private static final String TEST_CUSTOM_SAFETY_CLASS = "safety_cls"; - private static final String TEST_CUSTOM_SAFETY_PKG = "safety_pkg"; - private static final String TEST_CUSTOM_SAFETY_SPEC = CustomTile.toSpec(new ComponentName( - TEST_CUSTOM_SAFETY_PKG, TEST_CUSTOM_SAFETY_CLASS)); - private static final String SEPARATOR = AutoTileManager.SETTING_SEPARATOR; - - private static final int USER = 0; - - @Mock private QSHost mQsHost; - @Mock private AutoAddTracker mAutoAddTracker; - @Mock private CastController mCastController; - @Mock private HotspotController mHotspotController; - @Mock private DataSaverController mDataSaverController; - @Mock private ManagedProfileController mManagedProfileController; - @Mock private NightDisplayListener mNightDisplayListener; - @Mock(answer = Answers.RETURNS_SELF) - private NightDisplayListenerModule.Builder mNightDisplayListenerBuilder; - @Mock private ReduceBrightColorsController mReduceBrightColorsController; - @Mock private DeviceControlsController mDeviceControlsController; - @Mock private WalletController mWalletController; - @Mock private SafetyController mSafetyController; - @Mock(answer = Answers.RETURNS_SELF) - private AutoAddTracker.Builder mAutoAddTrackerBuilder; - @Mock private Context mUserContext; - @Spy private PackageManager mPackageManager; - private final boolean mIsReduceBrightColorsAvailable = true; - - private AutoTileManager mAutoTileManager; // under test - - private SecureSettings mSecureSettings; - private ManagedProfileController.Callback mManagedProfileCallback; - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - mSecureSettings = new FakeSettings(); - - mSetFlagsRule.disableFlags(FLAG_QS_NEW_PIPELINE); - - mContext.getOrCreateTestableResources().addOverride( - R.array.config_quickSettingsAutoAdd, - new String[] { - TEST_SETTING + SEPARATOR + TEST_SPEC, - TEST_SETTING_COMPONENT + SEPARATOR + TEST_CUSTOM_SPEC - } - ); - mContext.getOrCreateTestableResources().addOverride( - com.android.internal.R.bool.config_nightDisplayAvailable, true); - mContext.getOrCreateTestableResources().addOverride( - R.string.safety_quick_settings_tile_class, TEST_CUSTOM_SAFETY_CLASS); - - when(mAutoAddTrackerBuilder.build()).thenReturn(mAutoAddTracker); - when(mQsHost.getUserContext()).thenReturn(mUserContext); - when(mUserContext.getUser()).thenReturn(UserHandle.of(USER)); - mPackageManager = Mockito.spy(mContext.getPackageManager()); - when(mPackageManager.getPermissionControllerPackageName()) - .thenReturn(TEST_CUSTOM_SAFETY_PKG); - Context context = Mockito.spy(mContext); - when(context.getPackageManager()).thenReturn(mPackageManager); - when(mNightDisplayListenerBuilder.build()).thenReturn(mNightDisplayListener); - - mAutoTileManager = createAutoTileManager(context); - mAutoTileManager.init(); - } - - @After - public void tearDown() { - mAutoTileManager.destroy(); - } - - private AutoTileManager createAutoTileManager( - Context context, - AutoAddTracker.Builder autoAddTrackerBuilder, - HotspotController hotspotController, - DataSaverController dataSaverController, - ManagedProfileController managedProfileController, - NightDisplayListenerModule.Builder nightDisplayListenerBuilder, - CastController castController, - ReduceBrightColorsController reduceBrightColorsController, - DeviceControlsController deviceControlsController, - WalletController walletController, - SafetyController safetyController, - @Named(RBC_AVAILABLE) boolean isReduceBrightColorsAvailable) { - return new AutoTileManager(context, autoAddTrackerBuilder, mQsHost, - Handler.createAsync(TestableLooper.get(this).getLooper()), - mSecureSettings, - hotspotController, - dataSaverController, - managedProfileController, - mNightDisplayListenerBuilder, - castController, - reduceBrightColorsController, - deviceControlsController, - walletController, - safetyController, - isReduceBrightColorsAvailable); - } - - private AutoTileManager createAutoTileManager(Context context) { - return createAutoTileManager(context, mAutoAddTrackerBuilder, mHotspotController, - mDataSaverController, mManagedProfileController, mNightDisplayListenerBuilder, - mCastController, mReduceBrightColorsController, mDeviceControlsController, - mWalletController, mSafetyController, mIsReduceBrightColorsAvailable); - } - - @Test - public void testCreatedAutoTileManagerIsNotInitialized() { - AutoAddTracker.Builder builder = mock(AutoAddTracker.Builder.class, Answers.RETURNS_SELF); - AutoAddTracker tracker = mock(AutoAddTracker.class); - when(builder.build()).thenReturn(tracker); - HotspotController hC = mock(HotspotController.class); - DataSaverController dSC = mock(DataSaverController.class); - ManagedProfileController mPC = mock(ManagedProfileController.class); - NightDisplayListenerModule.Builder nDSB = mock(NightDisplayListenerModule.Builder.class); - CastController cC = mock(CastController.class); - ReduceBrightColorsController rBC = mock(ReduceBrightColorsController.class); - DeviceControlsController dCC = mock(DeviceControlsController.class); - WalletController wC = mock(WalletController.class); - SafetyController sC = mock(SafetyController.class); - - AutoTileManager manager = - createAutoTileManager(mock(Context.class), builder, hC, dSC, mPC, nDSB, cC, rBC, - dCC, wC, sC, true); - - verify(tracker, never()).initialize(); - verify(hC, never()).addCallback(any()); - verify(dSC, never()).addCallback(any()); - verify(mPC, never()).addCallback(any()); - verifyNoMoreInteractions(nDSB); - verify(cC, never()).addCallback(any()); - verify(rBC, never()).addCallback(any()); - verify(dCC, never()).setCallback(any()); - verify(wC, never()).getWalletPosition(); - verify(sC, never()).addCallback(any()); - assertNull(manager.getSecureSettingForKey(TEST_SETTING)); - assertNull(manager.getSecureSettingForKey(TEST_SETTING_COMPONENT)); - } - - @Test - public void testChangeUserWhenNotInitializedThrows() { - AutoTileManager manager = createAutoTileManager(mock(Context.class)); - - try { - manager.changeUser(UserHandle.of(USER + 1)); - fail(); - } catch (Exception e) { - // This should throw and take this path - } - } - - @Test - public void testChangeUserCallbacksStoppedAndStarted() throws Exception { - TestableLooper.get(this).runWithLooper(() -> - mAutoTileManager.changeUser(UserHandle.of(USER + 1)) - ); - - InOrder inOrderHotspot = inOrder(mHotspotController); - inOrderHotspot.verify(mHotspotController).removeCallback(any()); - inOrderHotspot.verify(mHotspotController).addCallback(any()); - - InOrder inOrderDataSaver = inOrder(mDataSaverController); - inOrderDataSaver.verify(mDataSaverController).removeCallback(any()); - inOrderDataSaver.verify(mDataSaverController).addCallback(any()); - - InOrder inOrderManagedProfile = inOrder(mManagedProfileController); - inOrderManagedProfile.verify(mManagedProfileController).removeCallback(any()); - inOrderManagedProfile.verify(mManagedProfileController).addCallback(any()); - - if (ColorDisplayManager.isNightDisplayAvailable(mContext)) { - InOrder inOrderNightDisplay = inOrder(mNightDisplayListener); - inOrderNightDisplay.verify(mNightDisplayListener).setCallback(isNull()); - inOrderNightDisplay.verify(mNightDisplayListener).setCallback(isNotNull()); - } - - InOrder inOrderReduceBrightColors = inOrder(mReduceBrightColorsController); - inOrderReduceBrightColors.verify(mReduceBrightColorsController).removeCallback(any()); - inOrderReduceBrightColors.verify(mReduceBrightColorsController).addCallback(any()); - - InOrder inOrderCast = inOrder(mCastController); - inOrderCast.verify(mCastController).removeCallback(any()); - inOrderCast.verify(mCastController).addCallback(any()); - - InOrder inOrderDevices = inOrder(mDeviceControlsController); - inOrderDevices.verify(mDeviceControlsController).removeCallback(); - inOrderDevices.verify(mDeviceControlsController).setCallback(any()); - - verify(mWalletController, times(2)).getWalletPosition(); - - InOrder inOrderSafety = inOrder(mSafetyController); - inOrderSafety.verify(mSafetyController).removeCallback(any()); - inOrderSafety.verify(mSafetyController).addCallback(any()); - - UserSettingObserver setting = mAutoTileManager.getSecureSettingForKey(TEST_SETTING); - assertEquals(USER + 1, setting.getCurrentUser()); - assertTrue(setting.isListening()); - } - - @Test - public void testChangeUserSomeCallbacksNotAdded() throws Exception { - when(mAutoAddTracker.isAdded("hotspot")).thenReturn(true); - when(mAutoAddTracker.isAdded("work")).thenReturn(true); - when(mAutoAddTracker.isAdded("cast")).thenReturn(true); - when(mAutoAddTracker.isAdded(TEST_SPEC)).thenReturn(true); - - TestableLooper.get(this).runWithLooper(() -> - mAutoTileManager.changeUser(UserHandle.of(USER + 1)) - ); - - verify(mAutoAddTracker).changeUser(UserHandle.of(USER + 1)); - - InOrder inOrderHotspot = inOrder(mHotspotController); - inOrderHotspot.verify(mHotspotController).removeCallback(any()); - inOrderHotspot.verify(mHotspotController, never()).addCallback(any()); - - InOrder inOrderDataSaver = inOrder(mDataSaverController); - inOrderDataSaver.verify(mDataSaverController).removeCallback(any()); - inOrderDataSaver.verify(mDataSaverController).addCallback(any()); - - InOrder inOrderManagedProfile = inOrder(mManagedProfileController); - inOrderManagedProfile.verify(mManagedProfileController).removeCallback(any()); - inOrderManagedProfile.verify(mManagedProfileController).addCallback(any()); - - if (ColorDisplayManager.isNightDisplayAvailable(mContext)) { - InOrder inOrderNightDisplay = inOrder(mNightDisplayListener); - inOrderNightDisplay.verify(mNightDisplayListener).setCallback(isNull()); - inOrderNightDisplay.verify(mNightDisplayListener).setCallback(isNotNull()); - } - - InOrder inOrderReduceBrightColors = inOrder(mReduceBrightColorsController); - inOrderReduceBrightColors.verify(mReduceBrightColorsController).removeCallback(any()); - inOrderReduceBrightColors.verify(mReduceBrightColorsController).addCallback(any()); - - InOrder inOrderCast = inOrder(mCastController); - inOrderCast.verify(mCastController).removeCallback(any()); - inOrderCast.verify(mCastController, never()).addCallback(any()); - - InOrder inOrderDevices = inOrder(mDeviceControlsController); - inOrderDevices.verify(mDeviceControlsController).removeCallback(); - inOrderDevices.verify(mDeviceControlsController).setCallback(any()); - - verify(mWalletController, times(2)).getWalletPosition(); - - InOrder inOrderSafety = inOrder(mSafetyController); - inOrderSafety.verify(mSafetyController).removeCallback(any()); - inOrderSafety.verify(mSafetyController).addCallback(any()); - - UserSettingObserver setting = mAutoTileManager.getSecureSettingForKey(TEST_SETTING); - assertEquals(USER + 1, setting.getCurrentUser()); - assertFalse(setting.isListening()); - } - - @Test - public void testGetCurrentUserId() throws Exception { - assertEquals(USER, mAutoTileManager.getCurrentUserId()); - - TestableLooper.get(this).runWithLooper(() -> - mAutoTileManager.changeUser(UserHandle.of(USER + 100)) - ); - - assertEquals(USER + 100, mAutoTileManager.getCurrentUserId()); - } - - @Test - public void nightTileAdded_whenActivated() { - if (!ColorDisplayManager.isNightDisplayAvailable(mContext)) { - return; - } - mAutoTileManager.mNightDisplayCallback.onActivated(true); - verify(mQsHost).addTile("night"); - } - - @Test - public void nightTileNotAdded_whenDeactivated() { - if (!ColorDisplayManager.isNightDisplayAvailable(mContext)) { - return; - } - mAutoTileManager.mNightDisplayCallback.onActivated(false); - verify(mQsHost, never()).addTile("night"); - } - - @Test - public void nightTileAdded_whenNightModeTwilight() { - if (!ColorDisplayManager.isNightDisplayAvailable(mContext)) { - return; - } - mAutoTileManager.mNightDisplayCallback.onAutoModeChanged( - ColorDisplayManager.AUTO_MODE_TWILIGHT); - verify(mQsHost).addTile("night"); - } - - @Test - public void nightTileAdded_whenNightModeCustom() { - if (!ColorDisplayManager.isNightDisplayAvailable(mContext)) { - return; - } - mAutoTileManager.mNightDisplayCallback.onAutoModeChanged( - ColorDisplayManager.AUTO_MODE_CUSTOM_TIME); - verify(mQsHost).addTile("night"); - } - - @Test - public void nightTileNotAdded_whenNightModeDisabled() { - if (!ColorDisplayManager.isNightDisplayAvailable(mContext)) { - return; - } - mAutoTileManager.mNightDisplayCallback.onAutoModeChanged( - ColorDisplayManager.AUTO_MODE_DISABLED); - verify(mQsHost, never()).addTile("night"); - } - - @Test - public void reduceBrightColorsTileAdded_whenActivated() { - mAutoTileManager.mReduceBrightColorsCallback.onActivated(true); - verify(mQsHost).addTile("reduce_brightness"); - } - - @Test - public void reduceBrightColorsTileNotAdded_whenDeactivated() { - mAutoTileManager.mReduceBrightColorsCallback.onActivated(false); - verify(mQsHost, never()).addTile("reduce_brightness"); - } - - private static List<CastDevice> buildFakeCastDevice(boolean isCasting) { - CastDevice.CastState state = isCasting - ? CastDevice.CastState.Connected - : CastDevice.CastState.Disconnected; - return Collections.singletonList( - new CastDevice( - "id", - /* name= */ null, - /* description= */ null, - /* state= */ state, - /* origin= */ CastDevice.CastOrigin.MediaProjection, - /* tag= */ null)); - } - - @Test - public void castTileAdded_whenDeviceIsCasting() { - doReturn(buildFakeCastDevice(true)).when(mCastController).getCastDevices(); - mAutoTileManager.mCastCallback.onCastDevicesChanged(); - verify(mQsHost).addTile("cast"); - } - - @Test - public void castTileNotAdded_whenDeviceIsNotCasting() { - doReturn(buildFakeCastDevice(false)).when(mCastController).getCastDevices(); - mAutoTileManager.mCastCallback.onCastDevicesChanged(); - verify(mQsHost, never()).addTile("cast"); - } - - @Test - public void testSettingTileAdded_onChanged() { - changeValue(TEST_SETTING, 1); - verify(mAutoAddTracker).setTileAdded(TEST_SPEC); - verify(mQsHost).addTile(TEST_SPEC); - } - - @Test - public void testSettingTileAddedComponentAtEnd_onChanged() { - changeValue(TEST_SETTING_COMPONENT, 1); - verify(mAutoAddTracker).setTileAdded(TEST_CUSTOM_SPEC); - verify(mQsHost).addTile(ComponentName.unflattenFromString(TEST_COMPONENT) - , /* end */ true); - } - - @Test - public void testSettingTileAdded_onlyOnce() { - changeValue(TEST_SETTING, 1); - changeValue(TEST_SETTING, 2); - verify(mAutoAddTracker).setTileAdded(TEST_SPEC); - verify(mQsHost).addTile(TEST_SPEC); - } - - @Test - public void testSettingTileNotAdded_onChangedTo0() { - changeValue(TEST_SETTING, 0); - verify(mAutoAddTracker, never()).setTileAdded(TEST_SPEC); - verify(mQsHost, never()).addTile(TEST_SPEC); - } - - @Test - public void testSettingTileNotAdded_ifPreviouslyAdded() { - when(mAutoAddTracker.isAdded(TEST_SPEC)).thenReturn(true); - - changeValue(TEST_SETTING, 1); - verify(mAutoAddTracker, never()).setTileAdded(TEST_SPEC); - verify(mQsHost, never()).addTile(TEST_SPEC); - } - - @Test - public void testSafetyTileNotAdded_ifPreviouslyAdded() { - ComponentName safetyComponent = CustomTile.getComponentFromSpec(TEST_CUSTOM_SAFETY_SPEC); - mAutoTileManager.init(); - verify(mQsHost, times(1)).addTile(safetyComponent, true); - when(mAutoAddTracker.isAdded(TEST_CUSTOM_SAFETY_SPEC)).thenReturn(true); - mAutoTileManager.init(); - verify(mQsHost, times(1)).addTile(safetyComponent, true); - } - - @Test - public void testSafetyTileAdded_onUserChange() { - ComponentName safetyComponent = CustomTile.getComponentFromSpec(TEST_CUSTOM_SAFETY_SPEC); - mAutoTileManager.init(); - verify(mQsHost, times(1)).addTile(safetyComponent, true); - when(mAutoAddTracker.isAdded(TEST_CUSTOM_SAFETY_SPEC)).thenReturn(false); - mAutoTileManager.changeUser(UserHandle.of(USER + 1)); - verify(mQsHost, times(2)).addTile(safetyComponent, true); - } - - @Test - public void testSafetyTileRemoved_onSafetyCenterDisable() { - ComponentName safetyComponent = CustomTile.getComponentFromSpec(TEST_CUSTOM_SAFETY_SPEC); - mAutoTileManager.init(); - when(mAutoAddTracker.isAdded(TEST_CUSTOM_SAFETY_SPEC)).thenReturn(true); - mAutoTileManager.mSafetyCallback.onSafetyCenterEnableChanged(false); - verify(mQsHost, times(1)).removeTile(TEST_CUSTOM_SAFETY_SPEC); - } - - @Test - public void testSafetyTileAdded_onSafetyCenterEnable() { - ComponentName safetyComponent = CustomTile.getComponentFromSpec(TEST_CUSTOM_SAFETY_SPEC); - mAutoTileManager.init(); - verify(mQsHost, times(1)).addTile(safetyComponent, true); - mAutoTileManager.mSafetyCallback.onSafetyCenterEnableChanged(false); - mAutoTileManager.mSafetyCallback.onSafetyCenterEnableChanged(true); - verify(mQsHost, times(2)).addTile(safetyComponent, true); - } - - @Test - public void managedProfileAdded_tileAdded() { - when(mAutoAddTracker.isAdded(eq("work"))).thenReturn(false); - when(mAutoAddTracker.getRestoredTilePosition(eq("work"))).thenReturn(2); - mAutoTileManager = createAutoTileManager(mContext); - Mockito.doAnswer((Answer<Object>) invocation -> { - mManagedProfileCallback = invocation.getArgument(0); - return null; - }).when(mManagedProfileController).addCallback(any()); - mAutoTileManager.init(); - when(mManagedProfileController.hasActiveProfile()).thenReturn(true); - - mManagedProfileCallback.onManagedProfileChanged(); - - verify(mQsHost, times(1)).addTile(eq("work"), eq(2)); - verify(mAutoAddTracker, times(1)).setTileAdded(eq("work")); - } - - @Test - public void managedProfileRemoved_tileRemoved() { - when(mAutoAddTracker.isAdded(eq("work"))).thenReturn(true); - mAutoTileManager = createAutoTileManager(mContext); - Mockito.doAnswer((Answer<Object>) invocation -> { - mManagedProfileCallback = invocation.getArgument(0); - return null; - }).when(mManagedProfileController).addCallback(any()); - mAutoTileManager.init(); - when(mManagedProfileController.hasActiveProfile()).thenReturn(false); - - mManagedProfileCallback.onManagedProfileChanged(); - - verify(mQsHost, times(1)).removeTile(eq("work")); - verify(mAutoAddTracker, times(1)).setTileRemoved(eq("work")); - } - - @Test - public void testAddControlsTileIfNotPresent() { - String spec = DEVICE_CONTROLS; - when(mAutoAddTracker.isAdded(eq(spec))).thenReturn(false); - when(mQsHost.getTiles()).thenReturn(new ArrayList<>()); - - mAutoTileManager.init(); - ArgumentCaptor<DeviceControlsController.Callback> captor = - ArgumentCaptor.forClass(DeviceControlsController.Callback.class); - - verify(mDeviceControlsController).setCallback(captor.capture()); - - captor.getValue().onControlsUpdate(3); - verify(mQsHost).addTile(spec, 3); - verify(mAutoAddTracker).setTileAdded(spec); - } - - @Test - public void testDontAddControlsTileIfPresent() { - String spec = DEVICE_CONTROLS; - when(mAutoAddTracker.isAdded(eq(spec))).thenReturn(false); - when(mQsHost.getTiles()).thenReturn(new ArrayList<>()); - - mAutoTileManager.init(); - ArgumentCaptor<DeviceControlsController.Callback> captor = - ArgumentCaptor.forClass(DeviceControlsController.Callback.class); - - verify(mDeviceControlsController).setCallback(captor.capture()); - - captor.getValue().removeControlsAutoTracker(); - verify(mQsHost, never()).addTile(spec, 3); - verify(mAutoAddTracker, never()).setTileAdded(spec); - verify(mAutoAddTracker).setTileRemoved(spec); - } - - @Test - public void testRemoveControlsTileFromTrackerWhenRequested() { - String spec = "controls"; - when(mAutoAddTracker.isAdded(eq(spec))).thenReturn(true); - QSTile mockTile = mock(QSTile.class); - when(mockTile.getTileSpec()).thenReturn(spec); - when(mQsHost.getTiles()).thenReturn(List.of(mockTile)); - - mAutoTileManager.init(); - ArgumentCaptor<DeviceControlsController.Callback> captor = - ArgumentCaptor.forClass(DeviceControlsController.Callback.class); - - verify(mDeviceControlsController).setCallback(captor.capture()); - - captor.getValue().onControlsUpdate(3); - verify(mQsHost, never()).addTile(spec, 3); - verify(mAutoAddTracker, never()).setTileAdded(spec); - } - - - @Test - public void testEmptyArray_doesNotCrash() { - mContext.getOrCreateTestableResources().addOverride( - R.array.config_quickSettingsAutoAdd, new String[0]); - createAutoTileManager(mContext).destroy(); - } - - @Test - public void testMissingConfig_doesNotCrash() { - mContext.getOrCreateTestableResources().addOverride( - R.array.config_quickSettingsAutoAdd, null); - createAutoTileManager(mContext).destroy(); - } - - @Test - public void testUserChange_newNightDisplayListenerCreated() { - UserHandle newUser = UserHandle.of(1000); - mAutoTileManager.changeUser(newUser); - InOrder inOrder = inOrder(mNightDisplayListenerBuilder); - inOrder.verify(mNightDisplayListenerBuilder).setUser(newUser.getIdentifier()); - inOrder.verify(mNightDisplayListenerBuilder).build(); - } - - // Will only notify if it's listening - private void changeValue(String key, int value) { - mSecureSettings.putIntForUser(key, value, USER); - TestableLooper.get(this).processAllMessages(); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt index 5b4578153233..70ac31d99559 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt @@ -33,8 +33,11 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import androidx.test.platform.app.InstrumentationRegistry import com.android.systemui.SysuiTestCase +import com.android.systemui.battery.BatteryMeterView import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.plugins.fakeDarkIconDispatcher import com.android.systemui.res.R import com.android.systemui.scene.ui.view.WindowRootView import com.android.systemui.shade.ShadeControllerImpl @@ -42,6 +45,7 @@ import com.android.systemui.shade.ShadeLogger import com.android.systemui.shade.ShadeViewController import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor import com.android.systemui.statusbar.CommandQueue +import com.android.systemui.statusbar.policy.Clock import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.window.StatusBarWindowStateController import com.android.systemui.unfold.SysUIUnfoldComponent @@ -70,7 +74,9 @@ import org.mockito.MockitoAnnotations @SmallTest @RunWith(AndroidJUnit4::class) class PhoneStatusBarViewControllerTest : SysuiTestCase() { + private val kosmos = Kosmos() + private val fakeDarkIconDispatcher = kosmos.fakeDarkIconDispatcher @Mock private lateinit var shadeViewController: ShadeViewController @Mock private lateinit var panelExpansionInteractor: PanelExpansionInteractor @Mock private lateinit var featureFlags: FeatureFlags @@ -91,6 +97,12 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { private lateinit var view: PhoneStatusBarView private lateinit var controller: PhoneStatusBarViewController + private val clockView: Clock + get() = view.requireViewById(R.id.clock) + + private val batteryView: BatteryMeterView + get() = view.requireViewById(R.id.battery) + private val unfoldConfig = UnfoldConfig() @Before @@ -114,16 +126,25 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { @Test fun onViewAttachedAndDrawn_startListeningConfigurationControllerCallback() { val view = createViewMock() - val argumentCaptor = - ArgumentCaptor.forClass(ConfigurationController.ConfigurationListener::class.java) + InstrumentationRegistry.getInstrumentation().runOnMainSync { controller = createAndInitController(view) } - verify(configurationController).addCallback(argumentCaptor.capture()) - argumentCaptor.value.onDensityOrFontScaleChanged() + verify(configurationController).addCallback(any()) + } + + @Test + fun onViewAttachedAndDrawn_darkReceiversRegistered() { + val view = createViewMock() + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + controller = createAndInitController(view) + } - verify(view).onDensityOrFontScaleChanged() + assertThat(fakeDarkIconDispatcher.receivers.size).isEqualTo(2) + assertThat(fakeDarkIconDispatcher.receivers).contains(clockView) + assertThat(fakeDarkIconDispatcher.receivers).contains(batteryView) } @Test @@ -158,6 +179,21 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { } @Test + fun onViewDetached_darkReceiversUnregistered() { + val view = createViewMock() + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + controller = createAndInitController(view) + } + + assertThat(fakeDarkIconDispatcher.receivers).isNotEmpty() + + controller.onViewDetached() + + assertThat(fakeDarkIconDispatcher.receivers).isEmpty() + } + + @Test fun handleTouchEventFromStatusBar_panelsNotEnabled_returnsFalseAndNoViewEvent() { `when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(false) val returnVal = @@ -353,7 +389,9 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { shadeLogger, viewUtil, configurationController, - mStatusOverlayHoverListenerFactory + mStatusOverlayHoverListenerFactory, + fakeDarkIconDispatcher, + mock(StatusBarContentInsetsProvider::class.java), ) .create(view) .also { it.init() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt index abc50bc09e55..648ddf847ea0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt @@ -32,10 +32,10 @@ import android.view.View import android.view.WindowInsets import android.widget.FrameLayout import androidx.test.filters.SmallTest +import com.android.systemui.Flags.FLAG_STATUS_BAR_STOP_UPDATING_WINDOW_HEIGHT import com.android.systemui.Flags.FLAG_STATUS_BAR_SWIPE_OVER_CHIP import com.android.systemui.Gefingerpoken import com.android.systemui.SysuiTestCase -import com.android.systemui.plugins.DarkIconDispatcher import com.android.systemui.res.R import com.android.systemui.statusbar.window.StatusBarWindowController import com.android.systemui.util.mockito.mock @@ -43,6 +43,7 @@ import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test +import org.mockito.Mockito.never import org.mockito.Mockito.spy import org.mockito.Mockito.times import org.mockito.Mockito.verify @@ -55,22 +56,14 @@ class PhoneStatusBarViewTest : SysuiTestCase() { private val systemIconsContainer: View get() = view.requireViewById(R.id.system_icons) - private val contentInsetsProvider = mock<StatusBarContentInsetsProvider>() private val windowController = mock<StatusBarWindowController>() @Before fun setUp() { - mDependency.injectTestDependency( - StatusBarContentInsetsProvider::class.java, - contentInsetsProvider - ) - mDependency.injectTestDependency(DarkIconDispatcher::class.java, mock<DarkIconDispatcher>()) mDependency.injectTestDependency(StatusBarWindowController::class.java, windowController) context.ensureTestableResources() view = spy(createStatusBarView()) whenever(view.rootWindowInsets).thenReturn(emptyWindowInsets()) - whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation()) - .thenReturn(Insets.NONE) } @Test @@ -185,21 +178,40 @@ class PhoneStatusBarViewTest : SysuiTestCase() { } @Test - fun onAttachedToWindow_updatesWindowHeight() { + @DisableFlags(FLAG_STATUS_BAR_STOP_UPDATING_WINDOW_HEIGHT) + fun onAttachedToWindow_flagOff_updatesWindowHeight() { view.onAttachedToWindow() verify(windowController).refreshStatusBarHeight() } @Test - fun onConfigurationChanged_updatesWindowHeight() { + @EnableFlags(FLAG_STATUS_BAR_STOP_UPDATING_WINDOW_HEIGHT) + fun onAttachedToWindow_flagOn_doesNotUpdateWindowHeight() { + view.onAttachedToWindow() + + verify(windowController, never()).refreshStatusBarHeight() + } + + @Test + @DisableFlags(FLAG_STATUS_BAR_STOP_UPDATING_WINDOW_HEIGHT) + fun onConfigurationChanged_flagOff_updatesWindowHeight() { view.onConfigurationChanged(Configuration()) verify(windowController).refreshStatusBarHeight() } @Test - fun onConfigurationChanged_multipleCalls_updatesWindowHeightMultipleTimes() { + @EnableFlags(FLAG_STATUS_BAR_STOP_UPDATING_WINDOW_HEIGHT) + fun onConfigurationChanged_flagOn_doesNotUpdateWindowHeight() { + view.onConfigurationChanged(Configuration()) + + verify(windowController, never()).refreshStatusBarHeight() + } + + @Test + @DisableFlags(FLAG_STATUS_BAR_STOP_UPDATING_WINDOW_HEIGHT) + fun onConfigurationChanged_multipleCalls_flagOff_updatesWindowHeightMultipleTimes() { view.onConfigurationChanged(Configuration()) view.onConfigurationChanged(Configuration()) view.onConfigurationChanged(Configuration()) @@ -209,10 +221,20 @@ class PhoneStatusBarViewTest : SysuiTestCase() { } @Test + @EnableFlags(FLAG_STATUS_BAR_STOP_UPDATING_WINDOW_HEIGHT) + fun onConfigurationChanged_multipleCalls_flagOn_neverUpdatesWindowHeight() { + view.onConfigurationChanged(Configuration()) + view.onConfigurationChanged(Configuration()) + view.onConfigurationChanged(Configuration()) + view.onConfigurationChanged(Configuration()) + + verify(windowController, never()).refreshStatusBarHeight() + } + + @Test fun onAttachedToWindow_updatesLeftTopRightPaddingsBasedOnInsets() { val insets = Insets.of(/* left= */ 10, /* top= */ 20, /* right= */ 30, /* bottom= */ 40) - whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation()) - .thenReturn(insets) + view.setInsetsFetcher { insets } view.onAttachedToWindow() @@ -223,10 +245,23 @@ class PhoneStatusBarViewTest : SysuiTestCase() { } @Test + fun onAttachedToWindow_noInsetsFetcher_noCrash() { + // Don't call `PhoneStatusBarView.setInsetsFetcher` + + // WHEN the view is attached + view.onAttachedToWindow() + + // THEN there's no crash, and the padding stays as it was + assertThat(view.paddingLeft).isEqualTo(0) + assertThat(view.paddingTop).isEqualTo(0) + assertThat(view.paddingRight).isEqualTo(0) + assertThat(view.paddingBottom).isEqualTo(0) + } + + @Test fun onConfigurationChanged_updatesLeftTopRightPaddingsBasedOnInsets() { val insets = Insets.of(/* left= */ 40, /* top= */ 30, /* right= */ 20, /* bottom= */ 10) - whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation()) - .thenReturn(insets) + view.setInsetsFetcher { insets } view.onConfigurationChanged(Configuration()) @@ -237,17 +272,31 @@ class PhoneStatusBarViewTest : SysuiTestCase() { } @Test + fun onConfigurationChanged_noInsetsFetcher_noCrash() { + // Don't call `PhoneStatusBarView.setInsetsFetcher` + + // WHEN the view is attached + view.onConfigurationChanged(Configuration()) + + // THEN there's no crash, and the padding stays as it was + assertThat(view.paddingLeft).isEqualTo(0) + assertThat(view.paddingTop).isEqualTo(0) + assertThat(view.paddingRight).isEqualTo(0) + assertThat(view.paddingBottom).isEqualTo(0) + } + + @Test fun onConfigurationChanged_noRelevantChange_doesNotUpdateInsets() { val previousInsets = Insets.of(/* left= */ 40, /* top= */ 30, /* right= */ 20, /* bottom= */ 10) - whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation()) - .thenReturn(previousInsets) + view.setInsetsFetcher { previousInsets } + context.orCreateTestableResources.overrideConfiguration(Configuration()) view.onAttachedToWindow() val newInsets = Insets.NONE - whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation()) - .thenReturn(newInsets) + view.setInsetsFetcher { newInsets } + view.onConfigurationChanged(Configuration()) assertThat(view.paddingLeft).isEqualTo(previousInsets.left) @@ -260,16 +309,14 @@ class PhoneStatusBarViewTest : SysuiTestCase() { fun onConfigurationChanged_densityChanged_updatesInsets() { val previousInsets = Insets.of(/* left= */ 40, /* top= */ 30, /* right= */ 20, /* bottom= */ 10) - whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation()) - .thenReturn(previousInsets) + view.setInsetsFetcher { previousInsets } val configuration = Configuration() configuration.densityDpi = 123 context.orCreateTestableResources.overrideConfiguration(configuration) view.onAttachedToWindow() val newInsets = Insets.NONE - whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation()) - .thenReturn(newInsets) + view.setInsetsFetcher { newInsets } configuration.densityDpi = 456 view.onConfigurationChanged(configuration) @@ -283,16 +330,14 @@ class PhoneStatusBarViewTest : SysuiTestCase() { fun onConfigurationChanged_fontScaleChanged_updatesInsets() { val previousInsets = Insets.of(/* left= */ 40, /* top= */ 30, /* right= */ 20, /* bottom= */ 10) - whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation()) - .thenReturn(previousInsets) + view.setInsetsFetcher { previousInsets } val configuration = Configuration() configuration.fontScale = 1f context.orCreateTestableResources.overrideConfiguration(configuration) view.onAttachedToWindow() val newInsets = Insets.NONE - whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation()) - .thenReturn(newInsets) + view.setInsetsFetcher { newInsets } configuration.fontScale = 2f view.onConfigurationChanged(configuration) @@ -318,8 +363,7 @@ class PhoneStatusBarViewTest : SysuiTestCase() { @Test fun onApplyWindowInsets_updatesLeftTopRightPaddingsBasedOnInsets() { val insets = Insets.of(/* left= */ 90, /* top= */ 10, /* right= */ 45, /* bottom= */ 50) - whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation()) - .thenReturn(insets) + view.setInsetsFetcher { insets } view.onApplyWindowInsets(WindowInsets(Rect())) @@ -360,7 +404,7 @@ class PhoneStatusBarViewTest : SysuiTestCase() { /* typeVisibilityMap = */ booleanArrayOf(), /* isRound = */ false, /* forceConsumingTypes = */ 0, - /* forceConsumingCaptionBar = */ false, + /* forceConsumingOpaqueCaptionBar = */ false, /* suppressScrimTypes = */ 0, /* displayCutout = */ DisplayCutout.NO_CUTOUT, /* roundedCorners = */ RoundedCorners.NO_ROUNDED_CORNERS, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java index 9fa392f3a337..7a34e94ab362 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java @@ -434,7 +434,11 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { // Then verify(mBubblesManager).onUserChangedBubble(entry, false); - verify(mHeadsUpManager).removeNotification(entry.getKey(), true); + verify(mHeadsUpManager).removeNotification( + entry.getKey(), + /* releaseImmediately= */ true, + /* reason= */ "onNotificationBubbleIconClicked" + ); verifyNoMoreInteractions(mContentIntent); verifyNoMoreInteractions(mShadeController); @@ -456,7 +460,11 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { // Then verify(mBubblesManager).onUserChangedBubble(entry, true); - verify(mHeadsUpManager).removeNotification(entry.getKey(), true); + verify(mHeadsUpManager).removeNotification( + entry.getKey(), + /* releaseImmediately= */ true, + /* reason= */ "onNotificationBubbleIconClicked" + ); verify(mContentIntent, atLeastOnce()).isActivity(); verifyNoMoreInteractions(mContentIntent); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt index bf0a39be044b..06b3b57bd133 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt @@ -57,6 +57,7 @@ class ModesDialogDelegateTest : SysuiTestCase() { private val activityStarter = kosmos.activityStarter private val mockDialogTransitionAnimator = kosmos.mockDialogTransitionAnimator private val mockAnimationController = kosmos.mockActivityTransitionAnimatorController + private val mockDialogEventLogger = kosmos.mockModesDialogEventLogger private lateinit var underTest: ModesDialogDelegate @Before @@ -75,6 +76,7 @@ class ModesDialogDelegateTest : SysuiTestCase() { mockDialogTransitionAnimator, activityStarter, { kosmos.modesDialogViewModel }, + mockDialogEventLogger, kosmos.mainCoroutineContext, ) } @@ -121,4 +123,12 @@ class ModesDialogDelegateTest : SysuiTestCase() { assertThat(underTest.currentDialog).isNull() } + + @Test + fun openSettings_logsEvent() = + testScope.runTest { + val dialog: SystemUIDialog = mock() + underTest.openSettings(dialog) + verify(mockDialogEventLogger).logDialogSettings() + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java index 8b7d921a1c5b..4ea1a0ca9f2b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java @@ -229,6 +229,32 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase { } @Test + public void testVolumeChangeW_inAudioSharing_doStateChanged() { + ArgumentCaptor<VolumeDialogController.State> stateCaptor = + ArgumentCaptor.forClass(VolumeDialogController.State.class); + mVolumeController.setDeviceInteractive(false); + when(mWakefullnessLifcycle.getWakefulness()) + .thenReturn(WakefulnessLifecycle.WAKEFULNESS_AWAKE); + // For now, mAudioManager.getDevicesForStream returns DEVICE_NONE during audio sharing + when(mAudioManager.getDevicesForStream(AudioManager.STREAM_MUSIC)) + .thenReturn(AudioManager.DEVICE_NONE); + + mVolumeController.mInAudioSharing = true; + mVolumeController.onVolumeChangedW(AudioManager.STREAM_MUSIC, AudioManager.FLAG_SHOW_UI); + verify(mCallback).onStateChanged(stateCaptor.capture()); + assertThat(stateCaptor.getValue().states.contains(AudioManager.STREAM_MUSIC)).isTrue(); + assertThat(stateCaptor.getValue().states.get(AudioManager.STREAM_MUSIC).routedToBluetooth) + .isTrue(); + + mVolumeController.mInAudioSharing = false; + mVolumeController.onVolumeChangedW(AudioManager.STREAM_MUSIC, AudioManager.FLAG_SHOW_UI); + verify(mCallback, times(2)).onStateChanged(stateCaptor.capture()); + assertThat(stateCaptor.getValue().states.contains(AudioManager.STREAM_MUSIC)).isTrue(); + assertThat(stateCaptor.getValue().states.get(AudioManager.STREAM_MUSIC).routedToBluetooth) + .isFalse(); + } + + @Test public void testOnRemoteVolumeChanged_newStream_noNullPointer() { MediaSession.Token token = new MediaSession.Token(Process.myUid(), null); mVolumeController.mMediaSessionsCallbacksW.onRemoteVolumeChanged(token, 0); diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubbleEducationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubbleEducationControllerTest.kt index 2021f02e5a8a..55aff130f691 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubbleEducationControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubbleEducationControllerTest.kt @@ -123,11 +123,12 @@ class BubbleEducationControllerTest : SysUiStateTest() { /* taskId= */ 0, "locus", /* isDismissable= */ true, + directExecutor(), directExecutor() ) {} } else { val intent = Intent(Intent.ACTION_VIEW).setPackage(mContext.packageName) - Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor()) + Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor(), directExecutor()) } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index e5e04dc9b82f..9dd3e53efa63 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -459,7 +459,7 @@ public class BubblesTest extends SysuiTestCase { mContext.getSystemService(WindowManager.class)); mPositioner.setMaxBubbles(5); mBubbleData = new BubbleData(mContext, mBubbleLogger, mPositioner, mEducationController, - syncExecutor); + syncExecutor, syncExecutor); when(mUserManager.getProfiles(ActivityManager.getCurrentUser())).thenReturn( Collections.singletonList(mock(UserInfo.class))); @@ -2465,9 +2465,10 @@ public class BubblesTest extends SysuiTestCase { workEntry.setBubbleMetadata(getMetadata()); workEntry.setFlagBubble(true); + SyncExecutor executor = new SyncExecutor(); return new Bubble(mBubblesManager.notifToBubbleEntry(workEntry), null, - mock(Bubbles.PendingIntentCanceledListener.class), new SyncExecutor()); + mock(Bubbles.PendingIntentCanceledListener.class), executor, executor); } private BubbleEntry createBubbleEntry(boolean isConversation) { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelKosmos.kt index e70631e89939..e8612d084b14 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelKosmos.kt @@ -14,6 +14,8 @@ * limitations under the License. */ +@file:OptIn(ExperimentalCoroutinesApi::class) + package com.android.systemui.bouncer.ui.viewmodel import android.content.applicationContext @@ -26,26 +28,31 @@ import com.android.systemui.deviceentry.domain.interactor.deviceEntryBiometricsA import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor import com.android.systemui.kosmos.Kosmos -import com.android.systemui.kosmos.testScope +import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.user.ui.viewmodel.userSwitcherViewModel import com.android.systemui.util.time.systemClock import kotlinx.coroutines.ExperimentalCoroutinesApi -@ExperimentalCoroutinesApi -val Kosmos.bouncerMessageViewModel by - Kosmos.Fixture { - BouncerMessageViewModel( - applicationContext = applicationContext, - applicationScope = testScope.backgroundScope, - bouncerInteractor = bouncerInteractor, - simBouncerInteractor = simBouncerInteractor, - authenticationInteractor = authenticationInteractor, - selectedUser = userSwitcherViewModel.selectedUser, - clock = systemClock, - biometricMessageInteractor = biometricMessageInteractor, - faceAuthInteractor = deviceEntryFaceAuthInteractor, - deviceUnlockedInteractor = deviceUnlockedInteractor, - deviceEntryBiometricsAllowedInteractor = deviceEntryBiometricsAllowedInteractor, - flags = composeBouncerFlags, - ) +val Kosmos.bouncerMessageViewModel by Fixture { + BouncerMessageViewModel( + applicationContext = applicationContext, + bouncerInteractor = bouncerInteractor, + simBouncerInteractor = simBouncerInteractor, + authenticationInteractor = authenticationInteractor, + userSwitcherViewModel = userSwitcherViewModel, + clock = systemClock, + biometricMessageInteractor = biometricMessageInteractor, + faceAuthInteractor = deviceEntryFaceAuthInteractor, + deviceUnlockedInteractor = deviceUnlockedInteractor, + deviceEntryBiometricsAllowedInteractor = deviceEntryBiometricsAllowedInteractor, + flags = composeBouncerFlags, + ) +} + +val Kosmos.bouncerMessageViewModelFactory by Fixture { + object : BouncerMessageViewModel.Factory { + override fun create(): BouncerMessageViewModel { + return bouncerMessageViewModel + } } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt index c3dad748064d..e405d17166b9 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt @@ -21,6 +21,7 @@ package com.android.systemui.bouncer.ui.viewmodel import android.app.admin.devicePolicyManager import android.content.applicationContext import com.android.systemui.authentication.domain.interactor.authenticationInteractor +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.bouncer.domain.interactor.bouncerActionButtonInteractor import com.android.systemui.bouncer.domain.interactor.bouncerInteractor import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor @@ -28,28 +29,97 @@ import com.android.systemui.bouncer.shared.flag.composeBouncerFlags import com.android.systemui.inputmethod.domain.interactor.inputMethodInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import com.android.systemui.kosmos.testDispatcher -import com.android.systemui.kosmos.testScope import com.android.systemui.user.domain.interactor.selectedUserInteractor import com.android.systemui.user.ui.viewmodel.userSwitcherViewModel import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.StateFlow -val Kosmos.bouncerViewModel by Fixture { - BouncerViewModel( +val Kosmos.bouncerSceneActionsViewModel by Fixture { + BouncerSceneActionsViewModel( + bouncerInteractor = bouncerInteractor, + ) +} + +val Kosmos.bouncerSceneActionsViewModelFactory by Fixture { + object : BouncerSceneActionsViewModel.Factory { + override fun create(): BouncerSceneActionsViewModel { + return bouncerSceneActionsViewModel + } + } +} + +val Kosmos.bouncerSceneContentViewModel by Fixture { + BouncerSceneContentViewModel( applicationContext = applicationContext, - applicationScope = testScope.backgroundScope, - mainDispatcher = testDispatcher, bouncerInteractor = bouncerInteractor, - inputMethodInteractor = inputMethodInteractor, - simBouncerInteractor = simBouncerInteractor, authenticationInteractor = authenticationInteractor, - selectedUserInteractor = selectedUserInteractor, devicePolicyManager = devicePolicyManager, - bouncerMessageViewModel = bouncerMessageViewModel, + bouncerMessageViewModelFactory = bouncerMessageViewModelFactory, flags = composeBouncerFlags, - selectedUser = userSwitcherViewModel.selectedUser, - users = userSwitcherViewModel.users, - userSwitcherMenu = userSwitcherViewModel.menu, - actionButton = bouncerActionButtonInteractor.actionButton, + userSwitcher = userSwitcherViewModel, + actionButtonInteractor = bouncerActionButtonInteractor, + pinViewModelFactory = pinBouncerViewModelFactory, + patternViewModelFactory = patternBouncerViewModelFactory, + passwordViewModelFactory = passwordBouncerViewModelFactory, ) } + +val Kosmos.bouncerSceneContentViewModelFactory by Fixture { + object : BouncerSceneContentViewModel.Factory { + override fun create(): BouncerSceneContentViewModel { + return bouncerSceneContentViewModel + } + } +} + +val Kosmos.pinBouncerViewModelFactory by Fixture { + object : PinBouncerViewModel.Factory { + override fun create( + isInputEnabled: StateFlow<Boolean>, + onIntentionalUserInput: () -> Unit, + authenticationMethod: AuthenticationMethodModel, + ): PinBouncerViewModel { + return PinBouncerViewModel( + applicationContext = applicationContext, + interactor = bouncerInteractor, + simBouncerInteractor = simBouncerInteractor, + isInputEnabled = isInputEnabled, + onIntentionalUserInput = onIntentionalUserInput, + authenticationMethod = authenticationMethod, + ) + } + } +} + +val Kosmos.patternBouncerViewModelFactory by Fixture { + object : PatternBouncerViewModel.Factory { + override fun create( + isInputEnabled: StateFlow<Boolean>, + onIntentionalUserInput: () -> Unit, + ): PatternBouncerViewModel { + return PatternBouncerViewModel( + applicationContext = applicationContext, + interactor = bouncerInteractor, + isInputEnabled = isInputEnabled, + onIntentionalUserInput = onIntentionalUserInput, + ) + } + } +} + +val Kosmos.passwordBouncerViewModelFactory by Fixture { + object : PasswordBouncerViewModel.Factory { + override fun create( + isInputEnabled: StateFlow<Boolean>, + onIntentionalUserInput: () -> Unit, + ): PasswordBouncerViewModel { + return PasswordBouncerViewModel( + interactor = bouncerInteractor, + inputMethodInteractor = inputMethodInteractor, + selectedUserInteractor = selectedUserInteractor, + isInputEnabled = isInputEnabled, + onIntentionalUserInput = onIntentionalUserInput, + ) + } + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorKosmos.kt index ee48c105a987..2ab82214e497 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorKosmos.kt @@ -17,6 +17,7 @@ package com.android.systemui.communal.domain.interactor import com.android.systemui.communal.data.repository.communalSceneRepository +import com.android.systemui.communal.shared.log.communalSceneLogger import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope @@ -24,6 +25,7 @@ val Kosmos.communalSceneInteractor: CommunalSceneInteractor by Kosmos.Fixture { CommunalSceneInteractor( applicationScope = applicationCoroutineScope, - communalSceneRepository = communalSceneRepository, + repository = communalSceneRepository, + logger = communalSceneLogger, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorKosmos.kt new file mode 100644 index 000000000000..81242244b7a6 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorKosmos.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.domain.interactor + +import com.android.systemui.common.usagestats.domain.interactor.usageStatsInteractor +import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.log.logcatLogBuffer +import com.android.systemui.plugins.activityStarter +import com.android.systemui.shared.system.taskStackChangeListeners +import com.android.systemui.util.time.fakeSystemClock + +val Kosmos.widgetTrampolineInteractor: WidgetTrampolineInteractor by + Kosmos.Fixture { + WidgetTrampolineInteractor( + activityStarter = activityStarter, + systemClock = fakeSystemClock, + keyguardTransitionInteractor = keyguardTransitionInteractor, + taskStackChangeListeners = taskStackChangeListeners, + usageStatsInteractor = usageStatsInteractor, + logBuffer = logcatLogBuffer("WidgetTrampolineInteractor"), + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/shared/log/CommunalSceneLoggerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/shared/log/CommunalSceneLoggerKosmos.kt new file mode 100644 index 000000000000..b560ee882929 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/shared/log/CommunalSceneLoggerKosmos.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.shared.log + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.log.logcatLogBuffer + +val Kosmos.communalSceneLogger: CommunalSceneLogger by + Kosmos.Fixture { CommunalSceneLogger(logcatLogBuffer("CommunalSceneLogger")) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt index 4571c19d101a..54a6c0c1d182 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt @@ -60,13 +60,13 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository { override val bottomAreaAlpha: StateFlow<Float> = _bottomAreaAlpha private val _isKeyguardShowing = MutableStateFlow(false) - override val isKeyguardShowing: Flow<Boolean> = _isKeyguardShowing + override val isKeyguardShowing: StateFlow<Boolean> = _isKeyguardShowing private val _isKeyguardUnlocked = MutableStateFlow(false) override val isKeyguardDismissible: StateFlow<Boolean> = _isKeyguardUnlocked.asStateFlow() private val _isKeyguardOccluded = MutableStateFlow(false) - override val isKeyguardOccluded: Flow<Boolean> = _isKeyguardOccluded + override val isKeyguardOccluded: StateFlow<Boolean> = _isKeyguardOccluded private val _isDozing = MutableStateFlow(false) override val isDozing: StateFlow<Boolean> = _isDozing diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt index f1625948880f..64ae05131b5a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt @@ -16,6 +16,7 @@ package com.android.systemui.keyguard.domain.interactor +import com.android.systemui.communal.domain.interactor.communalSceneInteractor import com.android.systemui.communal.domain.interactor.communalSettingsInteractor import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository @@ -38,6 +39,7 @@ var Kosmos.fromDreamingTransitionInteractor by mainDispatcher = testDispatcher, keyguardInteractor = keyguardInteractor, glanceableHubTransitions = glanceableHubTransitions, + communalSceneInteractor = communalSceneInteractor, communalSettingsInteractor = communalSettingsInteractor, powerInteractor = powerInteractor, keyguardOcclusionInteractor = keyguardOcclusionInteractor, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelKosmos.kt index 450dcc25c903..d06bab2f5345 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelKosmos.kt @@ -19,13 +19,11 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import com.android.systemui.util.mockito.mock import kotlinx.coroutines.ExperimentalCoroutinesApi @ExperimentalCoroutinesApi val Kosmos.dreamingToLockscreenTransitionViewModel by Fixture { DreamingToLockscreenTransitionViewModel( - fromDreamingTransitionInteractor = mock(), animationFlow = keyguardTransitionAnimationFlow, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/DarkIconDispatcherKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/DarkIconDispatcherKosmos.kt new file mode 100644 index 000000000000..3d125e93a030 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/DarkIconDispatcherKosmos.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.plugins + +import com.android.systemui.kosmos.Kosmos + +var Kosmos.fakeDarkIconDispatcher: FakeDarkIconDispatcher by + Kosmos.Fixture { FakeDarkIconDispatcher() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/FakeDarkIconDispatcher.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/FakeDarkIconDispatcher.kt new file mode 100644 index 000000000000..102a853a8250 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/FakeDarkIconDispatcher.kt @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.plugins + +import android.graphics.Rect +import java.util.ArrayList + +class FakeDarkIconDispatcher : DarkIconDispatcher { + val receivers = mutableListOf<DarkIconDispatcher.DarkReceiver>() + + override fun setIconsDarkArea(r: ArrayList<Rect>) {} + + override fun addDarkReceiver(receiver: DarkIconDispatcher.DarkReceiver) { + receivers.add(receiver) + } + + override fun removeDarkReceiver(receiver: DarkIconDispatcher.DarkReceiver) { + receivers.remove(receiver) + } + + override fun applyDark(`object`: DarkIconDispatcher.DarkReceiver) {} +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt index 9ff7dd590781..ffe6918a56f8 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt @@ -27,6 +27,9 @@ class FakeFgsManagerController( numRunningPackages: Int = 0, ) : FgsManagerController { + var initialized = false + private set + override var numRunningPackages = numRunningPackages set(value) { if (value != field) { @@ -53,7 +56,9 @@ class FakeFgsManagerController( dialogDismissedListeners.forEach { it.onDialogDismissed() } } - override fun init() {} + override fun init() { + initialized = true + } override fun showDialog(expandable: Expandable?) {} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt new file mode 100644 index 000000000000..d37d8f39b9ee --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.composefragment.viewmodel + +import android.content.res.mainResources +import androidx.lifecycle.LifecycleCoroutineScope +import com.android.systemui.common.ui.domain.interactor.configurationInteractor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.footerActionsController +import com.android.systemui.qs.footerActionsViewModelFactory +import com.android.systemui.qs.ui.viewmodel.quickSettingsContainerViewModel +import com.android.systemui.shade.largeScreenHeaderHelper +import com.android.systemui.shade.transition.largeScreenShadeInterpolator +import com.android.systemui.statusbar.disableflags.data.repository.disableFlagsRepository +import com.android.systemui.statusbar.phone.keyguardBypassController +import com.android.systemui.statusbar.sysuiStatusBarStateController + +val Kosmos.qsFragmentComposeViewModelFactory by + Kosmos.Fixture { + object : QSFragmentComposeViewModel.Factory { + override fun create( + lifecycleScope: LifecycleCoroutineScope + ): QSFragmentComposeViewModel { + return QSFragmentComposeViewModel( + quickSettingsContainerViewModel, + mainResources, + footerActionsViewModelFactory, + footerActionsController, + sysuiStatusBarStateController, + keyguardBypassController, + disableFlagsRepository, + largeScreenShadeInterpolator, + configurationInteractor, + largeScreenHeaderHelper, + lifecycleScope, + ) + } + } + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shared/system/TaskStackChangeListenersKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shared/system/TaskStackChangeListenersKosmos.kt new file mode 100644 index 000000000000..67f611a040cf --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shared/system/TaskStackChangeListenersKosmos.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.shared.system + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.taskStackChangeListeners: TaskStackChangeListeners by + Kosmos.Fixture { TaskStackChangeListeners.getTestInstance() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt index 99bb47976c87..932e768676cb 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt @@ -33,6 +33,7 @@ var Kosmos.modesDialogDelegate: ModesDialogDelegate by dialogTransitionAnimator, activityStarter, { modesDialogViewModel }, + modesDialogEventLogger, mainCoroutineContext, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLoggerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLoggerKosmos.kt new file mode 100644 index 000000000000..24e7a872d641 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLoggerKosmos.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.policy.ui.dialog + +import com.android.internal.logging.uiEventLogger +import com.android.systemui.kosmos.Kosmos +import org.mockito.kotlin.mock + +var Kosmos.modesDialogEventLogger by Kosmos.Fixture { ModesDialogEventLogger(uiEventLogger) } +var Kosmos.mockModesDialogEventLogger by Kosmos.Fixture { mock<ModesDialogEventLogger>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLoggerTest.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLoggerTest.kt new file mode 100644 index 000000000000..5146f77bbcf6 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLoggerTest.kt @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.policy.ui.dialog + +import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.internal.logging.testing.UiEventLoggerFake +import com.android.settingslib.notification.modes.TestModeBuilder +import com.android.systemui.SysuiTestCase +import com.android.systemui.qs.QSModesEvent +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +@TestableLooper.RunWithLooper +class ModesDialogEventLoggerTest : SysuiTestCase() { + + private val uiEventLogger = UiEventLoggerFake() + private val underTest = ModesDialogEventLogger(uiEventLogger) + + @Test + fun testLogModeOn_manual() { + underTest.logModeOn(TestModeBuilder.MANUAL_DND_INACTIVE) + + assertThat(uiEventLogger.numLogs()).isEqualTo(1) + uiEventLogger[0].match(QSModesEvent.QS_MODES_DND_ON, "android") + } + + @Test + fun testLogModeOff_manual() { + underTest.logModeOff(TestModeBuilder.MANUAL_DND_ACTIVE) + + assertThat(uiEventLogger.numLogs()).isEqualTo(1) + uiEventLogger[0].match(QSModesEvent.QS_MODES_DND_OFF, "android") + } + + @Test + fun testLogModeSettings_manual() { + underTest.logModeSettings(TestModeBuilder.MANUAL_DND_ACTIVE) + + assertThat(uiEventLogger.numLogs()).isEqualTo(1) + uiEventLogger[0].match(QSModesEvent.QS_MODES_DND_SETTINGS, "android") + } + + @Test + fun testLogModeOn_automatic() { + underTest.logModeOn(TestModeBuilder().setActive(true).setPackage("pkg1").build()) + + assertThat(uiEventLogger.numLogs()).isEqualTo(1) + uiEventLogger[0].match(QSModesEvent.QS_MODES_MODE_ON, "pkg1") + } + + @Test + fun testLogModeOff_automatic() { + underTest.logModeOff(TestModeBuilder().setActive(false).setPackage("pkg2").build()) + + assertThat(uiEventLogger.numLogs()).isEqualTo(1) + uiEventLogger[0].match(QSModesEvent.QS_MODES_MODE_OFF, "pkg2") + } + + @Test + fun testLogModeSettings_automatic() { + underTest.logModeSettings(TestModeBuilder().setPackage("pkg3").build()) + + assertThat(uiEventLogger.numLogs()).isEqualTo(1) + uiEventLogger[0].match(QSModesEvent.QS_MODES_MODE_SETTINGS, "pkg3") + } + + @Test + fun testLogOpenDurationDialog_manual() { + underTest.logOpenDurationDialog(TestModeBuilder.MANUAL_DND_INACTIVE) + + assertThat(uiEventLogger.numLogs()).isEqualTo(1) + // package not logged for duration dialog as it only applies to manual mode + uiEventLogger[0].match(QSModesEvent.QS_MODES_DURATION_DIALOG, null) + } + + @Test + fun testLogOpenDurationDialog_automatic_doesNotLog() { + underTest.logOpenDurationDialog( + TestModeBuilder().setActive(false).setPackage("mypkg").build() + ) + + // ignore calls to open dialog on something other than the manual rule (shouldn't happen) + assertThat(uiEventLogger.numLogs()).isEqualTo(0) + } + + @Test + fun testLogDialogSettings() { + underTest.logDialogSettings() + assertThat(uiEventLogger.numLogs()).isEqualTo(1) + uiEventLogger[0].match(QSModesEvent.QS_MODES_SETTINGS, null) + } + + private fun UiEventLoggerFake.FakeUiEvent.match(event: QSModesEvent, modePackage: String?) { + assertThat(eventId).isEqualTo(event.id) + assertThat(packageName).isEqualTo(modePackage) + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelKosmos.kt index 00020f8bb391..3571a737704b 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelKosmos.kt @@ -21,6 +21,7 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testDispatcher import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor import com.android.systemui.statusbar.policy.ui.dialog.modesDialogDelegate +import com.android.systemui.statusbar.policy.ui.dialog.modesDialogEventLogger import javax.inject.Provider val Kosmos.modesDialogViewModel: ModesDialogViewModel by @@ -30,5 +31,6 @@ val Kosmos.modesDialogViewModel: ModesDialogViewModel by zenModeInteractor, testDispatcher, Provider { modesDialogDelegate }.get(), + modesDialogEventLogger, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt index 295e150e00d8..2e1ecfd3666e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt @@ -219,7 +219,7 @@ inline fun <reified T : Any> kotlinArgumentCaptor(): KotlinArgumentCaptor<T> = * * NOTE: this uses the KotlinArgumentCaptor to avoid the NullPointerException. */ -@Deprecated("Replace with mockito-kotlin", level = WARNING) +// TODO(359670968): rewrite this to use mockito-kotlin inline fun <reified T : Any> withArgCaptor(block: KotlinArgumentCaptor<T>.() -> Unit): T = kotlinArgumentCaptor<T>().apply { block() }.value diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/OWNERS b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/OWNERS new file mode 100644 index 000000000000..1f07df9f1e8e --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/OWNERS @@ -0,0 +1 @@ +include /packages/SystemUI/src/com/android/systemui/volume/OWNERS
\ No newline at end of file diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioSharingRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioSharingRepository.kt index 0a617d17b033..a4719e5a2492 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioSharingRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioSharingRepository.kt @@ -18,7 +18,6 @@ package com.android.systemui.volume.data.repository import com.android.settingslib.volume.data.repository.AudioSharingRepository import com.android.settingslib.volume.data.repository.GroupIdToVolumes -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -30,7 +29,7 @@ class FakeAudioSharingRepository : AudioSharingRepository { MutableStateFlow(TEST_GROUP_ID_INVALID) private val mutableVolumeMap: MutableStateFlow<GroupIdToVolumes> = MutableStateFlow(emptyMap()) - override val inAudioSharing: Flow<Boolean> = mutableInAudioSharing + override val inAudioSharing: StateFlow<Boolean> = mutableInAudioSharing override val primaryGroupId: StateFlow<Int> = mutablePrimaryGroupId override val secondaryGroupId: StateFlow<Int> = mutableSecondaryGroupId override val volumeMap: StateFlow<GroupIdToVolumes> = mutableVolumeMap diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorKosmos.kt index e2d414e23abd..3ac565aeacf9 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorKosmos.kt @@ -22,7 +22,6 @@ import com.android.systemui.bluetooth.localBluetoothManager import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.volume.data.repository.audioRepository -import com.android.systemui.volume.data.repository.audioSharingRepository import com.android.systemui.volume.mediaOutputInteractor val Kosmos.audioOutputInteractor by @@ -37,6 +36,5 @@ val Kosmos.audioOutputInteractor by bluetoothAdapter, deviceIconInteractor, mediaOutputInteractor, - audioSharingRepository, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorKosmos.kt index 9f11822adc0c..63a132565177 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorKosmos.kt @@ -20,6 +20,7 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.volume.domain.interactor.audioModeInteractor import com.android.systemui.volume.domain.interactor.audioOutputInteractor +import com.android.systemui.volume.domain.interactor.audioSharingInteractor import com.android.systemui.volume.mediaDeviceSessionInteractor import com.android.systemui.volume.mediaOutputInteractor @@ -31,5 +32,6 @@ val Kosmos.mediaOutputComponentInteractor by audioOutputInteractor, audioModeInteractor, mediaOutputInteractor, + audioSharingInteractor, ) } diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java index 2c4bc7cb0d47..531fa4546bf9 100644 --- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java +++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java @@ -78,8 +78,8 @@ import android.util.Range; import android.util.Size; import android.view.Surface; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; +import android.annotation.NonNull; +import android.annotation.Nullable; import androidx.camera.extensions.impl.AutoImageCaptureExtenderImpl; import androidx.camera.extensions.impl.AutoPreviewExtenderImpl; import androidx.camera.extensions.impl.BeautyImageCaptureExtenderImpl; diff --git a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt index cc9b70e387e8..639ebab4515b 100644 --- a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt +++ b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt @@ -311,6 +311,7 @@ com.android.internal.util.ProcFileReader com.android.internal.util.QuickSelect com.android.internal.util.RingBuffer com.android.internal.util.SizedInputStream +com.android.internal.util.RateLimitingCache com.android.internal.util.StringPool com.android.internal.util.TokenBucket com.android.internal.util.XmlPullParserWrapper diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java index b052d23971ab..5c6f99a3e5a9 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java @@ -16,8 +16,6 @@ package com.android.server.accessibility.magnification; -import static android.view.InputDevice.SOURCE_MOUSE; -import static android.view.InputDevice.SOURCE_STYLUS; import static android.view.InputDevice.SOURCE_TOUCHSCREEN; import static android.view.MotionEvent.ACTION_CANCEL; import static android.view.MotionEvent.ACTION_DOWN; @@ -342,8 +340,12 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH cancelFling(); } handleTouchEventWith(mCurrentState, event, rawEvent, policyFlags); - } else if (Flags.enableMagnificationFollowsMouse() - && (event.getSource() == SOURCE_MOUSE || event.getSource() == SOURCE_STYLUS)) { + } + } + + @Override + void handleMouseOrStylusEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + if (Flags.enableMagnificationFollowsMouse()) { if (mFullScreenMagnificationController.isActivated(mDisplayId)) { // TODO(b/354696546): Allow mouse/stylus to activate whichever display they are // over, rather than only interacting with the current display. @@ -351,8 +353,6 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH // Send through the mouse/stylus event handler. mMouseEventHandler.onEvent(event, mDisplayId); } - // Dispatch to normal event handling flow. - dispatchTransformedEvent(event, rawEvent, policyFlags); } } diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java index 08411c22b943..446123f07f64 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java @@ -127,49 +127,41 @@ public abstract class MagnificationGestureHandler extends BaseEventStreamTransfo if (DEBUG_EVENT_STREAM) { storeEventInto(mDebugInputEventHistory, event); } - if (shouldDispatchTransformedEvent(event)) { - dispatchTransformedEvent(event, rawEvent, policyFlags); - } else { - onMotionEventInternal(event, rawEvent, policyFlags); - - final int action = event.getAction(); - if (action == MotionEvent.ACTION_DOWN) { - mCallback.onTouchInteractionStart(mDisplayId, getMode()); - } else if (action == ACTION_UP || action == ACTION_CANCEL) { - mCallback.onTouchInteractionEnd(mDisplayId, getMode()); + switch (event.getSource()) { + case SOURCE_TOUCHSCREEN: { + if (magnificationShortcutExists()) { + // Observe touchscreen events while magnification activation is detected. + onMotionEventInternal(event, rawEvent, policyFlags); + + final int action = event.getAction(); + if (action == MotionEvent.ACTION_DOWN) { + mCallback.onTouchInteractionStart(mDisplayId, getMode()); + } else if (action == ACTION_UP || action == ACTION_CANCEL) { + mCallback.onTouchInteractionEnd(mDisplayId, getMode()); + } + // Return early: Do not dispatch event through normal eventing + // flow, it has been fully consumed by the magnifier. + return; + } + } break; + case SOURCE_MOUSE: + case SOURCE_STYLUS: { + if (magnificationShortcutExists() && Flags.enableMagnificationFollowsMouse()) { + handleMouseOrStylusEvent(event, rawEvent, policyFlags); + } } + break; + default: + break; } + // Dispatch event through normal eventing flow. + dispatchTransformedEvent(event, rawEvent, policyFlags); } - /** - * Some touchscreen, mouse and stylus events may modify magnifier state. Checks for whether the - * event should not be dispatched to the magnifier. - * - * @param event The event to check. - * @return `true` if the event should be sent through the normal event flow or `false` if it - * should be observed by magnifier. - */ - private boolean shouldDispatchTransformedEvent(MotionEvent event) { - if (event.getSource() == SOURCE_TOUCHSCREEN) { - if (mDetectSingleFingerTripleTap - || mDetectTwoFingerTripleTap - || mDetectShortcutTrigger) { - // Observe touchscreen events while magnification activation is detected. - return false; - } - } - if (Flags.enableMagnificationFollowsMouse()) { - if (event.isFromSource(SOURCE_MOUSE) || event.isFromSource(SOURCE_STYLUS)) { - // Note that mouse events include other mouse-like pointing devices - // such as touchpads and pointing sticks. - // Observe any mouse or stylus movement. - // We observe all movement to ensure that events continue to come in order, - // even though only some movement types actually move the viewport. - return false; - } - } - // Magnification dispatches (ignores) all other events - return true; + private boolean magnificationShortcutExists() { + return (mDetectSingleFingerTripleTap + || mDetectTwoFingerTripleTap + || mDetectShortcutTrigger); } final void dispatchTransformedEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { @@ -202,6 +194,13 @@ public abstract class MagnificationGestureHandler extends BaseEventStreamTransfo abstract void onMotionEventInternal(MotionEvent event, MotionEvent rawEvent, int policyFlags); /** + * Called when this MagnificationGestureHandler should handle a mouse or stylus motion event, + * but not re-dispatch it when completed. + */ + abstract void handleMouseOrStylusEvent( + MotionEvent event, MotionEvent rawEvent, int policyFlags); + + /** * Called when the shortcut target is magnification. */ public void notifyShortcutTriggered() { diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java index 1818cddbcf4c..a84140419166 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java @@ -147,9 +147,13 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl } @Override + void handleMouseOrStylusEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + // Window Magnification viewport doesn't move with mouse events (yet). + } + + @Override void onMotionEventInternal(MotionEvent event, MotionEvent rawEvent, int policyFlags) { if (event.getSource() != SOURCE_TOUCHSCREEN) { - // Window Magnification viewport doesn't move with mouse events (yet). return; } // To keep InputEventConsistencyVerifiers within GestureDetectors happy. diff --git a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java index 930af5e7f056..5044e93b293d 100644 --- a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java +++ b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java @@ -71,6 +71,7 @@ import android.util.ArraySet; import android.util.Slog; import android.view.autofill.AutofillId; import android.view.autofill.AutofillManager; +import android.view.autofill.AutofillValue; import com.android.internal.util.FrameworkStatsLog; @@ -244,10 +245,18 @@ public final class PresentationStatsEventLogger { Slog.e(TAG, "Failed to start new event because already have active event."); return; } + Slog.d(TAG, "Started new PresentationStatsEvent"); mEventInternal = Optional.of(new PresentationStatsEventInternal()); } /** + * Test use only, returns a copy of the events object + */ + Optional<PresentationStatsEventInternal> getInternalEvent() { + return mEventInternal; + } + + /** * Set request_id */ public void maybeSetRequestId(int requestId) { @@ -339,10 +348,16 @@ public final class PresentationStatsEventLogger { }); } - public void maybeSetCountShown(int datasets) { + /** + * This is called when a dataset is shown to the user. Will set the count shown, + * related timestamps and presentation reason. + */ + public void logWhenDatasetShown(int datasets) { mEventInternal.ifPresent( event -> { + maybeSetSuggestionPresentedTimestampMs(); event.mCountShown = datasets; + event.mNoPresentationReason = NOT_SHOWN_REASON_ANY_SHOWN; }); } @@ -405,7 +420,12 @@ public final class PresentationStatsEventLogger { public void maybeSetDisplayPresentationType(@UiType int uiType) { mEventInternal.ifPresent(event -> { - event.mDisplayPresentationType = getDisplayPresentationType(uiType); + // There are cases in which another UI type will show up after selects a dataset + // such as with Inline after Fill Dialog. Set as the first presentation type only. + if (event.mDisplayPresentationType + == AUTOFILL_PRESENTATION_EVENT_REPORTED__DISPLAY_PRESENTATION_TYPE__UNKNOWN_AUTOFILL_DISPLAY_PRESENTATION_TYPE) { + event.mDisplayPresentationType = getDisplayPresentationType(uiType); + } }); } @@ -430,9 +450,12 @@ public final class PresentationStatsEventLogger { } public void maybeSetSuggestionSentTimestampMs(int timestamp) { - mEventInternal.ifPresent(event -> { - event.mSuggestionSentTimestampMs = timestamp; - }); + mEventInternal.ifPresent( + event -> { + if (event.mSuggestionSentTimestampMs == DEFAULT_VALUE_INT) { + event.mSuggestionSentTimestampMs = timestamp; + } + }); } public void maybeSetSuggestionSentTimestampMs() { @@ -481,8 +504,6 @@ public final class PresentationStatsEventLogger { public void maybeSetInlinePresentationAndSuggestionHostUid(Context context, int userId) { mEventInternal.ifPresent(event -> { - event.mDisplayPresentationType = - AUTOFILL_PRESENTATION_EVENT_REPORTED__DISPLAY_PRESENTATION_TYPE__INLINE; String imeString = Settings.Secure.getStringForUser(context.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD, userId); if (TextUtils.isEmpty(imeString)) { @@ -602,40 +623,56 @@ public final class PresentationStatsEventLogger { } /** + * Sets the field length whenever the text changes. Will keep track of the first + * and last modification lengths. + */ + public void updateTextFieldLength(AutofillValue value) { + mEventInternal.ifPresent(event -> { + if (value == null || !value.isText()) { + return; + } + + int length = value.getTextValue().length(); + + if (event.mFieldFirstLength == DEFAULT_VALUE_INT) { + event.mFieldFirstLength = length; + } + event.mFieldLastLength = length; + }); + } + + /** * Set various timestamps whenever the ViewState is modified * * <p>If the ViewState contains ViewState.STATE_AUTOFILLED, sets field_autofilled_timestamp_ms * else, set field_first_modified_timestamp_ms (if unset) and field_last_modified_timestamp_ms */ - public void onFieldTextUpdated(ViewState state, int length) { + public void onFieldTextUpdated(ViewState state, AutofillValue value) { mEventInternal.ifPresent(event -> { - int timestamp = getElapsedTime(); - // Focused id should be set before this is called - if (state == null || state.id == null || state.id.getViewId() != event.mFocusedId) { - // if these don't match, the currently field different than before - Slog.w( - TAG, - "Bad view state for: " + event.mFocusedId); - return; - } + int timestamp = getElapsedTime(); + // Focused id should be set before this is called + if (state == null || state.id == null || state.id.getViewId() != event.mFocusedId) { + // if these don't match, the currently field different than before + Slog.w( + TAG, + "Bad view state for: " + event.mFocusedId + ", state: " + state); + return; + } - // Text changed because filling into form, just log Autofill timestamp - if ((state.getState() & ViewState.STATE_AUTOFILLED) != 0) { - event.mAutofilledTimestampMs = timestamp; - return; - } + updateTextFieldLength(value); - // Set length variables - if (event.mFieldFirstLength == DEFAULT_VALUE_INT) { - event.mFieldFirstLength = length; - } - event.mFieldLastLength = length; + // Text changed because filling into form, just log Autofill timestamp + if ((state.getState() & ViewState.STATE_AUTOFILLED) != 0) { + event.mAutofilledTimestampMs = timestamp; + return; + } - // Set timestamp variables - if (event.mFieldModifiedFirstTimestampMs == DEFAULT_VALUE_INT) { - event.mFieldModifiedFirstTimestampMs = timestamp; - } - event.mFieldModifiedLastTimestampMs = timestamp; + + // Set timestamp variables + if (event.mFieldModifiedFirstTimestampMs == DEFAULT_VALUE_INT) { + event.mFieldModifiedFirstTimestampMs = timestamp; + } + event.mFieldModifiedLastTimestampMs = timestamp; }); } @@ -796,7 +833,10 @@ public final class PresentationStatsEventLogger { }); } - public void logAndEndEvent() { + /** + * Finish and log the event. + */ + public void logAndEndEvent(String caller) { if (!mEventInternal.isPresent()) { Slog.w(TAG, "Shouldn't be logging AutofillPresentationEventReported again for same " + "event"); @@ -804,7 +844,8 @@ public final class PresentationStatsEventLogger { } PresentationStatsEventInternal event = mEventInternal.get(); if (sVerbose) { - Slog.v(TAG, "Log AutofillPresentationEventReported:" + Slog.v(TAG, "(" + caller + ") " + + "Log AutofillPresentationEventReported:" + " requestId=" + event.mRequestId + " sessionId=" + mSessionId + " mNoPresentationEventReason=" + event.mNoPresentationReason @@ -926,7 +967,7 @@ public final class PresentationStatsEventLogger { mEventInternal = Optional.empty(); } - private static final class PresentationStatsEventInternal { + static final class PresentationStatsEventInternal { int mRequestId; @NotShownReason int mNoPresentationReason = NOT_SHOWN_REASON_UNKNOWN; boolean mIsDatasetAvailable; diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 6dea8b052173..c75fd0b7e025 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -28,7 +28,6 @@ import static android.service.autofill.Dataset.PICK_REASON_PROVIDER_DETECTION_ON import static android.service.autofill.Dataset.PICK_REASON_PROVIDER_DETECTION_PREFERRED_WITH_PCC; import static android.service.autofill.Dataset.PICK_REASON_UNKNOWN; import static android.service.autofill.FillEventHistory.Event.UI_TYPE_CREDMAN_BOTTOM_SHEET; -import static android.service.autofill.FillEventHistory.Event.UI_TYPE_DIALOG; import static android.service.autofill.FillEventHistory.Event.UI_TYPE_INLINE; import static android.service.autofill.FillEventHistory.Event.UI_TYPE_UNKNOWN; import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST; @@ -67,10 +66,10 @@ import static com.android.server.autofill.FillResponseEventLogger.RESPONSE_STATU import static com.android.server.autofill.FillResponseEventLogger.RESPONSE_STATUS_SUCCESS; import static com.android.server.autofill.FillResponseEventLogger.RESPONSE_STATUS_TIMEOUT; import static com.android.server.autofill.FillResponseEventLogger.RESPONSE_STATUS_TRANSACTION_TOO_LARGE; +import static com.android.server.autofill.Helper.SaveInfoStats; import static com.android.server.autofill.Helper.containsCharsInOrder; import static com.android.server.autofill.Helper.createSanitizers; import static com.android.server.autofill.Helper.getNumericValue; -import static com.android.server.autofill.Helper.SaveInfoStats; import static com.android.server.autofill.Helper.sDebug; import static com.android.server.autofill.Helper.sVerbose; import static com.android.server.autofill.Helper.toArray; @@ -78,6 +77,7 @@ import static com.android.server.autofill.PresentationStatsEventLogger.AUTHENTIC import static com.android.server.autofill.PresentationStatsEventLogger.AUTHENTICATION_RESULT_SUCCESS; import static com.android.server.autofill.PresentationStatsEventLogger.AUTHENTICATION_TYPE_DATASET_AUTHENTICATION; import static com.android.server.autofill.PresentationStatsEventLogger.AUTHENTICATION_TYPE_FULL_AUTHENTICATION; +import static com.android.server.autofill.PresentationStatsEventLogger.NOT_SHOWN_REASON_ANY_SHOWN; import static com.android.server.autofill.PresentationStatsEventLogger.NOT_SHOWN_REASON_NO_FOCUS; import static com.android.server.autofill.PresentationStatsEventLogger.NOT_SHOWN_REASON_REQUEST_FAILED; import static com.android.server.autofill.PresentationStatsEventLogger.NOT_SHOWN_REASON_REQUEST_TIMEOUT; @@ -1288,11 +1288,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * Clears the existing response for the partition, reads a new structure, and then requests a * new fill response from the fill service. * - * <p> Also asks the IME to make an inline suggestions request if it's enabled. + * <p>Also asks the IME to make an inline suggestions request if it's enabled. */ @GuardedBy("mLock") - private void requestNewFillResponseLocked(@NonNull ViewState viewState, int newState, - int flags) { + private Optional<Integer> requestNewFillResponseLocked( + @NonNull ViewState viewState, int newState, int flags) { boolean isSecondary = shouldRequestSecondaryProvider(flags); final FillResponse existingResponse = isSecondary ? viewState.getSecondaryResponse() : viewState.getResponse(); @@ -1333,7 +1333,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mFillRequestEventLogger.maybeSetIsAugmented(true); mFillRequestEventLogger.logAndEndEvent(); triggerAugmentedAutofillLocked(flags); - return; + return Optional.empty(); } viewState.setState(newState); @@ -1353,11 +1353,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState + ", flags=" + flags); } boolean isCredmanRequested = (flags & FLAG_VIEW_REQUESTS_CREDMAN_SERVICE) != 0; - mPresentationStatsEventLogger.maybeSetRequestId(requestId); - mPresentationStatsEventLogger.maybeSetIsCredentialRequest(isCredmanRequested); - mPresentationStatsEventLogger.maybeSetFieldClassificationRequestId( - mFieldClassificationIdSnapshot); - mPresentationStatsEventLogger.maybeSetAutofillServiceUid(getAutofillServiceUid()); mFillRequestEventLogger.maybeSetRequestId(requestId); mFillRequestEventLogger.maybeSetAutofillServiceUid(getAutofillServiceUid()); mSaveEventLogger.maybeSetAutofillServiceUid(getAutofillServiceUid()); @@ -1417,6 +1412,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // Now request the assist structure data. requestAssistStructureLocked(requestId, flags); + + return Optional.of(requestId); } private boolean isRequestSupportFillDialog(int flags) { @@ -1662,6 +1659,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final LogMaker requestLog; synchronized (mLock) { + mPresentationStatsEventLogger.maybeSetRequestId(requestId); // Start a new FillResponse logger for the success case. mFillResponseEventLogger.startLogForNewResponse(); mFillResponseEventLogger.maybeSetRequestId(requestId); @@ -2419,7 +2417,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState NOT_SHOWN_REASON_REQUEST_FAILED); mFillResponseEventLogger.maybeSetResponseStatus(RESPONSE_STATUS_FAILURE); } - mPresentationStatsEventLogger.logAndEndEvent(); + mPresentationStatsEventLogger.logAndEndEvent("fill request failure"); mFillResponseEventLogger.maybeSetLatencyResponseProcessingMillis(); mFillResponseEventLogger.logAndEndEvent(); } @@ -2642,6 +2640,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState public void onShown(int uiType, int numDatasetsShown) { synchronized (mLock) { mPresentationStatsEventLogger.maybeSetDisplayPresentationType(uiType); + mPresentationStatsEventLogger.maybeSetNoPresentationEventReason( + NOT_SHOWN_REASON_ANY_SHOWN); if (uiType == UI_TYPE_INLINE) { // Inline Suggestions are inflated one at a time @@ -2657,7 +2657,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } mLoggedInlineDatasetShown = true; } else { - mPresentationStatsEventLogger.maybeSetCountShown(numDatasetsShown); + mPresentationStatsEventLogger.logWhenDatasetShown(numDatasetsShown); // Explicitly sets maybeSetSuggestionPresentedTimestampMs mPresentationStatsEventLogger.maybeSetSuggestionPresentedTimestampMs(); mService.logDatasetShown(this.id, mClientState, uiType); @@ -2800,7 +2800,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (mCurrentViewId == null) { return; } + mPresentationStatsEventLogger.logAndEndEvent("fallback from fill dialog"); + startNewEventForPresentationStatsEventLogger(); final ViewState currentView = mViewStates.get(mCurrentViewId); + logPresentationStatsOnViewEnteredLocked(currentView.getResponse(), false); currentView.maybeCallOnFillReady(mFlags); } } @@ -2850,7 +2853,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (requestId == AUGMENTED_AUTOFILL_REQUEST_ID) { setAuthenticationResultForAugmentedAutofillLocked(data, authenticationId); // Augmented autofill is not logged. - mPresentationStatsEventLogger.logAndEndEvent(); + mPresentationStatsEventLogger.logAndEndEvent("authentication - augmented"); return; } if (mResponses == null) { @@ -2859,7 +2862,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState Slog.w(TAG, "setAuthenticationResultLocked(" + authenticationId + "): no responses"); mPresentationStatsEventLogger.maybeSetAuthenticationResult( AUTHENTICATION_RESULT_FAILURE); - mPresentationStatsEventLogger.logAndEndEvent(); + mPresentationStatsEventLogger.logAndEndEvent("authentication - no response"); removeFromService(); return; } @@ -2870,7 +2873,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState Slog.w(TAG, "no authenticated response"); mPresentationStatsEventLogger.maybeSetAuthenticationResult( AUTHENTICATION_RESULT_FAILURE); - mPresentationStatsEventLogger.logAndEndEvent(); + mPresentationStatsEventLogger.logAndEndEvent("authentication - bad response"); removeFromService(); return; } @@ -2885,7 +2888,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState Slog.w(TAG, "no dataset with index " + datasetIdx + " on fill response"); mPresentationStatsEventLogger.maybeSetAuthenticationResult( AUTHENTICATION_RESULT_FAILURE); - mPresentationStatsEventLogger.logAndEndEvent(); + mPresentationStatsEventLogger.logAndEndEvent("authentication - no datasets"); removeFromService(); return; } @@ -3330,7 +3333,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mPresentationStatsEventLogger.maybeSetNoPresentationEventReason( PresentationStatsEventLogger.getNoPresentationEventReason(commitReason)); - mPresentationStatsEventLogger.logAndEndEvent(); + mPresentationStatsEventLogger.logAndEndEvent("Context committed"); final int flags = lastResponse.getFlags(); if ((flags & FillResponse.FLAG_TRACK_CONTEXT_COMMITED) == 0) { @@ -4299,6 +4302,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * Starts (if necessary) a new fill request upon entering a view. * * <p>A new request will be started in 2 scenarios: + * * <ol> * <li>If the user manually requested autofill. * <li>If the view is part of a new partition. @@ -4307,18 +4311,17 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * @param id The id of the view that is entered. * @param viewState The view that is entered. * @param flags The flag that was passed by the AutofillManager. - * * @return {@code true} if a new fill response is requested. */ @GuardedBy("mLock") - private boolean requestNewFillResponseOnViewEnteredIfNecessaryLocked(@NonNull AutofillId id, - @NonNull ViewState viewState, int flags) { + private Optional<Integer> requestNewFillResponseOnViewEnteredIfNecessaryLocked( + @NonNull AutofillId id, @NonNull ViewState viewState, int flags) { // Force new response for manual request if ((flags & FLAG_MANUAL_REQUEST) != 0) { mSessionFlags.mAugmentedAutofillOnly = false; if (sDebug) Slog.d(TAG, "Re-starting session on view " + id + " and flags " + flags); - requestNewFillResponseLocked(viewState, ViewState.STATE_RESTARTED_SESSION, flags); - return true; + return requestNewFillResponseLocked( + viewState, ViewState.STATE_RESTARTED_SESSION, flags); } // If it's not, then check if it should start a partition. @@ -4331,15 +4334,15 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // Sometimes activity contain IMPORTANT_FOR_AUTOFILL_NO fields which marks session as // augmentedOnly, but other fields are still fillable by standard autofill. mSessionFlags.mAugmentedAutofillOnly = false; - requestNewFillResponseLocked(viewState, ViewState.STATE_STARTED_PARTITION, flags); - return true; + return requestNewFillResponseLocked( + viewState, ViewState.STATE_STARTED_PARTITION, flags); } if (sVerbose) { Slog.v(TAG, "Not starting new partition for view " + id + ": " + viewState.getStateAsString()); } - return false; + return Optional.empty(); } /** @@ -4428,31 +4431,32 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState void updateLocked(AutofillId id, Rect virtualBounds, AutofillValue value, int action, int flags) { if (mDestroyed) { - Slog.w(TAG, "Call to Session#updateLocked() rejected - session: " - + id + " destroyed"); + Slog.w(TAG, "updateLocked(" + id + "): rejected - session: destroyed"); return; } if (action == ACTION_RESPONSE_EXPIRED) { mSessionFlags.mExpiredResponse = true; if (sDebug) { - Slog.d(TAG, "Set the response has expired."); + Slog.d(TAG, "updateLocked(" + id + "): Set the response has expired."); } mPresentationStatsEventLogger.maybeSetNoPresentationEventReasonIfNoReasonExists( NOT_SHOWN_REASON_VIEW_CHANGED); - mPresentationStatsEventLogger.logAndEndEvent(); + mPresentationStatsEventLogger.logAndEndEvent("ACTION_RESPONSE_EXPIRED"); return; } id.setSessionId(this.id); - if (sVerbose) { - Slog.v(TAG, "updateLocked(" + this.id + "): id=" + id + ", action=" - + actionAsString(action) + ", flags=" + flags); - } ViewState viewState = mViewStates.get(id); if (sVerbose) { - Slog.v(TAG, "updateLocked(" + this.id + "): mCurrentViewId=" + mCurrentViewId - + ", mExpiredResponse=" + mSessionFlags.mExpiredResponse - + ", viewState=" + viewState); + Slog.v( + TAG, + "updateLocked(" + id + "): " + + "id=" + this.id + + ", action=" + actionAsString(action) + + ", flags=" + flags + + ", mCurrentViewId=" + mCurrentViewId + + ", mExpiredResponse=" + mSessionFlags.mExpiredResponse + + ", viewState=" + viewState); } if (viewState == null) { @@ -4505,14 +4509,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mSessionFlags.mFillDialogDisabled = true; mPreviouslyFillDialogPotentiallyStarted = false; } else { - // Set the default reason for now if the user doesn't trigger any focus event - // on the autofillable view. This can be changed downstream when more - // information is available or session is committed. - mPresentationStatsEventLogger.maybeSetNoPresentationEventReason( - NOT_SHOWN_REASON_NO_FOCUS); mPreviouslyFillDialogPotentiallyStarted = true; } - requestNewFillResponseLocked(viewState, ViewState.STATE_STARTED_SESSION, flags); + Optional<Integer> maybeRequestId = + requestNewFillResponseLocked( + viewState, ViewState.STATE_STARTED_SESSION, flags); + if (maybeRequestId.isPresent()) { + mPresentationStatsEventLogger.maybeSetRequestId(maybeRequestId.get()); + } break; case ACTION_VALUE_CHANGED: if (mCompatMode && (viewState.getState() & ViewState.STATE_URL_BAR) != 0) { @@ -4577,8 +4581,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } boolean isCredmanRequested = (flags & FLAG_VIEW_REQUESTS_CREDMAN_SERVICE) != 0; if (shouldRequestSecondaryProvider(flags)) { - if (requestNewFillResponseOnViewEnteredIfNecessaryLocked( - id, viewState, flags)) { + Optional<Integer> maybeRequestIdCred = + requestNewFillResponseOnViewEnteredIfNecessaryLocked( + id, viewState, flags); + if (maybeRequestIdCred.isPresent()) { Slog.v(TAG, "Started a new fill request for secondary provider."); return; } @@ -4622,17 +4628,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mLogViewEntered = true; } - // Previously, fill request will only start whenever a view is entered. - // With Fill Dialog, request starts prior to view getting entered. So, we can't end - // the event at this moment, otherwise we will be wrongly attributing fill dialog - // event as concluded. - if (!wasPreviouslyFillDialog && !isSameViewAgain) { - // TODO(b/319872477): Re-consider this logic below - mPresentationStatsEventLogger.maybeSetNoPresentationEventReason( - NOT_SHOWN_REASON_VIEW_FOCUS_CHANGED); - mPresentationStatsEventLogger.logAndEndEvent(); - } - + // Trigger augmented autofill if applicable if ((flags & FLAG_MANUAL_REQUEST) == 0) { // Not a manual request if (mAugmentedAutofillableIds != null && mAugmentedAutofillableIds.contains( @@ -4658,26 +4654,25 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return; } } - // If previous request was FillDialog request, a logger event was already started - if (!wasPreviouslyFillDialog) { + + Optional<Integer> maybeNewRequestId = + requestNewFillResponseOnViewEnteredIfNecessaryLocked(id, viewState, flags); + + // Previously, fill request will only start whenever a view is entered. + // With Fill Dialog, request starts prior to view getting entered. So, we can't end + // the event at this moment, otherwise we will be wrongly attributing fill dialog + // event as concluded. + if (!wasPreviouslyFillDialog + && (!isSameViewEntered || maybeNewRequestId.isPresent())) { startNewEventForPresentationStatsEventLogger(); - } - if (requestNewFillResponseOnViewEnteredIfNecessaryLocked(id, viewState, flags)) { - // If a new request was issued even if previously it was fill dialog request, - // we should end the log event, and start a new one. However, it leaves us - // susceptible to race condition. But since mPresentationStatsEventLogger is - // lock guarded, we should be safe. - if (wasPreviouslyFillDialog) { - mPresentationStatsEventLogger.logAndEndEvent(); - startNewEventForPresentationStatsEventLogger(); + if (maybeNewRequestId.isPresent()) { + mPresentationStatsEventLogger.maybeSetRequestId(maybeNewRequestId.get()); } - return; } - FillResponse response = viewState.getResponse(); - if (response != null) { - logPresentationStatsOnViewEnteredLocked(response, isCredmanRequested); - } + logPresentationStatsOnViewEnteredLocked( + viewState.getResponse(), isCredmanRequested); + mPresentationStatsEventLogger.updateTextFieldLength(value); if (isSameViewEntered) { setFillDialogDisabledAndStartInput(); @@ -4719,13 +4714,17 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") private void logPresentationStatsOnViewEnteredLocked(FillResponse response, boolean isCredmanRequested) { - mPresentationStatsEventLogger.maybeSetRequestId(response.getRequestId()); mPresentationStatsEventLogger.maybeSetIsCredentialRequest(isCredmanRequested); mPresentationStatsEventLogger.maybeSetFieldClassificationRequestId( mFieldClassificationIdSnapshot); - mPresentationStatsEventLogger.maybeSetAvailableCount( - response.getDatasets(), mCurrentViewId); + mPresentationStatsEventLogger.maybeSetAutofillServiceUid(getAutofillServiceUid()); mPresentationStatsEventLogger.maybeSetFocusedId(mCurrentViewId); + + if (response != null) { + mPresentationStatsEventLogger.maybeSetRequestId(response.getRequestId()); + mPresentationStatsEventLogger.maybeSetAvailableCount( + response.getDatasets(), mCurrentViewId); + } } @GuardedBy("mLock") @@ -4796,8 +4795,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState viewState.setCurrentValue(value); final String filterText = textValue; - final AutofillValue filledValue = viewState.getAutofilledValue(); + + if (textValue != null) { + mPresentationStatsEventLogger.onFieldTextUpdated(viewState, value); + } + if (filledValue != null) { if (filledValue.equals(value)) { // When the update is caused by autofilling the view, just update the @@ -4821,9 +4824,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState currentView.maybeCallOnFillReady(flags); } } - if (textValue != null) { - mPresentationStatsEventLogger.onFieldTextUpdated(viewState, textValue.length()); - } if (viewState.id.equals(this.mCurrentViewId) && (viewState.getState() & ViewState.STATE_INLINE_SHOWN) != 0) { @@ -4888,7 +4888,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mSaveEventLogger.logAndEndEvent(); mPresentationStatsEventLogger.maybeSetNoPresentationEventReason( NOT_SHOWN_REASON_SESSION_COMMITTED_PREMATURELY); - mPresentationStatsEventLogger.logAndEndEvent(); + mPresentationStatsEventLogger.logAndEndEvent("on fill ready"); return; } } @@ -4920,7 +4920,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState synchronized (mLock) { final ViewState currentView = mViewStates.get(mCurrentViewId); currentView.setState(ViewState.STATE_FILL_DIALOG_SHOWN); - mPresentationStatsEventLogger.maybeSetDisplayPresentationType(UI_TYPE_DIALOG); } // Just show fill dialog once, so disabled after shown. // Note: Cannot disable before requestShowFillDialog() because the method @@ -6086,6 +6085,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState private void startNewEventForPresentationStatsEventLogger() { synchronized (mLock) { mPresentationStatsEventLogger.startNewEvent(); + // Set the default reason for now if the user doesn't trigger any focus event + // on the autofillable view. This can be changed downstream when more + // information is available or session is committed. + mPresentationStatsEventLogger.maybeSetNoPresentationEventReason( + NOT_SHOWN_REASON_NO_FOCUS); mPresentationStatsEventLogger.maybeSetDetectionPreference( getDetectionPreferenceForLogging()); mPresentationStatsEventLogger.maybeSetAutofillServiceUid(getAutofillServiceUid()); @@ -6724,7 +6728,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState SystemClock.elapsedRealtime() - mStartTime); mFillRequestEventLogger.logAndEndEvent(); mFillResponseEventLogger.logAndEndEvent(); - mPresentationStatsEventLogger.logAndEndEvent(); + mPresentationStatsEventLogger.logAndEndEvent("log all events"); mSaveEventLogger.logAndEndEvent(); mSessionCommittedEventLogger.logAndEndEvent(); } diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java index a10039f9bf6c..2446a6dfee89 100644 --- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java +++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java @@ -461,9 +461,9 @@ public final class AutoFillUI { } @Override - public void onShown() { + public void onShown(int datasetsShown) { if (mCallback != null) { - mCallback.onShown(UI_TYPE_DIALOG, response.getDatasets().size()); + mCallback.onShown(UI_TYPE_DIALOG, datasetsShown); } } diff --git a/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java b/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java index 5a71b895a57c..c7b6be60dd3e 100644 --- a/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java +++ b/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java @@ -85,7 +85,7 @@ final class DialogFillUi { void onDatasetPicked(@NonNull Dataset dataset); void onDismissed(); void onCanceled(); - void onShown(); + void onShown(int datasetsShown); void startIntentSender(IntentSender intentSender); } @@ -155,7 +155,8 @@ final class DialogFillUi { mDialog.setContentView(decor); setDialogParamsAsBottomSheet(); mDialog.setOnCancelListener((d) -> mCallback.onCanceled()); - mDialog.setOnShowListener((d) -> mCallback.onShown()); + int datasetsShown = (mAdapter != null) ? mAdapter.getCount() : 0; + mDialog.setOnShowListener((d) -> mCallback.onShown(datasetsShown)); show(); } diff --git a/services/contentcapture/java/com/android/server/contentprotection/OWNERS b/services/contentcapture/java/com/android/server/contentprotection/OWNERS new file mode 100644 index 000000000000..3d09da303b0f --- /dev/null +++ b/services/contentcapture/java/com/android/server/contentprotection/OWNERS @@ -0,0 +1,4 @@ +# Bug component: 1040349 + +include /core/java/android/view/contentprotection/OWNERS + diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS index a2bbff026b95..37dddc61a976 100644 --- a/services/core/java/com/android/server/OWNERS +++ b/services/core/java/com/android/server/OWNERS @@ -10,9 +10,6 @@ per-file DisplayThread.java = michaelwr@google.com, ogunwale@google.com # Zram writeback per-file ZramWriteback.java = minchan@google.com, rajekumar@google.com -# Userspace reboot -per-file UserspaceRebootLogger.java = ioffe@google.com, dvander@google.com - # ServiceWatcher per-file ServiceWatcher.java = sooniln@google.com diff --git a/services/core/java/com/android/server/UserspaceRebootLogger.java b/services/core/java/com/android/server/UserspaceRebootLogger.java deleted file mode 100644 index 89327b50883c..000000000000 --- a/services/core/java/com/android/server/UserspaceRebootLogger.java +++ /dev/null @@ -1,168 +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.server; - -import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_SHUTDOWN_SEQUENCE_ABORTED; -import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_USERDATA_REMOUNT; -import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_USERSPACE_REBOOT_WATCHDOG_TRIGGERED; -import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__OUTCOME__OUTCOME_UNKNOWN; -import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__OUTCOME__SUCCESS; -import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__USER_ENCRYPTION_STATE__LOCKED; -import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__USER_ENCRYPTION_STATE__UNLOCKED; - -import android.os.PowerManager; -import android.os.SystemClock; -import android.os.SystemProperties; -import android.text.TextUtils; -import android.util.Slog; - -import com.android.internal.util.FrameworkStatsLog; - -import java.util.concurrent.Executor; - -/** - * Utility class to help abstract logging {@code UserspaceRebootReported} atom. - */ -public final class UserspaceRebootLogger { - - private static final String TAG = "UserspaceRebootLogger"; - - private static final String USERSPACE_REBOOT_SHOULD_LOG_PROPERTY = - "persist.sys.userspace_reboot.log.should_log"; - private static final String USERSPACE_REBOOT_LAST_STARTED_PROPERTY = - "sys.userspace_reboot.log.last_started"; - private static final String USERSPACE_REBOOT_LAST_FINISHED_PROPERTY = - "sys.userspace_reboot.log.last_finished"; - private static final String LAST_BOOT_REASON_PROPERTY = "sys.boot.reason.last"; - - private UserspaceRebootLogger() {} - - /** - * Modifies internal state to note that {@code UserspaceRebootReported} atom needs to be - * logged on the next successful boot. - * - * <p>This call should only be made on devices supporting userspace reboot. - */ - public static void noteUserspaceRebootWasRequested() { - if (!PowerManager.isRebootingUserspaceSupportedImpl()) { - Slog.wtf(TAG, "noteUserspaceRebootWasRequested: Userspace reboot is not supported."); - return; - } - - SystemProperties.set(USERSPACE_REBOOT_SHOULD_LOG_PROPERTY, "1"); - SystemProperties.set(USERSPACE_REBOOT_LAST_STARTED_PROPERTY, - String.valueOf(SystemClock.elapsedRealtime())); - } - - /** - * Updates internal state on boot after successful userspace reboot. - * - * <p>Should be called right before framework sets {@code sys.boot_completed} property. - * - * <p>This call should only be made on devices supporting userspace reboot. - */ - public static void noteUserspaceRebootSuccess() { - if (!PowerManager.isRebootingUserspaceSupportedImpl()) { - Slog.wtf(TAG, "noteUserspaceRebootSuccess: Userspace reboot is not supported."); - return; - } - - SystemProperties.set(USERSPACE_REBOOT_LAST_FINISHED_PROPERTY, - String.valueOf(SystemClock.elapsedRealtime())); - } - - /** - * Returns {@code true} if {@code UserspaceRebootReported} atom should be logged. - * - * <p>On devices that do not support userspace reboot this method will always return {@code - * false}. - */ - public static boolean shouldLogUserspaceRebootEvent() { - if (!PowerManager.isRebootingUserspaceSupportedImpl()) { - return false; - } - - return SystemProperties.getBoolean(USERSPACE_REBOOT_SHOULD_LOG_PROPERTY, false); - } - - /** - * Asynchronously logs {@code UserspaceRebootReported} on the given {@code executor}. - * - * <p>Should be called in the end of {@link - * com.android.server.am.ActivityManagerService#finishBooting()} method, after framework have - * tried to proactivelly unlock storage of the primary user. - * - * <p>This call should only be made on devices supporting userspace reboot. - */ - public static void logEventAsync(boolean userUnlocked, Executor executor) { - if (!PowerManager.isRebootingUserspaceSupportedImpl()) { - Slog.wtf(TAG, "logEventAsync: Userspace reboot is not supported."); - return; - } - - final int outcome = computeOutcome(); - final long durationMillis; - if (outcome == USERSPACE_REBOOT_REPORTED__OUTCOME__SUCCESS) { - durationMillis = SystemProperties.getLong(USERSPACE_REBOOT_LAST_FINISHED_PROPERTY, 0) - - SystemProperties.getLong(USERSPACE_REBOOT_LAST_STARTED_PROPERTY, 0); - } else { - durationMillis = 0; - } - final int encryptionState = - userUnlocked - ? USERSPACE_REBOOT_REPORTED__USER_ENCRYPTION_STATE__UNLOCKED - : USERSPACE_REBOOT_REPORTED__USER_ENCRYPTION_STATE__LOCKED; - executor.execute( - () -> { - Slog.i(TAG, "Logging UserspaceRebootReported atom: { outcome: " + outcome - + " durationMillis: " + durationMillis + " encryptionState: " - + encryptionState + " }"); - FrameworkStatsLog.write(FrameworkStatsLog.USERSPACE_REBOOT_REPORTED, outcome, - durationMillis, encryptionState); - SystemProperties.set(USERSPACE_REBOOT_SHOULD_LOG_PROPERTY, ""); - }); - } - - private static int computeOutcome() { - if (SystemProperties.getLong(USERSPACE_REBOOT_LAST_STARTED_PROPERTY, -1) != -1) { - return USERSPACE_REBOOT_REPORTED__OUTCOME__SUCCESS; - } - String reason = TextUtils.emptyIfNull(SystemProperties.get(LAST_BOOT_REASON_PROPERTY, "")); - if (reason.startsWith("reboot,")) { - reason = reason.substring("reboot".length()); - } - if (reason.startsWith("userspace_failed,watchdog_fork")) { - return USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_SHUTDOWN_SEQUENCE_ABORTED; - } - if (reason.startsWith("userspace_failed,shutdown_aborted")) { - return USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_SHUTDOWN_SEQUENCE_ABORTED; - } - if (reason.startsWith("mount_userdata_failed")) { - return USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_USERDATA_REMOUNT; - } - if (reason.startsWith("userspace_failed,init_user0")) { - return USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_USERDATA_REMOUNT; - } - if (reason.startsWith("userspace_failed,enablefilecrypto")) { - return USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_USERDATA_REMOUNT; - } - if (reason.startsWith("userspace_failed,watchdog_triggered")) { - return USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_USERSPACE_REBOOT_WATCHDOG_TRIGGERED; - } - return USERSPACE_REBOOT_REPORTED__OUTCOME__OUTCOME_UNKNOWN; - } -} diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 4a18cb1f5ed8..91b549c9a04b 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -372,7 +372,6 @@ import android.os.storage.StorageManager; import android.provider.DeviceConfig; import android.provider.Settings; import android.server.ServerProtoEnums; -import android.sysprop.InitProperties; import android.system.Os; import android.system.OsConstants; import android.telephony.TelephonyManager; @@ -451,7 +450,6 @@ import com.android.server.SystemConfig; import com.android.server.SystemService; import com.android.server.SystemServiceManager; import com.android.server.ThreadPriorityBooster; -import com.android.server.UserspaceRebootLogger; import com.android.server.Watchdog; import com.android.server.am.ComponentAliasResolver.Resolution; import com.android.server.am.LowMemDetector.MemFactor; @@ -2360,20 +2358,6 @@ public class ActivityManagerService extends IActivityManager.Stub } } - private void maybeLogUserspaceRebootEvent() { - if (!UserspaceRebootLogger.shouldLogUserspaceRebootEvent()) { - return; - } - final int userId = mUserController.getCurrentUserId(); - if (userId != UserHandle.USER_SYSTEM) { - // Only log for user0. - return; - } - // TODO(b/148767783): should we check all profiles under user0? - UserspaceRebootLogger.logEventAsync(StorageManager.isCeStorageUnlocked(userId), - BackgroundThread.getExecutor()); - } - /** * Encapsulates global settings related to hidden API enforcement behaviour, including tracking * the latest value via a content observer. @@ -5323,12 +5307,6 @@ public class ActivityManagerService extends IActivityManager.Stub // Start looking for apps that are abusing wake locks. Message nmsg = mHandler.obtainMessage(CHECK_EXCESSIVE_POWER_USE_MSG); mHandler.sendMessageDelayed(nmsg, mConstants.POWER_CHECK_INTERVAL); - // Check if we are performing userspace reboot before setting sys.boot_completed to - // avoid race with init reseting sys.init.userspace_reboot.in_progress once sys - // .boot_completed is 1. - if (InitProperties.userspace_reboot_in_progress().orElse(false)) { - UserspaceRebootLogger.noteUserspaceRebootSuccess(); - } // Tell anyone interested that we are done booting! SystemProperties.set("sys.boot_completed", "1"); SystemProperties.set("dev.bootcomplete", "1"); @@ -5352,7 +5330,6 @@ public class ActivityManagerService extends IActivityManager.Stub }, mConstants.FULL_PSS_MIN_INTERVAL); } }); - maybeLogUserspaceRebootEvent(); mUserController.scheduleStartProfiles(); } // UART is on if init's console service is running, send a warning notification. diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index f61bd6040658..154b52b86e73 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -1413,6 +1413,22 @@ public class AppOpsService extends IAppOpsService.Stub { } public void uidRemoved(int uid) { + if (Flags.dontRemoveExistingUidStates()) { + // b/358365471 If apps sharing UID are installed on multiple users and only one of + // them is installed for a single user while keeping the others we observe this + // subroutine get invoked incorrectly since the UID still exists. + final long token = Binder.clearCallingIdentity(); + try { + String uidName = getPackageManagerInternal().getNameForUid(uid); + if (uidName != null) { + Slog.e(TAG, "Tried to remove existing UID. uid: " + uid + " name: " + uidName); + return; + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + synchronized (this) { if (mUidStates.indexOfKey(uid) >= 0) { mUidStates.get(uid).clear(); diff --git a/services/core/java/com/android/server/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java index 0bd22f3da67f..f0da67bd9f9a 100644 --- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java +++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java @@ -77,13 +77,15 @@ class PreAuthInfo { private final int mBiometricStrengthRequested; private final BiometricCameraManager mBiometricCameraManager; private final boolean mOnlyMandatoryBiometricsRequested; + private final boolean mIsMandatoryBiometricsAuthentication; private PreAuthInfo(boolean biometricRequested, int biometricStrengthRequested, boolean credentialRequested, List<BiometricSensor> eligibleSensors, List<Pair<BiometricSensor, Integer>> ineligibleSensors, boolean credentialAvailable, PromptInfo promptInfo, int userId, Context context, BiometricCameraManager biometricCameraManager, - boolean isOnlyMandatoryBiometricsRequested) { + boolean isOnlyMandatoryBiometricsRequested, + boolean isMandatoryBiometricsAuthentication) { mBiometricRequested = biometricRequested; mBiometricStrengthRequested = biometricStrengthRequested; mBiometricCameraManager = biometricCameraManager; @@ -97,6 +99,7 @@ class PreAuthInfo { this.userId = userId; this.context = context; this.mOnlyMandatoryBiometricsRequested = isOnlyMandatoryBiometricsRequested; + this.mIsMandatoryBiometricsAuthentication = isMandatoryBiometricsAuthentication; } static PreAuthInfo create(ITrustManager trustManager, @@ -110,10 +113,12 @@ class PreAuthInfo { final boolean isOnlyMandatoryBiometricsRequested = promptInfo.getAuthenticators() == BiometricManager.Authenticators.MANDATORY_BIOMETRICS; + boolean isMandatoryBiometricsAuthentication = false; if (dropCredentialFallback(promptInfo.getAuthenticators(), settingObserver.getMandatoryBiometricsEnabledAndRequirementsSatisfiedForUser( userId), trustManager)) { + isMandatoryBiometricsAuthentication = true; promptInfo.setAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG); promptInfo.setNegativeButtonText(context.getString(R.string.cancel)); } @@ -166,7 +171,8 @@ class PreAuthInfo { return new PreAuthInfo(biometricRequested, requestedStrength, credentialRequested, eligibleSensors, ineligibleSensors, credentialAvailable, promptInfo, userId, - context, biometricCameraManager, isOnlyMandatoryBiometricsRequested); + context, biometricCameraManager, isOnlyMandatoryBiometricsRequested, + isMandatoryBiometricsAuthentication); } private static boolean dropCredentialFallback(int authenticators, @@ -387,25 +393,6 @@ class PreAuthInfo { status = CREDENTIAL_NOT_ENROLLED; } } - } else if (Flags.mandatoryBiometrics() && mOnlyMandatoryBiometricsRequested) { - if (!eligibleSensors.isEmpty()) { - for (BiometricSensor sensor : eligibleSensors) { - modality |= sensor.modality; - } - - if (modality == TYPE_FACE && cameraPrivacyEnabled) { - // If the only modality requested is face, credential is unavailable, - // and the face sensor privacy is enabled then return - // BIOMETRIC_SENSOR_PRIVACY_ENABLED. - // - // Note: This sensor will not be eligible for calls to authenticate. - status = BIOMETRIC_SENSOR_PRIVACY_ENABLED; - } else { - status = AUTHENTICATOR_OK; - } - } else { - status = MANDATORY_BIOMETRIC_UNAVAILABLE_ERROR; - } } else if (mBiometricRequested) { if (!eligibleSensors.isEmpty()) { for (BiometricSensor sensor : eligibleSensors) { @@ -434,6 +421,9 @@ class PreAuthInfo { } else if (credentialRequested) { modality |= TYPE_CREDENTIAL; status = credentialAvailable ? AUTHENTICATOR_OK : CREDENTIAL_NOT_ENROLLED; + } else if (Flags.mandatoryBiometrics() && mOnlyMandatoryBiometricsRequested + && !mIsMandatoryBiometricsAuthentication) { + status = MANDATORY_BIOMETRIC_UNAVAILABLE_ERROR; } else { // This should not be possible via the public API surface and is here mainly for // "correctness". An exception should have been thrown before getting here. diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java index fb8a81be4b89..871121472938 100644 --- a/services/core/java/com/android/server/biometrics/Utils.java +++ b/services/core/java/com/android/server/biometrics/Utils.java @@ -35,6 +35,7 @@ import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_NOT_ENROLLED; import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_NO_HARDWARE; import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_SENSOR_PRIVACY_ENABLED; import static com.android.server.biometrics.PreAuthInfo.CREDENTIAL_NOT_ENROLLED; +import static com.android.server.biometrics.PreAuthInfo.MANDATORY_BIOMETRIC_UNAVAILABLE_ERROR; import android.annotation.NonNull; import android.annotation.Nullable; @@ -48,6 +49,7 @@ import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricPrompt; import android.hardware.biometrics.BiometricPrompt.AuthenticationResultType; +import android.hardware.biometrics.Flags; import android.hardware.biometrics.IBiometricService; import android.hardware.biometrics.PromptInfo; import android.hardware.biometrics.SensorProperties; @@ -309,11 +311,16 @@ public class Utils { break; case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT: case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT: - biometricManagerCode = BiometricManager.BIOMETRIC_SUCCESS; + biometricManagerCode = Flags.mandatoryBiometrics() + ? BiometricManager.BIOMETRIC_ERROR_LOCKOUT + : BiometricManager.BIOMETRIC_SUCCESS; break; case BiometricConstants.BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED: biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE; break; + case BiometricConstants.BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE: + biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE; + break; default: Slog.e(BiometricService.TAG, "Unhandled result code: " + biometricConstantsCode); biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE; @@ -375,6 +382,8 @@ public class Utils { return BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT; case BIOMETRIC_SENSOR_PRIVACY_ENABLED: return BiometricConstants.BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED; + case MANDATORY_BIOMETRIC_UNAVAILABLE_ERROR: + return BiometricConstants.BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE; case BIOMETRIC_DISABLED_BY_DEVICE_POLICY: case BIOMETRIC_HARDWARE_NOT_DETECTED: case BIOMETRIC_NOT_ENABLED_FOR_APPS: diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java index 38e6d822477a..1094bee1e7e6 100644 --- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java +++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java @@ -1002,9 +1002,9 @@ public final class DeviceStateManagerService extends SystemService { /** * Checks if the process can cancel a device state request. If the calling process ID is not - * both the top app and foregrounded nor does the process ID and userID match the IDs that made - * the device state request, then check if this process holds the CONTROL_DEVICE_STATE - * permission. + * both the top app and foregrounded, verify that the calling process is in the foreground and + * that it matches the process ID and user ID that made the device state request. If neither are + * true, then check if this process holds the CONTROL_DEVICE_STATE permission. * * @param callingPid Process ID that is requesting this state change * @param callingUid UID that is requesting this state change @@ -1018,8 +1018,8 @@ public final class DeviceStateManagerService extends SystemService { if (Flags.deviceStateRequesterCancelState()) { synchronized (mLock) { isAllowedToControlState = - isAllowedToControlState || doCallingIdsMatchOverrideRequestIdsLocked( - callingPid, callingUid); + isTopApp || (isForegroundApp && doCallingIdsMatchOverrideRequestIdsLocked( + callingPid, callingUid)); } } diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java index 5e471c82e108..31f5a417171d 100644 --- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java @@ -1592,6 +1592,12 @@ public class DisplayModeDirector { - SYNCHRONIZED_REFRESH_RATE_TOLERANCE, SYNCHRONIZED_REFRESH_RATE_TARGET + SYNCHRONIZED_REFRESH_RATE_TOLERANCE)); + mVotesStorage.updateGlobalVote(Vote.PRIORITY_SYNCHRONIZED_RENDER_FRAME_RATE, + Vote.forRenderFrameRates( + SYNCHRONIZED_REFRESH_RATE_TARGET + - SYNCHRONIZED_REFRESH_RATE_TOLERANCE, + SYNCHRONIZED_REFRESH_RATE_TARGET + + SYNCHRONIZED_REFRESH_RATE_TOLERANCE)); } private void removeDisplaysSynchronizedPeakRefreshRate(final int displayId) { @@ -1603,11 +1609,12 @@ public class DisplayModeDirector { return; } mExternalDisplaysConnected.remove(displayId); - if (mExternalDisplaysConnected.size() != 0) { + if (!mExternalDisplaysConnected.isEmpty()) { return; } } mVotesStorage.updateGlobalVote(Vote.PRIORITY_SYNCHRONIZED_REFRESH_RATE, null); + mVotesStorage.updateGlobalVote(Vote.PRIORITY_SYNCHRONIZED_RENDER_FRAME_RATE, null); } private void updateDisplayDeviceConfig(int displayId) { diff --git a/services/core/java/com/android/server/display/mode/Vote.java b/services/core/java/com/android/server/display/mode/Vote.java index 7cbdd13152b5..88ee044810db 100644 --- a/services/core/java/com/android/server/display/mode/Vote.java +++ b/services/core/java/com/android/server/display/mode/Vote.java @@ -92,48 +92,55 @@ interface Vote { // render rate vote can still apply int PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE = 9; - // Restrict all displays to 60Hz when external display is connected. It votes [59Hz, 61Hz]. + // Restrict all displays physical refresh rate to 60Hz when external display is connected. + // It votes [59Hz, 61Hz]. int PRIORITY_SYNCHRONIZED_REFRESH_RATE = 10; + // PRIORITY_SYNCHRONIZED_RENDER_FRAME_RATE has a higher priority than + // PRIORITY_SYNCHRONIZED_REFRESH_RATE and will limit render rate to [59Hz, 61Hz]. + // In case physical refresh rate vote discarded (due to physical refresh rate not supported), + // render rate vote can still apply. + int PRIORITY_SYNCHRONIZED_RENDER_FRAME_RATE = 11; + // Restrict displays max available resolution and refresh rates. It votes [0, LIMIT] - int PRIORITY_LIMIT_MODE = 11; + int PRIORITY_LIMIT_MODE = 12; // To avoid delay in switching between 60HZ -> 90HZ when activating LHBM, set refresh // rate to max value (same as for PRIORITY_UDFPS) on lock screen - int PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE = 12; + int PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE = 13; // For concurrent displays we want to limit refresh rate on all displays - int PRIORITY_LAYOUT_LIMITED_FRAME_RATE = 13; + int PRIORITY_LAYOUT_LIMITED_FRAME_RATE = 14; // For internal application to limit display modes to specific ids - int PRIORITY_SYSTEM_REQUESTED_MODES = 14; + int PRIORITY_SYSTEM_REQUESTED_MODES = 15; // PRIORITY_LOW_POWER_MODE_MODES limits display modes to specific refreshRate-vsync pairs if // Settings.Global.LOW_POWER_MODE is on. // Lower priority that PRIORITY_LOW_POWER_MODE_RENDER_RATE and if discarded (due to other // higher priority votes), render rate limit can still apply - int PRIORITY_LOW_POWER_MODE_MODES = 15; + int PRIORITY_LOW_POWER_MODE_MODES = 16; // PRIORITY_LOW_POWER_MODE_RENDER_RATE force the render frame rate to [0, 60HZ] if // Settings.Global.LOW_POWER_MODE is on. - int PRIORITY_LOW_POWER_MODE_RENDER_RATE = 16; + int PRIORITY_LOW_POWER_MODE_RENDER_RATE = 17; // PRIORITY_FLICKER_REFRESH_RATE_SWITCH votes for disabling refresh rate switching. If the // higher priority voters' result is a range, it will fix the rate to a single choice. // It's used to avoid refresh rate switches in certain conditions which may result in the // user seeing the display flickering when the switches occur. - int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 17; + int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 18; // Force display to [0, 60HZ] if skin temperature is at or above CRITICAL. - int PRIORITY_SKIN_TEMPERATURE = 18; + int PRIORITY_SKIN_TEMPERATURE = 19; // The proximity sensor needs the refresh rate to be locked in order to function, so this is // set to a high priority. - int PRIORITY_PROXIMITY = 19; + int PRIORITY_PROXIMITY = 20; // The Under-Display Fingerprint Sensor (UDFPS) needs the refresh rate to be locked in order // to function, so this needs to be the highest priority of all votes. - int PRIORITY_UDFPS = 20; + int PRIORITY_UDFPS = 21; @IntDef(prefix = { "PRIORITY_" }, value = { PRIORITY_DEFAULT_RENDER_FRAME_RATE, @@ -147,6 +154,7 @@ interface Vote { PRIORITY_USER_SETTING_PEAK_REFRESH_RATE, PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE, PRIORITY_SYNCHRONIZED_REFRESH_RATE, + PRIORITY_SYNCHRONIZED_RENDER_FRAME_RATE, PRIORITY_LIMIT_MODE, PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE, PRIORITY_LAYOUT_LIMITED_FRAME_RATE, @@ -267,6 +275,8 @@ interface Vote { return "PRIORITY_LIMIT_MODE"; case PRIORITY_SYNCHRONIZED_REFRESH_RATE: return "PRIORITY_SYNCHRONIZED_REFRESH_RATE"; + case PRIORITY_SYNCHRONIZED_RENDER_FRAME_RATE: + return "PRIORITY_SYNCHRONIZED_RENDER_FRAME_RATE"; case PRIORITY_USER_SETTING_PEAK_REFRESH_RATE: return "PRIORITY_USER_SETTING_PEAK_REFRESH_RATE"; case PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE: diff --git a/services/core/java/com/android/server/input/InputManagerInternal.java b/services/core/java/com/android/server/input/InputManagerInternal.java index 819b9a166daa..73f18d17d058 100644 --- a/services/core/java/com/android/server/input/InputManagerInternal.java +++ b/services/core/java/com/android/server/input/InputManagerInternal.java @@ -21,7 +21,7 @@ import android.annotation.Nullable; import android.annotation.UserIdInt; import android.graphics.PointF; import android.hardware.display.DisplayViewport; -import android.hardware.input.KeyboardSystemShortcut; +import android.hardware.input.KeyGestureEvent; import android.os.IBinder; import android.view.InputChannel; import android.view.inputmethod.InputMethodSubtype; @@ -230,18 +230,14 @@ public abstract class InputManagerInternal { public abstract int getLastUsedInputDeviceId(); /** - * Notify Keyboard system shortcut was triggered by the user and handled by the framework. + * Notify key gesture was completed by the user. * - * NOTE: This is just to notify that a system shortcut was triggered. No further action is - * required to execute the said shortcut. This callback is meant for purposes of providing user - * hints or logging, etc. - * - * @param deviceId the device ID of the keyboard using which the shortcut was triggered - * @param keycodes the keys pressed for triggering the shortcut - * @param modifierState the modifier state of the key event that triggered the shortcut - * @param shortcut the shortcut that was triggered + * @param deviceId the device ID of the keyboard using which the event was completed + * @param keycodes the keys pressed for the event + * @param modifierState the modifier state + * @param event the gesture event that was completed * */ - public abstract void notifyKeyboardShortcutTriggered(int deviceId, int[] keycodes, - int modifierState, @KeyboardSystemShortcut.SystemShortcut int shortcut); + public abstract void notifyKeyGestureCompleted(int deviceId, int[] keycodes, int modifierState, + @KeyGestureEvent.KeyGestureType int event); } diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index a69c7efc5b21..a8fc8621cbc7 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -48,18 +48,18 @@ import android.hardware.input.IInputDeviceBatteryState; import android.hardware.input.IInputDevicesChangedListener; import android.hardware.input.IInputManager; import android.hardware.input.IInputSensorEventListener; +import android.hardware.input.IKeyGestureEventListener; import android.hardware.input.IKeyboardBacklightListener; -import android.hardware.input.IKeyboardSystemShortcutListener; import android.hardware.input.IStickyModifierStateListener; import android.hardware.input.ITabletModeChangedListener; import android.hardware.input.InputDeviceIdentifier; import android.hardware.input.InputManager; import android.hardware.input.InputSensorInfo; import android.hardware.input.InputSettings; +import android.hardware.input.KeyGestureEvent; import android.hardware.input.KeyGlyphMap; import android.hardware.input.KeyboardLayout; import android.hardware.input.KeyboardLayoutSelectionResult; -import android.hardware.input.KeyboardSystemShortcut; import android.hardware.input.TouchCalibration; import android.hardware.lights.Light; import android.hardware.lights.LightState; @@ -162,7 +162,7 @@ public class InputManagerService extends IInputManager.Stub private static final int MSG_DELIVER_INPUT_DEVICES_CHANGED = 1; private static final int MSG_RELOAD_DEVICE_ALIASES = 2; private static final int MSG_DELIVER_TABLET_MODE_CHANGED = 3; - private static final int MSG_KEYBOARD_SYSTEM_SHORTCUT_TRIGGERED = 4; + private static final int MSG_KEY_GESTURE_COMPLETED = 4; private static final int DEFAULT_VIBRATION_MAGNITUDE = 192; private static final AdditionalDisplayInputProperties @@ -314,9 +314,7 @@ public class InputManagerService extends IInputManager.Stub // Manages Sticky modifier state private final StickyModifierStateController mStickyModifierStateController; - - // Manages keyboard system shortcut callbacks - private final KeyboardShortcutCallbackHandler mKeyboardShortcutCallbackHandler; + private final KeyGestureController mKeyGestureController; // Manages Keyboard microphone mute led private final KeyboardLedController mKeyboardLedController; @@ -476,7 +474,7 @@ public class InputManagerService extends IInputManager.Stub injector.getLooper(), injector.getUEventManager()) : new KeyboardBacklightControllerInterface() {}; mStickyModifierStateController = new StickyModifierStateController(); - mKeyboardShortcutCallbackHandler = new KeyboardShortcutCallbackHandler(); + mKeyGestureController = new KeyGestureController(); mKeyboardLedController = new KeyboardLedController(mContext, injector.getLooper(), mNative); mKeyRemapper = new KeyRemapper(mContext, mNative, mDataStore, injector.getLooper()); @@ -2723,33 +2721,32 @@ public class InputManagerService extends IInputManager.Stub } @Override - @EnforcePermission(Manifest.permission.MONITOR_KEYBOARD_SYSTEM_SHORTCUTS) - public void registerKeyboardSystemShortcutListener( - @NonNull IKeyboardSystemShortcutListener listener) { - super.registerKeyboardSystemShortcutListener_enforcePermission(); + @EnforcePermission(Manifest.permission.MANAGE_KEY_GESTURES) + public void registerKeyGestureEventListener( + @NonNull IKeyGestureEventListener listener) { + super.registerKeyGestureEventListener_enforcePermission(); Objects.requireNonNull(listener); - mKeyboardShortcutCallbackHandler.registerKeyboardSystemShortcutListener(listener, + mKeyGestureController.registerKeyGestureEventListener(listener, Binder.getCallingPid()); } @Override - @EnforcePermission(Manifest.permission.MONITOR_KEYBOARD_SYSTEM_SHORTCUTS) - public void unregisterKeyboardSystemShortcutListener( - @NonNull IKeyboardSystemShortcutListener listener) { - super.unregisterKeyboardSystemShortcutListener_enforcePermission(); + @EnforcePermission(Manifest.permission.MANAGE_KEY_GESTURES) + public void unregisterKeyGestureEventListener( + @NonNull IKeyGestureEventListener listener) { + super.unregisterKeyGestureEventListener_enforcePermission(); Objects.requireNonNull(listener); - mKeyboardShortcutCallbackHandler.unregisterKeyboardSystemShortcutListener(listener, + mKeyGestureController.unregisterKeyGestureEventListener(listener, Binder.getCallingPid()); } - private void handleKeyboardSystemShortcutTriggered(int deviceId, - KeyboardSystemShortcut shortcut) { - InputDevice device = getInputDevice(deviceId); - if (device == null || device.isVirtual() || !device.isFullKeyboard()) { + private void handleKeyGestureCompleted(KeyGestureEvent event) { + InputDevice device = getInputDevice(event.getDeviceId()); + if (device == null || device.isVirtual()) { return; } - KeyboardMetricsCollector.logKeyboardSystemsEventReportedAtom(device, shortcut); - mKeyboardShortcutCallbackHandler.onKeyboardSystemShortcutTriggered(deviceId, shortcut); + KeyboardMetricsCollector.logKeyboardSystemsEventReportedAtom(device, event); + mKeyGestureController.onKeyGestureEvent(event); } /** @@ -2920,10 +2917,9 @@ public class InputManagerService extends IInputManager.Stub boolean inTabletMode = (boolean) args.arg1; deliverTabletModeChanged(whenNanos, inTabletMode); break; - case MSG_KEYBOARD_SYSTEM_SHORTCUT_TRIGGERED: - int deviceId = msg.arg1; - KeyboardSystemShortcut shortcut = (KeyboardSystemShortcut) msg.obj; - handleKeyboardSystemShortcutTriggered(deviceId, shortcut); + case MSG_KEY_GESTURE_COMPLETED: + KeyGestureEvent event = (KeyGestureEvent) msg.obj; + handleKeyGestureCompleted(event); } } } @@ -3251,10 +3247,11 @@ public class InputManagerService extends IInputManager.Stub } @Override - public void notifyKeyboardShortcutTriggered(int deviceId, int[] keycodes, int modifierState, - @KeyboardSystemShortcut.SystemShortcut int shortcut) { - mHandler.obtainMessage(MSG_KEYBOARD_SYSTEM_SHORTCUT_TRIGGERED, deviceId, 0, - new KeyboardSystemShortcut(keycodes, modifierState, shortcut)).sendToTarget(); + public void notifyKeyGestureCompleted(int deviceId, int[] keycodes, int modifierState, + @KeyGestureEvent.KeyGestureType int gestureType) { + mHandler.obtainMessage(MSG_KEY_GESTURE_COMPLETED, + new KeyGestureEvent(deviceId, keycodes, modifierState, + gestureType)).sendToTarget(); } } diff --git a/services/core/java/com/android/server/input/KeyGestureController.java b/services/core/java/com/android/server/input/KeyGestureController.java new file mode 100644 index 000000000000..674d3c448c86 --- /dev/null +++ b/services/core/java/com/android/server/input/KeyGestureController.java @@ -0,0 +1,134 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.input; + +import android.annotation.BinderThread; +import android.hardware.input.IKeyGestureEventListener; +import android.hardware.input.KeyGestureEvent; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; +import android.util.Slog; +import android.util.SparseArray; + +import com.android.internal.annotations.GuardedBy; + +/** + * A thread-safe component of {@link InputManagerService} responsible for managing callbacks when a + * key gesture event occurs. + */ +final class KeyGestureController { + + private static final String TAG = "KeyGestureController"; + + // To enable these logs, run: + // 'adb shell setprop log.tag.KeyGestureController DEBUG' (requires restart) + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + // List of currently registered key gesture event listeners keyed by process pid + @GuardedBy("mKeyGestureEventListenerRecords") + private final SparseArray<KeyGestureEventListenerRecord> + mKeyGestureEventListenerRecords = new SparseArray<>(); + + public void onKeyGestureEvent(KeyGestureEvent event) { + if (DEBUG) { + Slog.d(TAG, "Key gesture event occurred, event = " + event); + } + + synchronized (mKeyGestureEventListenerRecords) { + for (int i = 0; i < mKeyGestureEventListenerRecords.size(); i++) { + mKeyGestureEventListenerRecords.valueAt(i).onKeyGestureEvent(event); + } + } + } + + /** Register the key gesture event listener for a process. */ + @BinderThread + public void registerKeyGestureEventListener(IKeyGestureEventListener listener, + int pid) { + synchronized (mKeyGestureEventListenerRecords) { + if (mKeyGestureEventListenerRecords.get(pid) != null) { + throw new IllegalStateException("The calling process has already registered " + + "a KeyGestureEventListener."); + } + KeyGestureEventListenerRecord record = new KeyGestureEventListenerRecord( + pid, listener); + try { + listener.asBinder().linkToDeath(record, 0); + } catch (RemoteException ex) { + throw new RuntimeException(ex); + } + mKeyGestureEventListenerRecords.put(pid, record); + } + } + + /** Unregister the key gesture event listener for a process. */ + @BinderThread + public void unregisterKeyGestureEventListener(IKeyGestureEventListener listener, + int pid) { + synchronized (mKeyGestureEventListenerRecords) { + KeyGestureEventListenerRecord record = + mKeyGestureEventListenerRecords.get(pid); + if (record == null) { + throw new IllegalStateException("The calling process has no registered " + + "KeyGestureEventListener."); + } + if (record.mListener.asBinder() != listener.asBinder()) { + throw new IllegalStateException("The calling process has a different registered " + + "KeyGestureEventListener."); + } + record.mListener.asBinder().unlinkToDeath(record, 0); + mKeyGestureEventListenerRecords.remove(pid); + } + } + + private void onKeyGestureEventListenerDied(int pid) { + synchronized (mKeyGestureEventListenerRecords) { + mKeyGestureEventListenerRecords.remove(pid); + } + } + + // A record of a registered key gesture event listener from one process. + private class KeyGestureEventListenerRecord implements IBinder.DeathRecipient { + public final int mPid; + public final IKeyGestureEventListener mListener; + + KeyGestureEventListenerRecord(int pid, IKeyGestureEventListener listener) { + mPid = pid; + mListener = listener; + } + + @Override + public void binderDied() { + if (DEBUG) { + Slog.d(TAG, "Key gesture event listener for pid " + mPid + " died."); + } + onKeyGestureEventListenerDied(mPid); + } + + public void onKeyGestureEvent(KeyGestureEvent event) { + try { + mListener.onKeyGestureEvent(event.getDeviceId(), event.getKeycodes(), + event.getModifierState(), event.getKeyGestureType()); + } catch (RemoteException ex) { + Slog.w(TAG, "Failed to notify process " + mPid + + " that key gesture event occurred, assuming it died.", ex); + binderDied(); + } + } + } +} diff --git a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java index 3d2f95105e76..1daf4db699aa 100644 --- a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java +++ b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java @@ -24,9 +24,9 @@ import static android.hardware.input.KeyboardLayoutSelectionResult.layoutSelecti import android.annotation.NonNull; import android.annotation.Nullable; +import android.hardware.input.KeyGestureEvent; import android.hardware.input.KeyboardLayout; import android.hardware.input.KeyboardLayoutSelectionResult.LayoutSelectionCriteria; -import android.hardware.input.KeyboardSystemShortcut; import android.icu.util.ULocale; import android.text.TextUtils; import android.util.Log; @@ -66,14 +66,17 @@ public final class KeyboardMetricsCollector { * defined in "stats/atoms/input/input_extension_atoms.proto" */ public static void logKeyboardSystemsEventReportedAtom(@NonNull InputDevice inputDevice, - @NonNull KeyboardSystemShortcut keyboardSystemShortcut) { + @NonNull KeyGestureEvent keyGestureEvent) { + if (inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) { + return; + } FrameworkStatsLog.write(FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED, inputDevice.getVendorId(), inputDevice.getProductId(), - keyboardSystemShortcut.getSystemShortcut(), keyboardSystemShortcut.getKeycodes(), - keyboardSystemShortcut.getModifierState(), inputDevice.getDeviceBus()); + keyGestureEvent.getKeyGestureType(), keyGestureEvent.getKeycodes(), + keyGestureEvent.getModifierState(), inputDevice.getDeviceBus()); if (DEBUG) { - Slog.d(TAG, "Logging Keyboard system event: " + keyboardSystemShortcut); + Slog.d(TAG, "Logging Keyboard system event: " + keyGestureEvent); } } diff --git a/services/core/java/com/android/server/input/KeyboardShortcutCallbackHandler.java b/services/core/java/com/android/server/input/KeyboardShortcutCallbackHandler.java deleted file mode 100644 index 092058e6f7d0..000000000000 --- a/services/core/java/com/android/server/input/KeyboardShortcutCallbackHandler.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.input; - -import android.annotation.BinderThread; -import android.hardware.input.IKeyboardSystemShortcutListener; -import android.hardware.input.KeyboardSystemShortcut; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.Log; -import android.util.Slog; -import android.util.SparseArray; - -import com.android.internal.annotations.GuardedBy; - -/** - * A thread-safe component of {@link InputManagerService} responsible for managing callbacks when a - * keyboard shortcut is triggered. - */ -final class KeyboardShortcutCallbackHandler { - - private static final String TAG = "KeyboardShortcut"; - - // To enable these logs, run: - // 'adb shell setprop log.tag.KeyboardShortcutCallbackHandler DEBUG' (requires restart) - private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - - // List of currently registered keyboard system shortcut listeners keyed by process pid - @GuardedBy("mKeyboardSystemShortcutListenerRecords") - private final SparseArray<KeyboardSystemShortcutListenerRecord> - mKeyboardSystemShortcutListenerRecords = new SparseArray<>(); - - public void onKeyboardSystemShortcutTriggered(int deviceId, - KeyboardSystemShortcut systemShortcut) { - if (DEBUG) { - Slog.d(TAG, "Keyboard system shortcut triggered, deviceId = " + deviceId - + ", systemShortcut = " + systemShortcut); - } - - synchronized (mKeyboardSystemShortcutListenerRecords) { - for (int i = 0; i < mKeyboardSystemShortcutListenerRecords.size(); i++) { - mKeyboardSystemShortcutListenerRecords.valueAt(i).onKeyboardSystemShortcutTriggered( - deviceId, systemShortcut); - } - } - } - - /** Register the keyboard system shortcut listener for a process. */ - @BinderThread - public void registerKeyboardSystemShortcutListener(IKeyboardSystemShortcutListener listener, - int pid) { - synchronized (mKeyboardSystemShortcutListenerRecords) { - if (mKeyboardSystemShortcutListenerRecords.get(pid) != null) { - throw new IllegalStateException("The calling process has already registered " - + "a KeyboardSystemShortcutListener."); - } - KeyboardSystemShortcutListenerRecord record = new KeyboardSystemShortcutListenerRecord( - pid, listener); - try { - listener.asBinder().linkToDeath(record, 0); - } catch (RemoteException ex) { - throw new RuntimeException(ex); - } - mKeyboardSystemShortcutListenerRecords.put(pid, record); - } - } - - /** Unregister the keyboard system shortcut listener for a process. */ - @BinderThread - public void unregisterKeyboardSystemShortcutListener(IKeyboardSystemShortcutListener listener, - int pid) { - synchronized (mKeyboardSystemShortcutListenerRecords) { - KeyboardSystemShortcutListenerRecord record = - mKeyboardSystemShortcutListenerRecords.get(pid); - if (record == null) { - throw new IllegalStateException("The calling process has no registered " - + "KeyboardSystemShortcutListener."); - } - if (record.mListener.asBinder() != listener.asBinder()) { - throw new IllegalStateException("The calling process has a different registered " - + "KeyboardSystemShortcutListener."); - } - record.mListener.asBinder().unlinkToDeath(record, 0); - mKeyboardSystemShortcutListenerRecords.remove(pid); - } - } - - private void onKeyboardSystemShortcutListenerDied(int pid) { - synchronized (mKeyboardSystemShortcutListenerRecords) { - mKeyboardSystemShortcutListenerRecords.remove(pid); - } - } - - // A record of a registered keyboard system shortcut listener from one process. - private class KeyboardSystemShortcutListenerRecord implements IBinder.DeathRecipient { - public final int mPid; - public final IKeyboardSystemShortcutListener mListener; - - KeyboardSystemShortcutListenerRecord(int pid, IKeyboardSystemShortcutListener listener) { - mPid = pid; - mListener = listener; - } - - @Override - public void binderDied() { - if (DEBUG) { - Slog.d(TAG, "Keyboard system shortcut listener for pid " + mPid + " died."); - } - onKeyboardSystemShortcutListenerDied(mPid); - } - - public void onKeyboardSystemShortcutTriggered(int deviceId, KeyboardSystemShortcut data) { - try { - mListener.onKeyboardSystemShortcutTriggered(deviceId, data.getKeycodes(), - data.getModifierState(), data.getSystemShortcut()); - } catch (RemoteException ex) { - Slog.w(TAG, "Failed to notify process " + mPid - + " that keyboard system shortcut was triggered, assuming it died.", ex); - binderDied(); - } - } - } -} diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java index 981891669e7c..abb21323f7f0 100644 --- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java +++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java @@ -489,6 +489,7 @@ public final class NotificationAttentionHelper { } else if ((record.getFlags() & Notification.FLAG_INSISTENT) != 0) { hasValidSound = false; + hasValidVibrate = false; } } } @@ -753,6 +754,13 @@ public final class NotificationAttentionHelper { // notifying app does not have the VIBRATE permission. final long identity = Binder.clearCallingIdentity(); try { + // Need to explicitly cancel a previously playing vibration + // Otherwise a looping vibration will not be stopped when starting a new one. + if (mVibrateNotificationKey != null + && !mVibrateNotificationKey.equals(record.getKey())) { + mVibrateNotificationKey = null; + mVibratorHelper.cancelVibration(); + } final float scale = getVibrationIntensity(record); final VibrationEffect scaledEffect = Float.compare(scale, DEFAULT_VOLUME) != 0 ? mVibratorHelper.scale(effect, scale) : effect; diff --git a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java index 1938642ef396..e2889fa9cbf6 100644 --- a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java +++ b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java @@ -29,8 +29,8 @@ import android.content.Context; import android.media.AudioAttributes; import android.os.Binder; import android.os.RemoteException; -import android.os.ServiceManager; import android.util.Slog; + import com.android.internal.compat.IPlatformCompat; /** @@ -79,6 +79,11 @@ public class NotificationChannelExtractor implements NotificationSignalExtractor if (restrictAudioAttributesCall() || restrictAudioAttributesAlarm() || restrictAudioAttributesMedia()) { AudioAttributes attributes = record.getChannel().getAudioAttributes(); + if (attributes == null) { + if (DBG) Slog.d(TAG, "missing AudioAttributes"); + return null; + } + boolean updateAttributes = false; if (restrictAudioAttributesCall() && !record.getNotification().isStyle(Notification.CallStyle.class) diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 22b4d5def8f4..5105fd384367 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -1399,7 +1399,29 @@ final class InstallPackageHelper { "Package " + pkgName + " is a persistent app. " + "Persistent apps are not updateable."); } + // When updating an sdk library, make sure that the versionMajor is + // changed if the targetSdkVersion and minSdkVersion have changed + if (parsedPackage.isSdkLibrary() && ps.getPkg() != null + && ps.getPkg().isSdkLibrary()) { + final int oldMinSdk = ps.getPkg().getMinSdkVersion(); + final int newMinSdk = parsedPackage.getMinSdkVersion(); + if (oldTargetSdk != newTargetSdk || oldMinSdk != newMinSdk) { + final int oldVersionMajor = ps.getPkg().getSdkLibVersionMajor(); + final int newVersionMajor = parsedPackage.getSdkLibVersionMajor(); + if (oldVersionMajor == newVersionMajor) { + throw new PrepareFailure( + PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE, + "Failure updating " + pkgName + " as it updates" + + " an sdk library <" + + parsedPackage.getSdkLibraryName() + ">" + + " without changing the versionMajor, but the" + + " targetSdkVersion or minSdkVersion has changed." + ); + } + } + } } + } PackageSetting signatureCheckPs = ps; diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING index 8089f21e7a51..2c13bd0d91d5 100644 --- a/services/core/java/com/android/server/pm/TEST_MAPPING +++ b/services/core/java/com/android/server/pm/TEST_MAPPING @@ -9,22 +9,6 @@ ] }, { - "name": "CtsPackageInstallerCUJTestCases", - "file_patterns": [ - "core/java/.*Install.*", - "services/core/.*Install.*", - "services/core/java/com/android/server/pm/.*" - ], - "options":[ - { - "exclude-annotation":"androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation":"org.junit.Ignore" - } - ] - }, - { "name": "CtsUsesLibraryHostTestCases" }, { @@ -181,7 +165,55 @@ "name": "CtsUpdateOwnershipEnforcementTestCases" }, { - "name": "CtsPackageInstallerCUJTestCases", + "name": "CtsPackageInstallerCUJInstallationTestCases", + "file_patterns": [ + "core/java/.*Install.*", + "services/core/.*Install.*", + "services/core/java/com/android/server/pm/.*" + ], + "options":[ + { + "exclude-annotation":"androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation":"org.junit.Ignore" + } + ] + }, + { + "name": "CtsPackageInstallerCUJUninstallationTestCases", + "file_patterns": [ + "core/java/.*Install.*", + "services/core/.*Install.*", + "services/core/java/com/android/server/pm/.*" + ], + "options":[ + { + "exclude-annotation":"androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation":"org.junit.Ignore" + } + ] + }, + { + "name": "CtsPackageInstallerCUJUpdateOwnerShipTestCases", + "file_patterns": [ + "core/java/.*Install.*", + "services/core/.*Install.*", + "services/core/java/com/android/server/pm/.*" + ], + "options":[ + { + "exclude-annotation":"androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation":"org.junit.Ignore" + } + ] + }, + { + "name": "CtsPackageInstallerCUJUpdateSelfTestCases", "file_patterns": [ "core/java/.*Install.*", "services/core/.*Install.*", diff --git a/services/core/java/com/android/server/policy/ModifierShortcutManager.java b/services/core/java/com/android/server/policy/ModifierShortcutManager.java index 5a4518606ca6..7ed89728a005 100644 --- a/services/core/java/com/android/server/policy/ModifierShortcutManager.java +++ b/services/core/java/com/android/server/policy/ModifierShortcutManager.java @@ -29,8 +29,7 @@ import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.res.XmlResourceParser; import android.graphics.drawable.Icon; -import android.hardware.input.InputManager; -import android.hardware.input.KeyboardSystemShortcut; +import android.hardware.input.KeyGestureEvent; import android.os.Handler; import android.os.RemoteException; import android.os.UserHandle; @@ -476,7 +475,7 @@ public class ModifierShortcutManager { + "keyCode=" + KeyEvent.keyCodeToString(keyCode) + "," + " category=" + category + " role=" + role); } - notifyKeyboardShortcutTriggered(keyEvent, getSystemShortcutFromIntent(intent)); + notifyKeyGestureCompleted(keyEvent, getKeyGestureTypeFromIntent(intent)); return true; } else { return false; @@ -497,19 +496,19 @@ public class ModifierShortcutManager { + "the activity to which it is registered was not found: " + "META+ or SEARCH" + KeyEvent.keyCodeToString(keyCode)); } - notifyKeyboardShortcutTriggered(keyEvent, getSystemShortcutFromIntent(shortcutIntent)); + notifyKeyGestureCompleted(keyEvent, getKeyGestureTypeFromIntent(shortcutIntent)); return true; } return false; } - private void notifyKeyboardShortcutTriggered(KeyEvent event, - @KeyboardSystemShortcut.SystemShortcut int systemShortcut) { - if (systemShortcut == KeyboardSystemShortcut.SYSTEM_SHORTCUT_UNSPECIFIED) { + private void notifyKeyGestureCompleted(KeyEvent event, + @KeyGestureEvent.KeyGestureType int gestureType) { + if (gestureType == KeyGestureEvent.KEY_GESTURE_TYPE_UNSPECIFIED) { return; } - mInputManagerInternal.notifyKeyboardShortcutTriggered(event.getDeviceId(), - new int[]{event.getKeyCode()}, event.getMetaState(), systemShortcut); + mInputManagerInternal.notifyKeyGestureCompleted(event.getDeviceId(), + new int[]{event.getKeyCode()}, event.getMetaState(), gestureType); } /** @@ -710,21 +709,21 @@ public class ModifierShortcutManager { /** - * Find Keyboard shortcut event corresponding to intent filter category. Returns - * {@code SYSTEM_SHORTCUT_UNSPECIFIED if no matching event found} + * Find Key gesture type corresponding to intent filter category. Returns + * {@code KEY_GESTURE_TYPE_UNSPECIFIED if no matching event found} */ - @KeyboardSystemShortcut.SystemShortcut - private static int getSystemShortcutFromIntent(Intent intent) { + @KeyGestureEvent.KeyGestureType + private static int getKeyGestureTypeFromIntent(Intent intent) { Intent selectorIntent = intent.getSelector(); if (selectorIntent != null) { Set<String> selectorCategories = selectorIntent.getCategories(); if (selectorCategories != null && !selectorCategories.isEmpty()) { for (String intentCategory : selectorCategories) { - int systemShortcut = getEventFromSelectorCategory(intentCategory); - if (systemShortcut == KeyboardSystemShortcut.SYSTEM_SHORTCUT_UNSPECIFIED) { + int keyGestureType = getKeyGestureTypeFromSelectorCategory(intentCategory); + if (keyGestureType == KeyGestureEvent.KEY_GESTURE_TYPE_UNSPECIFIED) { continue; } - return systemShortcut; + return keyGestureType; } } } @@ -733,69 +732,68 @@ public class ModifierShortcutManager { // so check for that. String role = intent.getStringExtra(ModifierShortcutManager.EXTRA_ROLE); if (!TextUtils.isEmpty(role)) { - return getLogEventFromRole(role); + return getKeyGestureTypeFromRole(role); } Set<String> intentCategories = intent.getCategories(); if (intentCategories == null || intentCategories.isEmpty() || !intentCategories.contains(Intent.CATEGORY_LAUNCHER)) { - return KeyboardSystemShortcut.SYSTEM_SHORTCUT_UNSPECIFIED; + return KeyGestureEvent.KEY_GESTURE_TYPE_UNSPECIFIED; } if (intent.getComponent() == null) { - return KeyboardSystemShortcut.SYSTEM_SHORTCUT_UNSPECIFIED; + return KeyGestureEvent.KEY_GESTURE_TYPE_UNSPECIFIED; } // TODO(b/280423320): Add new field package name associated in the // KeyboardShortcutEvent atom and log it accordingly. - return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_APPLICATION_BY_PACKAGE_NAME; + return KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION_BY_PACKAGE_NAME; } - @KeyboardSystemShortcut.SystemShortcut - private static int getEventFromSelectorCategory(String category) { + @KeyGestureEvent.KeyGestureType + private static int getKeyGestureTypeFromSelectorCategory(String category) { switch (category) { case Intent.CATEGORY_APP_BROWSER: - return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER; + return KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER; case Intent.CATEGORY_APP_EMAIL: - return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL; + return KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL; case Intent.CATEGORY_APP_CONTACTS: - return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS; + return KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS; case Intent.CATEGORY_APP_CALENDAR: - return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR; + return KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR; case Intent.CATEGORY_APP_CALCULATOR: - return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR; + return KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR; case Intent.CATEGORY_APP_MUSIC: - return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC; + return KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC; case Intent.CATEGORY_APP_MAPS: - return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS; + return KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS; case Intent.CATEGORY_APP_MESSAGING: - return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING; + return KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING; case Intent.CATEGORY_APP_GALLERY: - return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_GALLERY; + return KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_GALLERY; case Intent.CATEGORY_APP_FILES: - return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FILES; + return KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FILES; case Intent.CATEGORY_APP_WEATHER: - return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_WEATHER; + return KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_WEATHER; case Intent.CATEGORY_APP_FITNESS: - return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FITNESS; + return KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FITNESS; default: - return KeyboardSystemShortcut.SYSTEM_SHORTCUT_UNSPECIFIED; + return KeyGestureEvent.KEY_GESTURE_TYPE_UNSPECIFIED; } } /** - * Find KeyboardLogEvent corresponding to the provide system role name. - * Returns {@code null} if no matching event found. + * Find KeyGestureType corresponding to the provide system role name. + * Returns {@code KEY_GESTURE_TYPE_UNSPECIFIED} if no matching event found. */ - @KeyboardSystemShortcut.SystemShortcut - private static int getLogEventFromRole(String role) { + @KeyGestureEvent.KeyGestureType + private static int getKeyGestureTypeFromRole(String role) { if (RoleManager.ROLE_BROWSER.equals(role)) { - return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER; + return KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER; } else if (RoleManager.ROLE_SMS.equals(role)) { - return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING; + return KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING; } else { - Log.w(TAG, "Keyboard shortcut to launch " - + role + " not supported for logging"); - return KeyboardSystemShortcut.SYSTEM_SHORTCUT_UNSPECIFIED; + Log.w(TAG, "Keyboard gesture event to launch " + role + " not supported for logging"); + return KeyGestureEvent.KEY_GESTURE_TYPE_UNSPECIFIED; } } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 720c1c201158..aa56e8d8eb55 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -139,7 +139,7 @@ import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiPlaybackClient; import android.hardware.hdmi.HdmiPlaybackClient.OneTouchPlayCallback; import android.hardware.input.InputManager; -import android.hardware.input.KeyboardSystemShortcut; +import android.hardware.input.KeyGestureEvent; import android.media.AudioManager; import android.media.AudioManagerInternal; import android.media.AudioSystem; @@ -1819,7 +1819,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } private void handleShortPressOnHome(KeyEvent event) { - notifyKeyboardShortcutTriggered(event, KeyboardSystemShortcut.SYSTEM_SHORTCUT_HOME); + notifyKeyGestureCompleted(event, KeyGestureEvent.KEY_GESTURE_TYPE_HOME); // Turn on the connected TV and switch HDMI input if we're a HDMI playback device. final HdmiControl hdmiControl = getHdmiControl(); @@ -2053,8 +2053,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { } switch (mDoubleTapOnHomeBehavior) { case DOUBLE_TAP_HOME_RECENT_SYSTEM_UI: - notifyKeyboardShortcutTriggered(event, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_APP_SWITCH); + notifyKeyGestureCompleted(event, + KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH); mHomeConsumed = true; toggleRecentApps(); break; @@ -2082,23 +2082,23 @@ public class PhoneWindowManager implements WindowManagerPolicy { case LONG_PRESS_HOME_ALL_APPS: if (mHasFeatureLeanback) { launchAllAppsAction(); - notifyKeyboardShortcutTriggered(event, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_ALL_APPS); + notifyKeyGestureCompleted(event, + KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS); } else { launchAllAppsViaA11y(); - notifyKeyboardShortcutTriggered(event, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS); + notifyKeyGestureCompleted(event, + KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS); } break; case LONG_PRESS_HOME_ASSIST: - notifyKeyboardShortcutTriggered(event, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_ASSISTANT); + notifyKeyGestureCompleted(event, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT); launchAssistAction(null, event.getDeviceId(), event.getEventTime(), AssistUtils.INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS); break; case LONG_PRESS_HOME_NOTIFICATION_PANEL: - notifyKeyboardShortcutTriggered(event, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL); + notifyKeyGestureCompleted(event, + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL); toggleNotificationPanel(); break; default: @@ -3285,29 +3285,29 @@ public class PhoneWindowManager implements WindowManagerPolicy { WindowManager.LayoutParams.TYPE_SYSTEM_ERROR, }; - private void notifyKeyboardShortcutTriggeredOnActionUp(KeyEvent event, - @KeyboardSystemShortcut.SystemShortcut int systemShortcut) { + private void notifyKeyGestureCompletedOnActionUp(KeyEvent event, + @KeyGestureEvent.KeyGestureType int gestureType) { if (event.getAction() != KeyEvent.ACTION_UP) { return; } - notifyKeyboardShortcutTriggered(event, systemShortcut); + notifyKeyGestureCompleted(event, gestureType); } - private void notifyKeyboardShortcutTriggeredOnActionDown(KeyEvent event, - @KeyboardSystemShortcut.SystemShortcut int systemShortcut) { + private void notifyKeyGestureCompletedOnActionDown(KeyEvent event, + @KeyGestureEvent.KeyGestureType int gestureType) { if (event.getAction() != KeyEvent.ACTION_DOWN) { return; } - notifyKeyboardShortcutTriggered(event, systemShortcut); + notifyKeyGestureCompleted(event, gestureType); } - private void notifyKeyboardShortcutTriggered(KeyEvent event, - @KeyboardSystemShortcut.SystemShortcut int systemShortcut) { - if (systemShortcut == KeyboardSystemShortcut.SYSTEM_SHORTCUT_UNSPECIFIED) { + private void notifyKeyGestureCompleted(KeyEvent event, + @KeyGestureEvent.KeyGestureType int gestureType) { + if (gestureType == KeyGestureEvent.KEY_GESTURE_TYPE_UNSPECIFIED) { return; } - mInputManagerInternal.notifyKeyboardShortcutTriggered(event.getDeviceId(), - new int[]{event.getKeyCode()}, event.getMetaState(), systemShortcut); + mInputManagerInternal.notifyKeyGestureCompleted(event.getDeviceId(), + new int[]{event.getKeyCode()}, event.getMetaState(), gestureType); } @Override @@ -3417,8 +3417,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { case KeyEvent.KEYCODE_RECENT_APPS: if (firstDown) { showRecentApps(false /* triggeredFromAltTab */); - notifyKeyboardShortcutTriggered(event, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_RECENT_APPS); + notifyKeyGestureCompleted(event, + KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS); } return true; case KeyEvent.KEYCODE_APP_SWITCH: @@ -3427,8 +3427,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { preloadRecentApps(); } else if (!down) { toggleRecentApps(); - notifyKeyboardShortcutTriggered(event, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_APP_SWITCH); + notifyKeyGestureCompleted(event, + KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH); } } return true; @@ -3437,8 +3437,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { launchAssistAction(Intent.EXTRA_ASSIST_INPUT_HINT_KEYBOARD, deviceId, event.getEventTime(), AssistUtils.INVOCATION_TYPE_UNKNOWN); - notifyKeyboardShortcutTriggered(event, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_ASSISTANT); + notifyKeyGestureCompleted(event, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT); return true; } break; @@ -3451,16 +3451,16 @@ public class PhoneWindowManager implements WindowManagerPolicy { case KeyEvent.KEYCODE_I: if (firstDown && event.isMetaPressed()) { showSystemSettings(); - notifyKeyboardShortcutTriggered(event, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS); + notifyKeyGestureCompleted(event, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS); return true; } break; case KeyEvent.KEYCODE_L: if (firstDown && event.isMetaPressed()) { lockNow(null /* options */); - notifyKeyboardShortcutTriggered(event, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_LOCK_SCREEN); + notifyKeyGestureCompleted(event, + KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN); return true; } break; @@ -3468,12 +3468,12 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (firstDown && event.isMetaPressed()) { if (event.isCtrlPressed()) { sendSystemKeyToStatusBarAsync(event); - notifyKeyboardShortcutTriggered(event, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_OPEN_NOTES); + notifyKeyGestureCompleted(event, + KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES); } else { toggleNotificationPanel(); - notifyKeyboardShortcutTriggered(event, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL); + notifyKeyGestureCompleted(event, + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL); } return true; } @@ -3481,8 +3481,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { case KeyEvent.KEYCODE_S: if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) { interceptScreenshotChord(SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/); - notifyKeyboardShortcutTriggered(event, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_TAKE_SCREENSHOT); + notifyKeyGestureCompleted(event, + KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT); return true; } break; @@ -3495,16 +3495,16 @@ public class PhoneWindowManager implements WindowManagerPolicy { } catch (RemoteException e) { Slog.d(TAG, "Error taking bugreport", e); } - notifyKeyboardShortcutTriggered(event, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT); + notifyKeyGestureCompleted(event, + KeyGestureEvent.KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT); return true; } } // fall through case KeyEvent.KEYCODE_ESCAPE: if (firstDown && event.isMetaPressed()) { - notifyKeyboardShortcutTriggered(event, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_BACK); + notifyKeyGestureCompleted(event, + KeyGestureEvent.KEY_GESTURE_TYPE_BACK); injectBackGesture(event.getDownTime()); return true; } @@ -3513,8 +3513,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { StatusBarManagerInternal statusbar = getStatusBarManagerInternal(); if (statusbar != null) { statusbar.moveFocusedTaskToFullscreen(getTargetDisplayIdForKeyEvent(event)); - notifyKeyboardShortcutTriggered(event, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION); + notifyKeyGestureCompleted(event, + KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION); return true; } } @@ -3524,8 +3524,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { StatusBarManagerInternal statusbar = getStatusBarManagerInternal(); if (statusbar != null) { statusbar.moveFocusedTaskToDesktop(getTargetDisplayIdForKeyEvent(event)); - notifyKeyboardShortcutTriggered(event, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_DESKTOP_MODE); + notifyKeyGestureCompleted(event, + KeyGestureEvent.KEY_GESTURE_TYPE_DESKTOP_MODE); return true; } } @@ -3535,15 +3535,15 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (event.isCtrlPressed()) { moveFocusedTaskToStageSplit(getTargetDisplayIdForKeyEvent(event), true /* leftOrTop */); - notifyKeyboardShortcutTriggered(event, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION); + notifyKeyGestureCompleted(event, + KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION); } else if (event.isAltPressed()) { setSplitscreenFocus(true /* leftOrTop */); - notifyKeyboardShortcutTriggered(event, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS); + notifyKeyGestureCompleted(event, + KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS); } else { - notifyKeyboardShortcutTriggered(event, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_BACK); + notifyKeyGestureCompleted(event, + KeyGestureEvent.KEY_GESTURE_TYPE_BACK); injectBackGesture(event.getDownTime()); } return true; @@ -3554,13 +3554,13 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (event.isCtrlPressed()) { moveFocusedTaskToStageSplit(getTargetDisplayIdForKeyEvent(event), false /* leftOrTop */); - notifyKeyboardShortcutTriggered(event, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION); + notifyKeyGestureCompleted(event, + KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION); return true; } else if (event.isAltPressed()) { setSplitscreenFocus(false /* leftOrTop */); - notifyKeyboardShortcutTriggered(event, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS); + notifyKeyGestureCompleted(event, + KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS); return true; } } @@ -3568,8 +3568,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { case KeyEvent.KEYCODE_SLASH: if (firstDown && event.isMetaPressed() && !keyguardOn) { toggleKeyboardShortcutsMenu(event.getDeviceId()); - notifyKeyboardShortcutTriggered(event, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER); + notifyKeyGestureCompleted(event, + KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER); return true; } break; @@ -3622,31 +3622,31 @@ public class PhoneWindowManager implements WindowManagerPolicy { intent.putExtra(EXTRA_FROM_BRIGHTNESS_KEY, true); startActivityAsUser(intent, UserHandle.CURRENT_OR_SELF); - int systemShortcut = keyCode == KeyEvent.KEYCODE_BRIGHTNESS_DOWN - ? KeyboardSystemShortcut.SYSTEM_SHORTCUT_BRIGHTNESS_DOWN - : KeyboardSystemShortcut.SYSTEM_SHORTCUT_BRIGHTNESS_UP; - notifyKeyboardShortcutTriggered(event, systemShortcut); + int gestureType = keyCode == KeyEvent.KEYCODE_BRIGHTNESS_DOWN + ? KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_DOWN + : KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_UP; + notifyKeyGestureCompleted(event, gestureType); } return true; case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN: if (down) { mInputManagerInternal.decrementKeyboardBacklight(event.getDeviceId()); - notifyKeyboardShortcutTriggered(event, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN); + notifyKeyGestureCompleted(event, + KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN); } return true; case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP: if (down) { mInputManagerInternal.incrementKeyboardBacklight(event.getDeviceId()); - notifyKeyboardShortcutTriggered(event, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP); + notifyKeyGestureCompleted(event, + KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP); } return true; case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE: // TODO: Add logic if (!down) { - notifyKeyboardShortcutTriggered(event, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE); + notifyKeyGestureCompleted(event, + KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE); } return true; case KeyEvent.KEYCODE_VOLUME_UP: @@ -3673,8 +3673,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (firstDown && !keyguardOn && isUserSetupComplete()) { if (event.isMetaPressed()) { showRecentApps(false); - notifyKeyboardShortcutTriggered(event, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_RECENT_APPS); + notifyKeyGestureCompleted(event, + KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS); return true; } else if (mRecentAppsHeldModifiers == 0) { final int shiftlessModifiers = @@ -3683,8 +3683,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { shiftlessModifiers, KeyEvent.META_ALT_ON)) { mRecentAppsHeldModifiers = shiftlessModifiers; showRecentApps(true); - notifyKeyboardShortcutTriggered(event, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_RECENT_APPS); + notifyKeyGestureCompleted(event, + KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS); return true; } } @@ -3697,20 +3697,20 @@ public class PhoneWindowManager implements WindowManagerPolicy { Message msg = mHandler.obtainMessage(MSG_HANDLE_ALL_APPS); msg.setAsynchronous(true); msg.sendToTarget(); - notifyKeyboardShortcutTriggered(event, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_ALL_APPS); + notifyKeyGestureCompleted(event, + KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS); } else { launchAllAppsViaA11y(); - notifyKeyboardShortcutTriggered(event, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS); + notifyKeyGestureCompleted(event, + KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS); } } return true; case KeyEvent.KEYCODE_NOTIFICATION: if (!down) { toggleNotificationPanel(); - notifyKeyboardShortcutTriggered(event, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL); + notifyKeyGestureCompleted(event, + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL); } return true; case KeyEvent.KEYCODE_SEARCH: @@ -3718,8 +3718,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { switch (mSearchKeyBehavior) { case SEARCH_KEY_BEHAVIOR_TARGET_ACTIVITY: { launchTargetSearchActivity(); - notifyKeyboardShortcutTriggered(event, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_SEARCH); + notifyKeyGestureCompleted(event, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SEARCH); return true; } case SEARCH_KEY_BEHAVIOR_DEFAULT_SEARCH: @@ -3732,8 +3732,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (firstDown) { int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1; sendSwitchKeyboardLayout(event, focusedToken, direction); - notifyKeyboardShortcutTriggered(event, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_LANGUAGE_SWITCH); + notifyKeyGestureCompleted(event, + KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH); return true; } break; @@ -3752,13 +3752,13 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (mPendingCapsLockToggle) { mInputManagerInternal.toggleCapsLock(event.getDeviceId()); mPendingCapsLockToggle = false; - notifyKeyboardShortcutTriggered(event, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK); + notifyKeyGestureCompleted(event, + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK); } else if (mPendingMetaAction) { if (!canceled) { launchAllAppsViaA11y(); - notifyKeyboardShortcutTriggered(event, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS); + notifyKeyGestureCompleted(event, + KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS); } mPendingMetaAction = false; } @@ -3786,16 +3786,16 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (mPendingCapsLockToggle) { mInputManagerInternal.toggleCapsLock(event.getDeviceId()); mPendingCapsLockToggle = false; - notifyKeyboardShortcutTriggered(event, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK); + notifyKeyGestureCompleted(event, + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK); return true; } } break; case KeyEvent.KEYCODE_CAPS_LOCK: if (!down) { - notifyKeyboardShortcutTriggered(event, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK); + notifyKeyGestureCompleted(event, + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK); } break; case KeyEvent.KEYCODE_STYLUS_BUTTON_PRIMARY: @@ -3809,12 +3809,12 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (firstDown) { if (mSettingsKeyBehavior == SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL) { toggleNotificationPanel(); - notifyKeyboardShortcutTriggered(event, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL); + notifyKeyGestureCompleted(event, + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL); } else if (mSettingsKeyBehavior == SETTINGS_KEY_BEHAVIOR_SETTINGS_ACTIVITY) { showSystemSettings(); - notifyKeyboardShortcutTriggered(event, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS); + notifyKeyGestureCompleted(event, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS); } } return true; @@ -4760,8 +4760,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { // Handle special keys. switch (keyCode) { case KeyEvent.KEYCODE_BACK: { - notifyKeyboardShortcutTriggeredOnActionUp(event, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_BACK); + notifyKeyGestureCompletedOnActionUp(event, + KeyGestureEvent.KEY_GESTURE_TYPE_BACK); if (down) { // There may have other embedded activities on the same Task. Try to move the // focus before processing the back event. @@ -4782,12 +4782,12 @@ public class PhoneWindowManager implements WindowManagerPolicy { case KeyEvent.KEYCODE_VOLUME_DOWN: case KeyEvent.KEYCODE_VOLUME_UP: case KeyEvent.KEYCODE_VOLUME_MUTE: { - int systemShortcut = keyCode == KEYCODE_VOLUME_DOWN - ? KeyboardSystemShortcut.SYSTEM_SHORTCUT_VOLUME_DOWN + int gestureType = keyCode == KEYCODE_VOLUME_DOWN + ? KeyGestureEvent.KEY_GESTURE_TYPE_VOLUME_DOWN : keyCode == KEYCODE_VOLUME_UP - ? KeyboardSystemShortcut.SYSTEM_SHORTCUT_VOLUME_UP - : KeyboardSystemShortcut.SYSTEM_SHORTCUT_VOLUME_MUTE; - notifyKeyboardShortcutTriggeredOnActionDown(event, systemShortcut); + ? KeyGestureEvent.KEY_GESTURE_TYPE_VOLUME_UP + : KeyGestureEvent.KEY_GESTURE_TYPE_VOLUME_MUTE; + notifyKeyGestureCompletedOnActionDown(event, gestureType); if (down) { sendSystemKeyToStatusBarAsync(event); @@ -4888,8 +4888,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { } case KeyEvent.KEYCODE_TV_POWER: { - notifyKeyboardShortcutTriggeredOnActionUp(event, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_POWER); + notifyKeyGestureCompletedOnActionUp(event, + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_POWER); result &= ~ACTION_PASS_TO_USER; isWakeKey = false; // wake-up will be handled separately if (down && hdmiControlManager != null) { @@ -4899,8 +4899,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { } case KeyEvent.KEYCODE_POWER: { - notifyKeyboardShortcutTriggeredOnActionUp(event, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_POWER); + notifyKeyGestureCompletedOnActionUp(event, + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_POWER); EventLogTags.writeInterceptPower( KeyEvent.actionToString(event.getAction()), mPowerKeyHandled ? 1 : 0, @@ -4923,16 +4923,16 @@ public class PhoneWindowManager implements WindowManagerPolicy { case KeyEvent.KEYCODE_SYSTEM_NAVIGATION_LEFT: // fall through case KeyEvent.KEYCODE_SYSTEM_NAVIGATION_RIGHT: { - notifyKeyboardShortcutTriggeredOnActionUp(event, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_SYSTEM_NAVIGATION); + notifyKeyGestureCompletedOnActionUp(event, + KeyGestureEvent.KEY_GESTURE_TYPE_SYSTEM_NAVIGATION); result &= ~ACTION_PASS_TO_USER; interceptSystemNavigationKey(event); break; } case KeyEvent.KEYCODE_SLEEP: { - notifyKeyboardShortcutTriggeredOnActionUp(event, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_SLEEP); + notifyKeyGestureCompletedOnActionUp(event, + KeyGestureEvent.KEY_GESTURE_TYPE_SLEEP); result &= ~ACTION_PASS_TO_USER; isWakeKey = false; if (!mPowerManager.isInteractive()) { @@ -4948,8 +4948,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { } case KeyEvent.KEYCODE_SOFT_SLEEP: { - notifyKeyboardShortcutTriggeredOnActionUp(event, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_SLEEP); + notifyKeyGestureCompletedOnActionUp(event, + KeyGestureEvent.KEY_GESTURE_TYPE_SLEEP); result &= ~ACTION_PASS_TO_USER; isWakeKey = false; if (!down) { @@ -4960,8 +4960,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { } case KeyEvent.KEYCODE_WAKEUP: { - notifyKeyboardShortcutTriggeredOnActionUp(event, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_WAKEUP); + notifyKeyGestureCompletedOnActionUp(event, + KeyGestureEvent.KEY_GESTURE_TYPE_WAKEUP); result &= ~ACTION_PASS_TO_USER; isWakeKey = true; break; @@ -4970,8 +4970,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { case KeyEvent.KEYCODE_MUTE: result &= ~ACTION_PASS_TO_USER; if (down && event.getRepeatCount() == 0) { - notifyKeyboardShortcutTriggered(event, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_SYSTEM_MUTE); + notifyKeyGestureCompleted(event, + KeyGestureEvent.KEY_GESTURE_TYPE_SYSTEM_MUTE); toggleMicrophoneMuteFromKey(); } break; @@ -4986,8 +4986,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { case KeyEvent.KEYCODE_MEDIA_RECORD: case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: { - notifyKeyboardShortcutTriggeredOnActionUp(event, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_MEDIA_KEY); + notifyKeyGestureCompletedOnActionUp(event, + KeyGestureEvent.KEY_GESTURE_TYPE_MEDIA_KEY); if (MediaSessionLegacyHelper.getHelper(mContext).isGlobalPriorityActive()) { // If the global session is active pass all media keys to it // instead of the active window. @@ -5032,8 +5032,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { 0 /* unused */, event.getEventTime() /* eventTime */); msg.setAsynchronous(true); msg.sendToTarget(); - notifyKeyboardShortcutTriggered(event, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_ASSISTANT); + notifyKeyGestureCompleted(event, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT); } result &= ~ACTION_PASS_TO_USER; break; @@ -5044,8 +5044,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { Message msg = mHandler.obtainMessage(MSG_LAUNCH_VOICE_ASSIST_WITH_WAKE_LOCK); msg.setAsynchronous(true); msg.sendToTarget(); - notifyKeyboardShortcutTriggered(event, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT); + notifyKeyGestureCompleted(event, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT); } result &= ~ACTION_PASS_TO_USER; break; diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index b22b72627f92..27024a7e7997 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -101,7 +101,6 @@ import android.provider.DeviceConfigInterface; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; import android.service.dreams.DreamManagerInternal; -import android.sysprop.InitProperties; import android.sysprop.PowerProperties; import android.util.ArrayMap; import android.util.IntArray; @@ -132,7 +131,6 @@ import com.android.server.RescueParty; import com.android.server.ServiceThread; import com.android.server.SystemService; import com.android.server.UiThread; -import com.android.server.UserspaceRebootLogger; import com.android.server.Watchdog; import com.android.server.am.BatteryStatsService; import com.android.server.display.feature.DeviceConfigParameterProvider; @@ -1274,8 +1272,7 @@ public final class PowerManagerService extends SystemService mHalInteractiveModeEnabled = true; mWakefulnessRaw = WAKEFULNESS_AWAKE; - sQuiescent = mSystemProperties.get(SYSTEM_PROPERTY_QUIESCENT, "0").equals("1") - || InitProperties.userspace_reboot_in_progress().orElse(false); + sQuiescent = mSystemProperties.get(SYSTEM_PROPERTY_QUIESCENT, "0").equals("1"); mNativeWrapper.nativeInit(this); mNativeWrapper.nativeSetAutoSuspend(false); @@ -4032,7 +4029,6 @@ public final class PowerManagerService extends SystemService throw new UnsupportedOperationException( "Attempted userspace reboot on a device that doesn't support it"); } - UserspaceRebootLogger.noteUserspaceRebootWasRequested(); } if (mHandler == null || !mSystemReady) { if (RescueParty.isRecoveryTriggeredReboot()) { diff --git a/services/core/java/com/android/server/tracing/TracingServiceProxy.java b/services/core/java/com/android/server/tracing/TracingServiceProxy.java index 68eb8eb1deaf..480db25ec606 100644 --- a/services/core/java/com/android/server/tracing/TracingServiceProxy.java +++ b/services/core/java/com/android/server/tracing/TracingServiceProxy.java @@ -76,8 +76,6 @@ public class TracingServiceProxy extends SystemService { // Keep this in sync with the definitions in TraceService private static final String INTENT_ACTION_NOTIFY_SESSION_STOPPED = "com.android.traceur.NOTIFY_SESSION_STOPPED"; - private static final String INTENT_ACTION_NOTIFY_SESSION_STOLEN = - "com.android.traceur.NOTIFY_SESSION_STOLEN"; private static final int REPORT_BEGIN = TRACING_SERVICE_REPORT_EVENT__EVENT__TRACING_SERVICE_REPORT_BEGIN; @@ -97,13 +95,12 @@ public class TracingServiceProxy extends SystemService { private final ITracingServiceProxy.Stub mTracingServiceProxy = new ITracingServiceProxy.Stub() { /** - * Notifies system tracing app that a tracing session has ended. If a session is repurposed - * for use in a bugreport, sessionStolen can be set to indicate that tracing has ended but - * there is no buffer available to dump. + * Notifies system tracing app that a tracing session has ended. sessionStolen is ignored, + * as trace sessions are no longer stolen and are always cloned instead. */ @Override - public void notifyTraceSessionEnded(boolean sessionStolen) { - TracingServiceProxy.this.notifyTraceur(sessionStolen); + public void notifyTraceSessionEnded(boolean sessionStolen /* unused */) { + TracingServiceProxy.this.notifyTraceur(); } @Override @@ -132,7 +129,7 @@ public class TracingServiceProxy extends SystemService { } } - private void notifyTraceur(boolean sessionStolen) { + private void notifyTraceur() { final Intent intent = new Intent(); try { @@ -141,11 +138,7 @@ public class TracingServiceProxy extends SystemService { PackageManager.MATCH_SYSTEM_ONLY); intent.setClassName(info.packageName, TRACING_APP_ACTIVITY); - if (sessionStolen) { - intent.setAction(INTENT_ACTION_NOTIFY_SESSION_STOLEN); - } else { - intent.setAction(INTENT_ACTION_NOTIFY_SESSION_STOPPED); - } + intent.setAction(INTENT_ACTION_NOTIFY_SESSION_STOPPED); final long identity = Binder.clearCallingIdentity(); try { diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java b/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java index 8e375275d080..096231910e6e 100644 --- a/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java +++ b/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java @@ -67,19 +67,19 @@ public final class ClientProfile { /** * The handle of the primary frontend resource */ - private int mPrimaryUsingFrontendHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE; + private long mPrimaryUsingFrontendHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE; /** * List of the frontend handles that are used by the current client. */ - private Set<Integer> mUsingFrontendHandles = new HashSet<>(); + private Set<Long> mUsingFrontendHandles = new HashSet<>(); /** * List of the client ids that share frontend with the current client. */ private Set<Integer> mShareFeClientIds = new HashSet<>(); - private Set<Integer> mUsingDemuxHandles = new HashSet<>(); + private Set<Long> mUsingDemuxHandles = new HashSet<>(); /** * Client id sharee that has shared frontend with the current client. @@ -89,7 +89,7 @@ public final class ClientProfile { /** * List of the Lnb handles that are used by the current client. */ - private Set<Integer> mUsingLnbHandles = new HashSet<>(); + private Set<Long> mUsingLnbHandles = new HashSet<>(); /** * List of the Cas system ids that are used by the current client. @@ -184,7 +184,7 @@ public final class ClientProfile { * * @param frontendHandle being used. */ - public void useFrontend(int frontendHandle) { + public void useFrontend(long frontendHandle) { mUsingFrontendHandles.add(frontendHandle); } @@ -193,14 +193,14 @@ public final class ClientProfile { * * @param frontendHandle being used. */ - public void setPrimaryFrontend(int frontendHandle) { + public void setPrimaryFrontend(long frontendHandle) { mPrimaryUsingFrontendHandle = frontendHandle; } /** * Get the primary frontend used by the client */ - public int getPrimaryFrontend() { + public long getPrimaryFrontend() { return mPrimaryUsingFrontendHandle; } @@ -222,7 +222,7 @@ public final class ClientProfile { mShareFeClientIds.remove(clientId); } - public Set<Integer> getInUseFrontendHandles() { + public Set<Long> getInUseFrontendHandles() { return mUsingFrontendHandles; } @@ -253,14 +253,14 @@ public final class ClientProfile { * * @param demuxHandle the demux being used. */ - public void useDemux(int demuxHandle) { + public void useDemux(long demuxHandle) { mUsingDemuxHandles.add(demuxHandle); } /** * Get the set of demux handles in use. */ - public Set<Integer> getInUseDemuxHandles() { + public Set<Long> getInUseDemuxHandles() { return mUsingDemuxHandles; } @@ -269,7 +269,7 @@ public final class ClientProfile { * * @param demuxHandle the demux handl being released. */ - public void releaseDemux(int demuxHandle) { + public void releaseDemux(long demuxHandle) { mUsingDemuxHandles.remove(demuxHandle); } @@ -278,11 +278,11 @@ public final class ClientProfile { * * @param lnbHandle being used. */ - public void useLnb(int lnbHandle) { + public void useLnb(long lnbHandle) { mUsingLnbHandles.add(lnbHandle); } - public Set<Integer> getInUseLnbHandles() { + public Set<Long> getInUseLnbHandles() { return mUsingLnbHandles; } @@ -291,7 +291,7 @@ public final class ClientProfile { * * @param lnbHandle being released. */ - public void releaseLnb(int lnbHandle) { + public void releaseLnb(long lnbHandle) { mUsingLnbHandles.remove(lnbHandle); } diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/DemuxResource.java b/services/core/java/com/android/server/tv/tunerresourcemanager/DemuxResource.java index df735659c0fe..14bc216d03ef 100644 --- a/services/core/java/com/android/server/tv/tunerresourcemanager/DemuxResource.java +++ b/services/core/java/com/android/server/tv/tunerresourcemanager/DemuxResource.java @@ -69,7 +69,7 @@ public final class DemuxResource extends TunerResourceBasic { public static class Builder extends TunerResourceBasic.Builder { private int mFilterTypes; - Builder(int handle) { + Builder(long handle) { super(handle); } diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/FrontendResource.java b/services/core/java/com/android/server/tv/tunerresourcemanager/FrontendResource.java index 7ef75e3120c5..953d97499c41 100644 --- a/services/core/java/com/android/server/tv/tunerresourcemanager/FrontendResource.java +++ b/services/core/java/com/android/server/tv/tunerresourcemanager/FrontendResource.java @@ -42,7 +42,7 @@ public final class FrontendResource extends TunerResourceBasic { /** * An array to save all the FE handles under the same exclisive group. */ - private Set<Integer> mExclusiveGroupMemberHandles = new HashSet<>(); + private Set<Long> mExclusiveGroupMemberHandles = new HashSet<>(); private FrontendResource(Builder builder) { super(builder); @@ -58,7 +58,7 @@ public final class FrontendResource extends TunerResourceBasic { return mExclusiveGroupId; } - public Set<Integer> getExclusiveGroupMemberFeHandles() { + public Set<Long> getExclusiveGroupMemberFeHandles() { return mExclusiveGroupMemberHandles; } @@ -67,7 +67,7 @@ public final class FrontendResource extends TunerResourceBasic { * * @param handle the handle to be added. */ - public void addExclusiveGroupMemberFeHandle(int handle) { + public void addExclusiveGroupMemberFeHandle(long handle) { mExclusiveGroupMemberHandles.add(handle); } @@ -76,7 +76,7 @@ public final class FrontendResource extends TunerResourceBasic { * * @param handles the handle collection to be added. */ - public void addExclusiveGroupMemberFeHandles(Collection<Integer> handles) { + public void addExclusiveGroupMemberFeHandles(Collection<Long> handles) { mExclusiveGroupMemberHandles.addAll(handles); } @@ -85,7 +85,7 @@ public final class FrontendResource extends TunerResourceBasic { * * @param id the id to be removed. */ - public void removeExclusiveGroupMemberFeId(int handle) { + public void removeExclusiveGroupMemberFeId(long handle) { mExclusiveGroupMemberHandles.remove(handle); } @@ -104,7 +104,7 @@ public final class FrontendResource extends TunerResourceBasic { @Type private int mType; private int mExclusiveGroupId; - Builder(int handle) { + Builder(long handle) { super(handle); } diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/LnbResource.java b/services/core/java/com/android/server/tv/tunerresourcemanager/LnbResource.java index 41cacea5f09e..ab283713b15a 100644 --- a/services/core/java/com/android/server/tv/tunerresourcemanager/LnbResource.java +++ b/services/core/java/com/android/server/tv/tunerresourcemanager/LnbResource.java @@ -37,8 +37,7 @@ public final class LnbResource extends TunerResourceBasic { * Builder class for {@link LnbResource}. */ public static class Builder extends TunerResourceBasic.Builder { - - Builder(int handle) { + Builder(long handle) { super(handle); } diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceBasic.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceBasic.java index 07853fc69055..d2ff8fa7c2e8 100644 --- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceBasic.java +++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceBasic.java @@ -28,7 +28,7 @@ public class TunerResourceBasic { * Handle of the current resource. Should not be changed and should be aligned with the driver * level implementation. */ - final int mHandle; + final long mHandle; /** * If the current resource is in use. @@ -44,7 +44,7 @@ public class TunerResourceBasic { this.mHandle = builder.mHandle; } - public int getHandle() { + public long getHandle() { return mHandle; } @@ -78,9 +78,9 @@ public class TunerResourceBasic { * Builder class for {@link TunerResourceBasic}. */ public static class Builder { - private final int mHandle; + private final long mHandle; - Builder(int handle) { + Builder(long handle) { this.mHandle = handle; } diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java index 0afb049d31c7..45a40edfaf41 100644 --- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java +++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java @@ -78,12 +78,18 @@ public class TunerResourceManagerService extends SystemService implements IBinde private static final int INVALID_FE_COUNT = -1; + private static final int RESOURCE_ID_SHIFT = 24; + private static final int RESOURCE_TYPE_SHIFT = 56; + private static final long RESOURCE_COUNT_MASK = 0xffffff; + private static final long RESOURCE_ID_MASK = 0xffffffff; + private static final long RESOURCE_TYPE_MASK = 0xff; + // Map of the registered client profiles private Map<Integer, ClientProfile> mClientProfiles = new HashMap<>(); private int mNextUnusedClientId = 0; // Map of the current available frontend resources - private Map<Integer, FrontendResource> mFrontendResources = new HashMap<>(); + private Map<Long, FrontendResource> mFrontendResources = new HashMap<>(); // SparseIntArray of the max usable number for each frontend resource type private SparseIntArray mFrontendMaxUsableNums = new SparseIntArray(); // SparseIntArray of the currently used number for each frontend resource type @@ -93,15 +99,15 @@ public class TunerResourceManagerService extends SystemService implements IBinde // Backups for the frontend resource maps for enabling testing with custom resource maps // such as TunerTest.testHasUnusedFrontend1() - private Map<Integer, FrontendResource> mFrontendResourcesBackup = new HashMap<>(); + private Map<Long, FrontendResource> mFrontendResourcesBackup = new HashMap<>(); private SparseIntArray mFrontendMaxUsableNumsBackup = new SparseIntArray(); private SparseIntArray mFrontendUsedNumsBackup = new SparseIntArray(); private SparseIntArray mFrontendExistingNumsBackup = new SparseIntArray(); // Map of the current available demux resources - private Map<Integer, DemuxResource> mDemuxResources = new HashMap<>(); + private Map<Long, DemuxResource> mDemuxResources = new HashMap<>(); // Map of the current available lnb resources - private Map<Integer, LnbResource> mLnbResources = new HashMap<>(); + private Map<Long, LnbResource> mLnbResources = new HashMap<>(); // Map of the current available Cas resources private Map<Integer, CasResource> mCasResources = new HashMap<>(); // Map of the current available CiCam resources @@ -272,7 +278,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde } @Override - public void setLnbInfoList(int[] lnbHandles) throws RemoteException { + public void setLnbInfoList(long[] lnbHandles) throws RemoteException { enforceTrmAccessPermission("setLnbInfoList"); if (lnbHandles == null) { throw new RemoteException("Lnb handle list can't be null"); @@ -283,8 +289,8 @@ public class TunerResourceManagerService extends SystemService implements IBinde } @Override - public boolean requestFrontend(@NonNull TunerFrontendRequest request, - @NonNull int[] frontendHandle) { + public boolean requestFrontend( + @NonNull TunerFrontendRequest request, @NonNull long[] frontendHandle) { enforceTunerAccessPermission("requestFrontend"); enforceTrmAccessPermission("requestFrontend"); if (frontendHandle == null) { @@ -369,8 +375,8 @@ public class TunerResourceManagerService extends SystemService implements IBinde } @Override - public boolean requestDemux(@NonNull TunerDemuxRequest request, - @NonNull int[] demuxHandle) throws RemoteException { + public boolean requestDemux(@NonNull TunerDemuxRequest request, @NonNull long[] demuxHandle) + throws RemoteException { enforceTunerAccessPermission("requestDemux"); enforceTrmAccessPermission("requestDemux"); if (demuxHandle == null) { @@ -387,7 +393,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde @Override public boolean requestDescrambler(@NonNull TunerDescramblerRequest request, - @NonNull int[] descramblerHandle) throws RemoteException { + @NonNull long[] descramblerHandle) throws RemoteException { enforceDescramblerAccessPermission("requestDescrambler"); enforceTrmAccessPermission("requestDescrambler"); if (descramblerHandle == null) { @@ -404,7 +410,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde @Override public boolean requestCasSession(@NonNull CasSessionRequest request, - @NonNull int[] casSessionHandle) throws RemoteException { + @NonNull long[] casSessionHandle) throws RemoteException { enforceTrmAccessPermission("requestCasSession"); if (casSessionHandle == null) { throw new RemoteException("casSessionHandle can't be null"); @@ -419,8 +425,8 @@ public class TunerResourceManagerService extends SystemService implements IBinde } @Override - public boolean requestCiCam(@NonNull TunerCiCamRequest request, - @NonNull int[] ciCamHandle) throws RemoteException { + public boolean requestCiCam(@NonNull TunerCiCamRequest request, @NonNull long[] ciCamHandle) + throws RemoteException { enforceTrmAccessPermission("requestCiCam"); if (ciCamHandle == null) { throw new RemoteException("ciCamHandle can't be null"); @@ -435,7 +441,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde } @Override - public boolean requestLnb(@NonNull TunerLnbRequest request, @NonNull int[] lnbHandle) + public boolean requestLnb(@NonNull TunerLnbRequest request, @NonNull long[] lnbHandle) throws RemoteException { enforceTunerAccessPermission("requestLnb"); enforceTrmAccessPermission("requestLnb"); @@ -452,7 +458,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde } @Override - public void releaseFrontend(int frontendHandle, int clientId) throws RemoteException { + public void releaseFrontend(long frontendHandle, int clientId) throws RemoteException { enforceTunerAccessPermission("releaseFrontend"); enforceTrmAccessPermission("releaseFrontend"); if (!validateResourceHandle(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND, @@ -481,7 +487,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde } @Override - public void releaseDemux(int demuxHandle, int clientId) throws RemoteException { + public void releaseDemux(long demuxHandle, int clientId) throws RemoteException { enforceTunerAccessPermission("releaseDemux"); enforceTrmAccessPermission("releaseDemux"); if (DEBUG) { @@ -512,7 +518,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde } @Override - public void releaseDescrambler(int descramblerHandle, int clientId) { + public void releaseDescrambler(long descramblerHandle, int clientId) { enforceTunerAccessPermission("releaseDescrambler"); enforceTrmAccessPermission("releaseDescrambler"); if (DEBUG) { @@ -521,7 +527,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde } @Override - public void releaseCasSession(int casSessionHandle, int clientId) throws RemoteException { + public void releaseCasSession(long casSessionHandle, int clientId) throws RemoteException { enforceTrmAccessPermission("releaseCasSession"); if (!validateResourceHandle( TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION, casSessionHandle)) { @@ -545,7 +551,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde } @Override - public void releaseCiCam(int ciCamHandle, int clientId) throws RemoteException { + public void releaseCiCam(long ciCamHandle, int clientId) throws RemoteException { enforceTrmAccessPermission("releaseCiCam"); if (!validateResourceHandle( TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM, ciCamHandle)) { @@ -573,7 +579,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde } @Override - public void releaseLnb(int lnbHandle, int clientId) throws RemoteException { + public void releaseLnb(long lnbHandle, int clientId) throws RemoteException { enforceTunerAccessPermission("releaseLnb"); enforceTrmAccessPermission("releaseLnb"); if (!validateResourceHandle(TunerResourceManager.TUNER_RESOURCE_TYPE_LNB, lnbHandle)) { @@ -871,7 +877,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde // A set to record the frontends pending on updating. Ids will be removed // from this set once its updating finished. Any frontend left in this set when all // the updates are done will be removed from mFrontendResources. - Set<Integer> updatingFrontendHandles = new HashSet<>(getFrontendResources().keySet()); + Set<Long> updatingFrontendHandles = new HashSet<>(getFrontendResources().keySet()); // Update frontendResources map and other mappings accordingly for (int i = 0; i < infos.length; i++) { @@ -890,7 +896,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde } } - for (int removingHandle : updatingFrontendHandles) { + for (long removingHandle : updatingFrontendHandles) { // update the exclusive group id member list removeFrontendResource(removingHandle); } @@ -908,7 +914,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde // A set to record the demuxes pending on updating. Ids will be removed // from this set once its updating finished. Any demux left in this set when all // the updates are done will be removed from mDemuxResources. - Set<Integer> updatingDemuxHandles = new HashSet<>(getDemuxResources().keySet()); + Set<Long> updatingDemuxHandles = new HashSet<>(getDemuxResources().keySet()); // Update demuxResources map and other mappings accordingly for (int i = 0; i < infos.length; i++) { @@ -926,13 +932,13 @@ public class TunerResourceManagerService extends SystemService implements IBinde } } - for (int removingHandle : updatingDemuxHandles) { + for (long removingHandle : updatingDemuxHandles) { // update the exclusive group id member list removeDemuxResource(removingHandle); } } @VisibleForTesting - protected void setLnbInfoListInternal(int[] lnbHandles) { + protected void setLnbInfoListInternal(long[] lnbHandles) { if (DEBUG) { for (int i = 0; i < lnbHandles.length; i++) { Slog.d(TAG, "updateLnbInfo(lnbHanle=" + lnbHandles[i] + ")"); @@ -942,7 +948,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde // A set to record the Lnbs pending on updating. Handles will be removed // from this set once its updating finished. Any lnb left in this set when all // the updates are done will be removed from mLnbResources. - Set<Integer> updatingLnbHandles = new HashSet<>(getLnbResources().keySet()); + Set<Long> updatingLnbHandles = new HashSet<>(getLnbResources().keySet()); // Update lnbResources map and other mappings accordingly for (int i = 0; i < lnbHandles.length; i++) { @@ -958,7 +964,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde } } - for (int removingHandle : updatingLnbHandles) { + for (long removingHandle : updatingLnbHandles) { removeLnbResource(removingHandle); } } @@ -1003,7 +1009,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde } @VisibleForTesting - protected boolean requestFrontendInternal(TunerFrontendRequest request, int[] frontendHandle) { + protected boolean requestFrontendInternal(TunerFrontendRequest request, long[] frontendHandle) { if (DEBUG) { Slog.d(TAG, "requestFrontend(request=" + request + ")"); } @@ -1015,8 +1021,8 @@ public class TunerResourceManagerService extends SystemService implements IBinde return false; } clientPriorityUpdateOnRequest(requestClient); - int grantingFrontendHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE; - int inUseLowestPriorityFrHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE; + long grantingFrontendHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE; + long inUseLowestPriorityFrHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE; // Priority max value is 1000 int currentLowestPriority = MAX_CLIENT_PRIORITY + 1; boolean isRequestFromSameProcess = false; @@ -1050,7 +1056,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde // we need to check the max used num if the target frontend type is not // currently in primary use (and simply blocked due to exclusive group) ClientProfile targetOwnerProfile = getClientProfile(fr.getOwnerClientId()); - int primaryFeId = targetOwnerProfile.getPrimaryFrontend(); + long primaryFeId = targetOwnerProfile.getPrimaryFrontend(); FrontendResource primaryFe = getFrontendResource(primaryFeId); if (fr.getType() != primaryFe.getType() && isFrontendMaxNumUseReached(fr.getType())) { @@ -1102,7 +1108,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde getClientProfile(shareeFeClientId).stopSharingFrontend(selfClientId); getClientProfile(selfClientId).releaseFrontend(); } - for (int feId : getClientProfile(targetClientId).getInUseFrontendHandles()) { + for (long feId : getClientProfile(targetClientId).getInUseFrontendHandles()) { getClientProfile(selfClientId).useFrontend(feId); } getClientProfile(selfClientId).setShareeFeClientId(targetClientId); @@ -1117,14 +1123,14 @@ public class TunerResourceManagerService extends SystemService implements IBinde currentOwnerProfile.stopSharingFrontend(newOwnerId); newOwnerProfile.setShareeFeClientId(ClientProfile.INVALID_RESOURCE_ID); currentOwnerProfile.setShareeFeClientId(newOwnerId); - for (int inUseHandle : newOwnerProfile.getInUseFrontendHandles()) { + for (long inUseHandle : newOwnerProfile.getInUseFrontendHandles()) { getFrontendResource(inUseHandle).setOwner(newOwnerId); } // change the primary frontend newOwnerProfile.setPrimaryFrontend(currentOwnerProfile.getPrimaryFrontend()); currentOwnerProfile.setPrimaryFrontend(TunerResourceManager.INVALID_RESOURCE_HANDLE); // double check there is no other resources tied to the previous owner - for (int inUseHandle : currentOwnerProfile.getInUseFrontendHandles()) { + for (long inUseHandle : currentOwnerProfile.getInUseFrontendHandles()) { int ownerId = getFrontendResource(inUseHandle).getOwnerClientId(); if (ownerId != newOwnerId) { Slog.e(TAG, "something is wrong in transferFeOwner:" + inUseHandle @@ -1156,8 +1162,8 @@ public class TunerResourceManagerService extends SystemService implements IBinde ClientProfile currentOwnerProfile = getClientProfile(currentOwnerId); ClientProfile newOwnerProfile = getClientProfile(newOwnerId); - Set<Integer> inUseLnbHandles = new HashSet<>(); - for (Integer lnbHandle : currentOwnerProfile.getInUseLnbHandles()) { + Set<Long> inUseLnbHandles = new HashSet<>(); + for (Long lnbHandle : currentOwnerProfile.getInUseLnbHandles()) { // link lnb handle to the new profile newOwnerProfile.useLnb(lnbHandle); @@ -1169,7 +1175,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde } // unlink lnb handles from the original owner - for (Integer lnbHandle : inUseLnbHandles) { + for (Long lnbHandle : inUseLnbHandles) { currentOwnerProfile.releaseLnb(lnbHandle); } @@ -1192,7 +1198,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde } @VisibleForTesting - protected boolean requestLnbInternal(TunerLnbRequest request, int[] lnbHandle) { + protected boolean requestLnbInternal(TunerLnbRequest request, long[] lnbHandle) { if (DEBUG) { Slog.d(TAG, "requestLnb(request=" + request + ")"); } @@ -1200,8 +1206,8 @@ public class TunerResourceManagerService extends SystemService implements IBinde lnbHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE; ClientProfile requestClient = getClientProfile(request.clientId); clientPriorityUpdateOnRequest(requestClient); - int grantingLnbHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE; - int inUseLowestPriorityLnbHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE; + long grantingLnbHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE; + long inUseLowestPriorityLnbHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE; // Priority max value is 1000 int currentLowestPriority = MAX_CLIENT_PRIORITY + 1; boolean isRequestFromSameProcess = false; @@ -1248,7 +1254,8 @@ public class TunerResourceManagerService extends SystemService implements IBinde } @VisibleForTesting - protected boolean requestCasSessionInternal(CasSessionRequest request, int[] casSessionHandle) { + protected boolean requestCasSessionInternal( + CasSessionRequest request, long[] casSessionHandle) { if (DEBUG) { Slog.d(TAG, "requestCasSession(request=" + request + ")"); } @@ -1301,7 +1308,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde } @VisibleForTesting - protected boolean requestCiCamInternal(TunerCiCamRequest request, int[] ciCamHandle) { + protected boolean requestCiCamInternal(TunerCiCamRequest request, long[] ciCamHandle) { if (DEBUG) { Slog.d(TAG, "requestCiCamInternal(TunerCiCamRequest=" + request + ")"); } @@ -1324,6 +1331,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde ciCamHandle[0] = generateResourceHandle( TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM, ciCam.getCiCamId()); updateCiCamClientMappingOnNewGrant(request.ciCamId, request.clientId); + Slog.e(TAG, "requestCiCamInternal(ciCamHandle=" + ciCamHandle[0] + ")"); return true; } for (int ownerId : ciCam.getOwnerClientIds()) { @@ -1349,6 +1357,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde ciCamHandle[0] = generateResourceHandle( TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM, ciCam.getCiCamId()); updateCiCamClientMappingOnNewGrant(request.ciCamId, request.clientId); + Slog.e(TAG, "requestCiCamInternal(ciCamHandle=" + ciCamHandle[0] + ")"); return true; } return false; @@ -1432,7 +1441,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde } @VisibleForTesting - protected boolean requestDemuxInternal(TunerDemuxRequest request, int[] demuxHandle) { + protected boolean requestDemuxInternal(TunerDemuxRequest request, long[] demuxHandle) { if (DEBUG) { Slog.d(TAG, "requestDemux(request=" + request + ")"); } @@ -1455,8 +1464,8 @@ public class TunerResourceManagerService extends SystemService implements IBinde } clientPriorityUpdateOnRequest(requestClient); - int grantingDemuxHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE; - int inUseLowestPriorityDrHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE; + long grantingDemuxHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE; + long inUseLowestPriorityDrHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE; // Priority max value is 1000 int currentLowestPriority = MAX_CLIENT_PRIORITY + 1; boolean isRequestFromSameProcess = false; @@ -1550,7 +1559,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde @VisibleForTesting protected boolean requestDescramblerInternal( - TunerDescramblerRequest request, int[] descramblerHandle) { + TunerDescramblerRequest request, long[] descramblerHandle) { if (DEBUG) { Slog.d(TAG, "requestDescrambler(request=" + request + ")"); } @@ -1869,20 +1878,20 @@ public class TunerResourceManagerService extends SystemService implements IBinde return false; } - private void updateFrontendClientMappingOnNewGrant(int grantingHandle, int ownerClientId) { + private void updateFrontendClientMappingOnNewGrant(long grantingHandle, int ownerClientId) { FrontendResource grantingFrontend = getFrontendResource(grantingHandle); ClientProfile ownerProfile = getClientProfile(ownerClientId); grantingFrontend.setOwner(ownerClientId); increFrontendNum(mFrontendUsedNums, grantingFrontend.getType()); ownerProfile.useFrontend(grantingHandle); - for (int exclusiveGroupMember : grantingFrontend.getExclusiveGroupMemberFeHandles()) { + for (long exclusiveGroupMember : grantingFrontend.getExclusiveGroupMemberFeHandles()) { getFrontendResource(exclusiveGroupMember).setOwner(ownerClientId); ownerProfile.useFrontend(exclusiveGroupMember); } ownerProfile.setPrimaryFrontend(grantingHandle); } - private void updateDemuxClientMappingOnNewGrant(int grantingHandle, int ownerClientId) { + private void updateDemuxClientMappingOnNewGrant(long grantingHandle, int ownerClientId) { DemuxResource grantingDemux = getDemuxResource(grantingHandle); if (grantingDemux != null) { ClientProfile ownerProfile = getClientProfile(ownerClientId); @@ -1897,7 +1906,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde ownerProfile.releaseDemux(releasingDemux.getHandle()); } - private void updateLnbClientMappingOnNewGrant(int grantingHandle, int ownerClientId) { + private void updateLnbClientMappingOnNewGrant(long grantingHandle, int ownerClientId) { LnbResource grantingLnb = getLnbResource(grantingHandle); ClientProfile ownerProfile = getClientProfile(ownerClientId); grantingLnb.setOwner(ownerClientId); @@ -1981,23 +1990,23 @@ public class TunerResourceManagerService extends SystemService implements IBinde @VisibleForTesting @Nullable - protected FrontendResource getFrontendResource(int frontendHandle) { + protected FrontendResource getFrontendResource(long frontendHandle) { return mFrontendResources.get(frontendHandle); } @VisibleForTesting - protected Map<Integer, FrontendResource> getFrontendResources() { + protected Map<Long, FrontendResource> getFrontendResources() { return mFrontendResources; } @VisibleForTesting @Nullable - protected DemuxResource getDemuxResource(int demuxHandle) { + protected DemuxResource getDemuxResource(long demuxHandle) { return mDemuxResources.get(demuxHandle); } @VisibleForTesting - protected Map<Integer, DemuxResource> getDemuxResources() { + protected Map<Long, DemuxResource> getDemuxResources() { return mDemuxResources; } @@ -2056,8 +2065,8 @@ public class TunerResourceManagerService extends SystemService implements IBinde } } - private void replaceFeResourceMap(Map<Integer, FrontendResource> srcMap, Map<Integer, - FrontendResource> dstMap) { + private void replaceFeResourceMap( + Map<Long, FrontendResource> srcMap, Map<Long, FrontendResource> dstMap) { if (dstMap != null) { dstMap.clear(); if (srcMap != null && srcMap.size() > 0) { @@ -2110,7 +2119,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde if (fe.getExclusiveGroupId() == newFe.getExclusiveGroupId()) { newFe.addExclusiveGroupMemberFeHandle(fe.getHandle()); newFe.addExclusiveGroupMemberFeHandles(fe.getExclusiveGroupMemberFeHandles()); - for (int excGroupmemberFeHandle : fe.getExclusiveGroupMemberFeHandles()) { + for (long excGroupmemberFeHandle : fe.getExclusiveGroupMemberFeHandles()) { getFrontendResource(excGroupmemberFeHandle) .addExclusiveGroupMemberFeHandle(newFe.getHandle()); } @@ -2128,7 +2137,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde mDemuxResources.put(newDemux.getHandle(), newDemux); } - private void removeFrontendResource(int removingHandle) { + private void removeFrontendResource(long removingHandle) { FrontendResource fe = getFrontendResource(removingHandle); if (fe == null) { return; @@ -2140,7 +2149,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde } clearFrontendAndClientMapping(ownerClient); } - for (int excGroupmemberFeHandle : fe.getExclusiveGroupMemberFeHandles()) { + for (long excGroupmemberFeHandle : fe.getExclusiveGroupMemberFeHandles()) { getFrontendResource(excGroupmemberFeHandle) .removeExclusiveGroupMemberFeId(fe.getHandle()); } @@ -2148,7 +2157,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde mFrontendResources.remove(removingHandle); } - private void removeDemuxResource(int removingHandle) { + private void removeDemuxResource(long removingHandle) { DemuxResource demux = getDemuxResource(removingHandle); if (demux == null) { return; @@ -2161,12 +2170,12 @@ public class TunerResourceManagerService extends SystemService implements IBinde @VisibleForTesting @Nullable - protected LnbResource getLnbResource(int lnbHandle) { + protected LnbResource getLnbResource(long lnbHandle) { return mLnbResources.get(lnbHandle); } @VisibleForTesting - protected Map<Integer, LnbResource> getLnbResources() { + protected Map<Long, LnbResource> getLnbResources() { return mLnbResources; } @@ -2175,7 +2184,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde mLnbResources.put(newLnb.getHandle(), newLnb); } - private void removeLnbResource(int removingHandle) { + private void removeLnbResource(long removingHandle) { LnbResource lnb = getLnbResource(removingHandle); if (lnb == null) { return; @@ -2279,7 +2288,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde if (profile == null) { return; } - for (Integer feId : profile.getInUseFrontendHandles()) { + for (Long feId : profile.getInUseFrontendHandles()) { FrontendResource fe = getFrontendResource(feId); int ownerClientId = fe.getOwnerClientId(); if (ownerClientId == profile.getId()) { @@ -2290,10 +2299,9 @@ public class TunerResourceManagerService extends SystemService implements IBinde if (ownerClientProfile != null) { ownerClientProfile.stopSharingFrontend(profile.getId()); } - } - int primaryFeId = profile.getPrimaryFrontend(); + long primaryFeId = profile.getPrimaryFrontend(); if (primaryFeId != TunerResourceManager.INVALID_RESOURCE_HANDLE) { FrontendResource primaryFe = getFrontendResource(primaryFeId); if (primaryFe != null) { @@ -2310,7 +2318,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde return; } // Clear Lnb - for (Integer lnbHandle : profile.getInUseLnbHandles()) { + for (Long lnbHandle : profile.getInUseLnbHandles()) { getLnbResource(lnbHandle).removeOwner(); } // Clear Cas @@ -2322,7 +2330,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde getCiCamResource(profile.getInUseCiCamId()).removeOwner(profile.getId()); } // Clear Demux - for (Integer demuxHandle : profile.getInUseDemuxHandles()) { + for (Long demuxHandle : profile.getInUseDemuxHandles()) { getDemuxResource(demuxHandle).removeOwner(); } // Clear Frontend @@ -2335,24 +2343,31 @@ public class TunerResourceManagerService extends SystemService implements IBinde return mClientProfiles.keySet().contains(clientId); } - private int generateResourceHandle( + /** + * Generate resource handle for resourceType and resourceId + * Resource Handle Allotment : 64 bits (long) + * 8 bits - resourceType + * 32 bits - resourceId + * 24 bits - resourceRequestCount + */ + private long generateResourceHandle( @TunerResourceManager.TunerResourceType int resourceType, int resourceId) { - return (resourceType & 0x000000ff) << 24 - | (resourceId << 16) - | (mResourceRequestCount++ & 0xffff); + return (resourceType & RESOURCE_TYPE_MASK) << RESOURCE_TYPE_SHIFT + | (resourceId & RESOURCE_ID_MASK) << RESOURCE_ID_SHIFT + | (mResourceRequestCount++ & RESOURCE_COUNT_MASK); } @VisibleForTesting - protected int getResourceIdFromHandle(int resourceHandle) { + protected int getResourceIdFromHandle(long resourceHandle) { if (resourceHandle == TunerResourceManager.INVALID_RESOURCE_HANDLE) { - return resourceHandle; + return (int) resourceHandle; } - return (resourceHandle & 0x00ff0000) >> 16; + return (int) ((resourceHandle >> RESOURCE_ID_SHIFT) & RESOURCE_ID_MASK); } - private boolean validateResourceHandle(int resourceType, int resourceHandle) { + private boolean validateResourceHandle(int resourceType, long resourceHandle) { if (resourceHandle == TunerResourceManager.INVALID_RESOURCE_HANDLE - || ((resourceHandle & 0xff000000) >> 24) != resourceType) { + || ((resourceHandle >> RESOURCE_TYPE_SHIFT) & RESOURCE_TYPE_MASK) != resourceType) { return false; } return true; diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java index eccbffb53529..07610872b208 100644 --- a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java +++ b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java @@ -364,10 +364,7 @@ public final class HapticFeedbackVibrationProvider { if ((privFlags & HapticFeedbackConstants.PRIVATE_FLAG_APPLY_INPUT_METHOD_SETTINGS) == 0) { return TOUCH_VIBRATION_ATTRIBUTES; } - return new VibrationAttributes.Builder(IME_FEEDBACK_VIBRATION_ATTRIBUTES) - // TODO(b/332661766): Remove CATEGORY_KEYBOARD logic - .setCategory(VibrationAttributes.CATEGORY_KEYBOARD) - .build(); + return IME_FEEDBACK_VIBRATION_ATTRIBUTES; } @Nullable diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java index 5c567da7844f..aa4b9f3e9d7d 100644 --- a/services/core/java/com/android/server/vibrator/Vibration.java +++ b/services/core/java/com/android/server/vibrator/Vibration.java @@ -282,12 +282,6 @@ abstract class Vibration { VibrationScaler.scaleLevelToString(mScaleLevel), mAdaptiveScale, Long.toBinaryString(mCallerInfo.attrs.getFlags()), mCallerInfo.attrs.usageToString()); - // Optional, most vibrations have category unknown so skip them to simplify the logs - String categoryStr = - mCallerInfo.attrs.getCategory() != VibrationAttributes.CATEGORY_UNKNOWN - ? " | category=" + VibrationAttributes.categoryToString( - mCallerInfo.attrs.getCategory()) - : ""; // Optional, most vibrations should not be defined via AudioAttributes // so skip them to simplify the logs String audioUsageStr = @@ -302,7 +296,7 @@ abstract class Vibration { " | played: %s | original: %s", mPlayedEffect == null ? null : mPlayedEffect.toDebugString(), mOriginalEffect == null ? null : mOriginalEffect.toDebugString()); - pw.println(timingsStr + paramStr + categoryStr + audioUsageStr + callerStr + effectStr); + pw.println(timingsStr + paramStr + audioUsageStr + callerStr + effectStr); } /** Write this info into given {@link PrintWriter}. */ @@ -335,7 +329,6 @@ abstract class Vibration { final VibrationAttributes attrs = mCallerInfo.attrs; proto.write(VibrationAttributesProto.USAGE, attrs.getUsage()); proto.write(VibrationAttributesProto.AUDIO_USAGE, attrs.getAudioUsage()); - proto.write(VibrationAttributesProto.CATEGORY, attrs.getCategory()); proto.write(VibrationAttributesProto.FLAGS, attrs.getFlags()); proto.end(attrsToken); diff --git a/services/core/java/com/android/server/vibrator/VibrationScaler.java b/services/core/java/com/android/server/vibrator/VibrationScaler.java index a74c4e07c9ed..b3862cc51c07 100644 --- a/services/core/java/com/android/server/vibrator/VibrationScaler.java +++ b/services/core/java/com/android/server/vibrator/VibrationScaler.java @@ -134,7 +134,8 @@ final class VibrationScaler { return effect.resolve(mDefaultVibrationAmplitude) .applyEffectStrength(newEffectStrength) .scale(scaleFactor) - .scaleLinearly(adaptiveScale); + // Make sure this is the last one so it is applied on top of the settings scaling. + .applyAdaptiveScale(adaptiveScale); } /** diff --git a/services/core/java/com/android/server/vibrator/VibratorController.java b/services/core/java/com/android/server/vibrator/VibratorController.java index 8cc157c2ed81..4fc0b74ecb80 100644 --- a/services/core/java/com/android/server/vibrator/VibratorController.java +++ b/services/core/java/com/android/server/vibrator/VibratorController.java @@ -279,8 +279,8 @@ final class VibratorController { vendorEffect.getVendorData().writeToParcel(vendorData, /* flags= */ 0); vendorData.setDataPosition(0); long duration = mNativeWrapper.performVendorEffect(vendorData, - vendorEffect.getEffectStrength(), vendorEffect.getLinearScale(), - vibrationId); + vendorEffect.getEffectStrength(), vendorEffect.getScale(), + vendorEffect.getAdaptiveScale(), vibrationId); if (duration > 0) { mCurrentAmplitude = -1; notifyListenerOnVibrating(true); @@ -459,7 +459,7 @@ final class VibratorController { long vibrationId); private static native long performVendorEffect(long nativePtr, Parcel vendorData, - long strength, float scale, long vibrationId); + long strength, float scale, float adaptiveScale, long vibrationId); private static native long performComposedEffect(long nativePtr, PrimitiveSegment[] effect, long vibrationId); @@ -518,8 +518,9 @@ final class VibratorController { /** Turns vibrator on to perform a vendor-specific effect. */ public long performVendorEffect(Parcel vendorData, long strength, float scale, - long vibrationId) { - return performVendorEffect(mNativePtr, vendorData, strength, scale, vibrationId); + float adaptiveScale, long vibrationId) { + return performVendorEffect(mNativePtr, vendorData, strength, scale, adaptiveScale, + vibrationId); } /** Turns vibrator on to perform effect composed of give primitives effect. */ diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java index 2c734127b7ea..10ce8c273736 100644 --- a/services/core/java/com/android/server/wm/AccessibilityController.java +++ b/services/core/java/com/android/server/wm/AccessibilityController.java @@ -1617,8 +1617,7 @@ final class AccessibilityController { // causing the notifying, or the recents/home window is removed, then we won't need the // delayed notification anymore. void onWMTransition(@TransitionType int type, @TransitionFlags int flags) { - if (Flags.delayNotificationToMagnificationWhenRecentsWindowToFrontTransition() - && type == WindowManager.TRANSIT_TO_FRONT + if (type == WindowManager.TRANSIT_TO_FRONT && (flags & TRANSIT_FLAG_IS_RECENTS) != 0) { // Delay the recents to front transition notification then send after if needed. mHasDelayedNotificationForRecentsToFrontTransition = true; @@ -2451,7 +2450,7 @@ final class AccessibilityController { long tokenInner = os.start(WINDOW_MANAGER_SERVICE); synchronized (mService.mGlobalLock) { - mService.dumpDebugLocked(os, WindowTraceLogLevel.ALL); + mService.dumpDebugLocked(os, WindowTracingLogLevel.ALL); } os.end(tokenInner); os.write(CPU_STATS, printCpuStats(reportedTimeStampNanos)); diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index d982925268b1..0bd844192233 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -789,8 +789,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A */ private boolean mWillCloseOrEnterPip; - final LetterboxUiController mLetterboxUiController; - /** * App Compat Facade */ @@ -1324,7 +1322,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A pw.print(prefix); pw.println("mWaitForEnteringPinnedMode=true"); } - mLetterboxUiController.dump(pw, prefix); + mAppCompatController.dump(pw, prefix); } static boolean dumpActivity(FileDescriptor fd, PrintWriter pw, int index, ActivityRecord r, @@ -1988,12 +1986,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } // Don't move below setActivityType since it triggers onConfigurationChange -> - // resolveOverrideConfiguration that requires having mLetterboxUiController initialised. + // resolveOverrideConfiguration that requires having mAppCompatController initialised. // Don't move below setOrientation(info.screenOrientation) since it triggers - // getOverrideOrientation that requires having mLetterboxUiController - // initialised. + // getOverrideOrientation that requires having mAppCompatController initialised. mAppCompatController = new AppCompatController(mWmService, this); - mLetterboxUiController = new LetterboxUiController(mWmService, this); mResolveConfigHint = new TaskFragment.ConfigOverrideHint(); if (mWmService.mFlags.mInsetsDecoupledConfiguration) { // When the stable configuration is the default behavior, override for the legacy apps @@ -7542,6 +7538,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (mStartingWindow == win) { // This could only happen when the window is removed from hierarchy. So do not keep its // reference anymore. + if (mStartingSurface != null) { + // Ensure the reference in client side can be removed. + mStartingSurface.remove(false /* animate */, false /* hasImeSurface */); + } mStartingWindow = null; mStartingData = null; mStartingSurface = null; @@ -10332,7 +10332,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * Write all fields to an {@code ActivityRecordProto}. This assumes the * {@code ActivityRecordProto} is the outer-most proto data. */ - void dumpDebug(ProtoOutputStream proto, @WindowTraceLogLevel int logLevel) { + void dumpDebug(ProtoOutputStream proto, @WindowTracingLogLevel int logLevel) { writeNameToProto(proto, NAME); super.dumpDebug(proto, WINDOW_TOKEN, logLevel); proto.write(LAST_SURFACE_SHOWING, mLastSurfaceShowing); @@ -10410,9 +10410,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @Override public void dumpDebug(ProtoOutputStream proto, long fieldId, - @WindowTraceLogLevel int logLevel) { + @WindowTracingLogLevel int logLevel) { // Critical log level logs only visible elements to mitigate performance overheard - if (logLevel == WindowTraceLogLevel.CRITICAL && !isVisible()) { + if (logLevel == WindowTracingLogLevel.CRITICAL && !isVisible()) { return; } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 5bb4a8adbd82..3d5808210e00 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -3771,6 +3771,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { // Shell calls back into Core with the entry bounds to be applied with startWCT. final Transition enterPipTransition = new Transition(TRANSIT_PIP, 0 /* flags */, getTransitionController(), mWindowManager.mSyncEngine); + r.setPictureInPictureParams(params); enterPipTransition.setPipActivity(r); r.mAutoEnteringPip = isAutoEnter; getTransitionController().startCollectOrQueue(enterPipTransition, (deferred) -> { @@ -6784,7 +6785,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { synchronized (mGlobalLock) { // The output proto of "activity --proto activities" mRootWindowContainer.dumpDebug( - proto, ROOT_WINDOW_CONTAINER, WindowTraceLogLevel.ALL); + proto, ROOT_WINDOW_CONTAINER, WindowTracingLogLevel.ALL); } } diff --git a/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java b/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java index 1562cf64ad96..a42b8794b43d 100644 --- a/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java +++ b/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java @@ -24,6 +24,7 @@ import android.content.pm.ActivityInfo.ScreenOrientation; import android.content.res.Configuration; import android.widget.Toast; +import com.android.internal.annotations.VisibleForTesting; import com.android.window.flags.Flags; /** @@ -32,7 +33,8 @@ import com.android.window.flags.Flags; class AppCompatCameraPolicy { @Nullable - private final CameraStateMonitor mCameraStateMonitor; + @VisibleForTesting + final CameraStateMonitor mCameraStateMonitor; @Nullable private final ActivityRefresher mActivityRefresher; @Nullable @@ -122,6 +124,9 @@ class AppCompatCameraPolicy { } void start() { + if (mDisplayRotationCompatPolicy != null) { + mDisplayRotationCompatPolicy.start(); + } if (mCameraCompatFreeformPolicy != null) { mCameraCompatFreeformPolicy.start(); } @@ -150,6 +155,10 @@ class AppCompatCameraPolicy { return mCameraCompatFreeformPolicy != null; } + boolean hasCameraStateMonitor() { + return mCameraStateMonitor != null; + } + @ScreenOrientation int getOrientation() { return mDisplayRotationCompatPolicy != null diff --git a/services/core/java/com/android/server/wm/AppCompatConfiguration.java b/services/core/java/com/android/server/wm/AppCompatConfiguration.java index ffa4251d0ded..42378aaf6c05 100644 --- a/services/core/java/com/android/server/wm/AppCompatConfiguration.java +++ b/services/core/java/com/android/server/wm/AppCompatConfiguration.java @@ -30,6 +30,7 @@ import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.server.wm.utils.DimenPxIntSupplier; +import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.function.Function; @@ -1382,6 +1383,26 @@ final class AppCompatConfiguration { setUserAppAspectRatioFullscreenOverrideEnabled(false); } + void dump(@NonNull PrintWriter pw, @NonNull String prefix) { + // TODO(b/359438445): Add more useful information to dump(). + pw.println(prefix + " letterboxPositionForHorizontalReachability=" + + letterboxHorizontalReachabilityPositionToString( + getLetterboxPositionForHorizontalReachability( + /* isInFullScreenBookMode */ false))); + pw.println(prefix + " letterboxPositionForVerticalReachability=" + + letterboxVerticalReachabilityPositionToString( + getLetterboxPositionForVerticalReachability( + /* isInFullScreenTabletopMode */ false))); + pw.println(prefix + " fixedOrientationLetterboxAspectRatio=" + + getFixedOrientationLetterboxAspectRatio()); + pw.println(prefix + " defaultMinAspectRatioForUnresizableApps=" + + getDefaultMinAspectRatioForUnresizableApps()); + pw.println(prefix + " isSplitScreenAspectRatioForUnresizableAppsEnabled=" + + getIsSplitScreenAspectRatioForUnresizableAppsEnabled()); + pw.println(prefix + " isDisplayAspectRatioEnabledForFixedOrientationLetterbox=" + + getIsDisplayAspectRatioEnabledForFixedOrientationLetterbox()); + } + /** * Checks whether the multiplier is between [0,1]. * diff --git a/services/core/java/com/android/server/wm/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java index 42900512de5d..906ee201d8e8 100644 --- a/services/core/java/com/android/server/wm/AppCompatController.java +++ b/services/core/java/com/android/server/wm/AppCompatController.java @@ -21,6 +21,8 @@ import android.content.pm.PackageManager; import com.android.server.wm.utils.OptPropFactory; +import java.io.PrintWriter; + /** * Allows the interaction with all the app compat policies and configurations */ @@ -59,7 +61,8 @@ class AppCompatController { mTransparentPolicy, mAppCompatOverrides); mAppCompatReachabilityPolicy = new AppCompatReachabilityPolicy(mActivityRecord, wmService.mAppCompatConfiguration); - mAppCompatLetterboxPolicy = new AppCompatLetterboxPolicy(mActivityRecord); + mAppCompatLetterboxPolicy = new AppCompatLetterboxPolicy(mActivityRecord, + wmService.mAppCompatConfiguration); } @NonNull @@ -140,4 +143,9 @@ class AppCompatController { return mAppCompatOverrides.getAppCompatLetterboxOverrides(); } + void dump(@NonNull PrintWriter pw, @NonNull String prefix) { + getTransparentPolicy().dump(pw, prefix); + getAppCompatLetterboxPolicy().dump(pw, prefix); + } + } diff --git a/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java b/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java index d602c47d26c0..afc6506f9091 100644 --- a/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java +++ b/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java @@ -21,6 +21,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_WALLPAPER; +import static com.android.server.wm.AppCompatConfiguration.letterboxBackgroundTypeToString; import android.annotation.NonNull; import android.annotation.Nullable; @@ -32,6 +33,8 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.LetterboxDetails; import com.android.server.wm.AppCompatConfiguration.LetterboxBackgroundType; +import java.io.PrintWriter; + /** * Encapsulates the logic for the Letterboxing policy. */ @@ -43,15 +46,19 @@ class AppCompatLetterboxPolicy { private final LetterboxPolicyState mLetterboxPolicyState; @NonNull private final AppCompatRoundedCorners mAppCompatRoundedCorners; + @NonNull + private final AppCompatConfiguration mAppCompatConfiguration; private boolean mLastShouldShowLetterboxUi; - AppCompatLetterboxPolicy(@NonNull ActivityRecord activityRecord) { + AppCompatLetterboxPolicy(@NonNull ActivityRecord activityRecord, + @NonNull AppCompatConfiguration appCompatConfiguration) { mActivityRecord = activityRecord; mLetterboxPolicyState = new LetterboxPolicyState(); // TODO (b/358334569) Improve cutout logic dependency on app compat. mAppCompatRoundedCorners = new AppCompatRoundedCorners(mActivityRecord, this::isLetterboxedNotForDisplayCutout); + mAppCompatConfiguration = appCompatConfiguration; } /** Cleans up {@link Letterbox} if it exists.*/ @@ -156,6 +163,38 @@ class AppCompatLetterboxPolicy { return mAppCompatRoundedCorners.getRoundedCornersRadius(mainWindow); } + void dump(@NonNull PrintWriter pw, @NonNull String prefix) { + final WindowState mainWin = mActivityRecord.findMainWindow(); + if (mainWin == null) { + return; + } + boolean areBoundsLetterboxed = mainWin.areAppWindowBoundsLetterboxed(); + pw.println(prefix + "areBoundsLetterboxed=" + areBoundsLetterboxed); + pw.println(prefix + "isLetterboxRunning=" + isRunning()); + if (!areBoundsLetterboxed) { + return; + } + pw.println(prefix + " letterboxReason=" + + AppCompatUtils.getLetterboxReasonString(mActivityRecord, mainWin)); + mActivityRecord.mAppCompatController.getAppCompatReachabilityPolicy().dump(pw, prefix); + final AppCompatLetterboxOverrides letterboxOverride = mActivityRecord.mAppCompatController + .getAppCompatLetterboxOverrides(); + pw.println(prefix + " letterboxBackgroundColor=" + Integer.toHexString( + letterboxOverride.getLetterboxBackgroundColor().toArgb())); + pw.println(prefix + " letterboxBackgroundType=" + + letterboxBackgroundTypeToString(letterboxOverride.getLetterboxBackgroundType())); + pw.println(prefix + " letterboxCornerRadius=" + getRoundedCornersRadius(mainWin)); + if (letterboxOverride.getLetterboxBackgroundType() == LETTERBOX_BACKGROUND_WALLPAPER) { + pw.println(prefix + " isLetterboxWallpaperBlurSupported=" + + letterboxOverride.isLetterboxWallpaperBlurSupported()); + pw.println(prefix + " letterboxBackgroundWallpaperDarkScrimAlpha=" + + letterboxOverride.getLetterboxWallpaperDarkScrimAlpha()); + pw.println(prefix + " letterboxBackgroundWallpaperBlurRadius=" + + letterboxOverride.getLetterboxWallpaperBlurRadiusPx()); + } + mAppCompatConfiguration.dump(pw, prefix); + } + private void updateWallpaperForLetterbox(@NonNull WindowState mainWindow) { final AppCompatLetterboxOverrides letterboxOverrides = mActivityRecord .mAppCompatController.getAppCompatLetterboxOverrides(); diff --git a/services/core/java/com/android/server/wm/AppCompatReachabilityPolicy.java b/services/core/java/com/android/server/wm/AppCompatReachabilityPolicy.java index c3bf116e227d..d03a80387657 100644 --- a/services/core/java/com/android/server/wm/AppCompatReachabilityPolicy.java +++ b/services/core/java/com/android/server/wm/AppCompatReachabilityPolicy.java @@ -33,6 +33,7 @@ import android.graphics.Rect; import com.android.internal.annotations.VisibleForTesting; +import java.io.PrintWriter; import java.util.function.Supplier; /** @@ -74,6 +75,25 @@ class AppCompatReachabilityPolicy { handleVerticalDoubleTap(y); } + void dump(@NonNull PrintWriter pw, @NonNull String prefix) { + final AppCompatReachabilityOverrides reachabilityOverrides = + mActivityRecord.mAppCompatController.getAppCompatReachabilityOverrides(); + pw.println(prefix + " isVerticalThinLetterboxed=" + reachabilityOverrides + .isVerticalThinLetterboxed()); + pw.println(prefix + " isHorizontalThinLetterboxed=" + reachabilityOverrides + .isHorizontalThinLetterboxed()); + pw.println(prefix + " isHorizontalReachabilityEnabled=" + + reachabilityOverrides.isHorizontalReachabilityEnabled()); + pw.println(prefix + " isVerticalReachabilityEnabled=" + + reachabilityOverrides.isVerticalReachabilityEnabled()); + pw.println(prefix + " letterboxHorizontalPositionMultiplier=" + + reachabilityOverrides.getHorizontalPositionMultiplier( + mActivityRecord.getParent().getConfiguration())); + pw.println(prefix + " letterboxVerticalPositionMultiplier=" + + reachabilityOverrides.getVerticalPositionMultiplier( + mActivityRecord.getParent().getConfiguration())); + } + private void handleHorizontalDoubleTap(int x) { final AppCompatReachabilityOverrides reachabilityOverrides = mActivityRecord.mAppCompatController.getAppCompatReachabilityOverrides(); diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java index 02c8a4999f4d..20c5f02aaee2 100644 --- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java +++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java @@ -1506,10 +1506,13 @@ public class BackgroundActivityStartController { PackageManager pm = mService.mContext.getPackageManager(); ApplicationInfo applicationInfo; + final int sourceUserId = UserHandle.getUserId(sourceUid); try { - applicationInfo = pm.getApplicationInfo(packageName, 0); + applicationInfo = pm.getApplicationInfoAsUser(packageName, /* flags= */ 0, + sourceUserId); } catch (PackageManager.NameNotFoundException e) { - Slog.wtf(TAG, "Package name: " + packageName + " not found."); + Slog.wtf(TAG, "Package name: " + packageName + " not found for user " + + sourceUserId); return bas.optedIn(ar); } diff --git a/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java b/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java index 9b142f280b31..dda39a6a12da 100644 --- a/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java +++ b/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java @@ -60,6 +60,11 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa @Nullable private Task mCameraTask; + /** + * Value toggled on {@link #start()} to {@code true} and on {@link #dispose()} to {@code false}. + */ + private boolean mIsRunning; + CameraCompatFreeformPolicy(@NonNull DisplayContent displayContent, @NonNull CameraStateMonitor cameraStateMonitor, @NonNull ActivityRefresher activityRefresher) { @@ -71,12 +76,19 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa void start() { mCameraStateMonitor.addCameraStateListener(this); mActivityRefresher.addEvaluator(this); + mIsRunning = true; } /** Releases camera callback listener. */ void dispose() { mCameraStateMonitor.removeCameraStateListener(this); mActivityRefresher.removeEvaluator(this); + mIsRunning = false; + } + + @VisibleForTesting + boolean isRunning() { + return mIsRunning; } // Refreshing only when configuration changes after rotation or camera split screen aspect ratio diff --git a/services/core/java/com/android/server/wm/CameraStateMonitor.java b/services/core/java/com/android/server/wm/CameraStateMonitor.java index 63c90ff93224..8bfef6d5d218 100644 --- a/services/core/java/com/android/server/wm/CameraStateMonitor.java +++ b/services/core/java/com/android/server/wm/CameraStateMonitor.java @@ -26,6 +26,7 @@ import android.os.Handler; import android.util.ArraySet; import android.util.Slog; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.ProtoLog; import java.util.ArrayList; @@ -73,6 +74,12 @@ class CameraStateMonitor { private final ArrayList<CameraCompatStateListener> mCameraStateListeners = new ArrayList<>(); + /** + * Value toggled on {@link #startListeningToCameraState()} to {@code true} and on {@link + * #dispose()} to {@code false}. + */ + private boolean mIsRunning; + private final CameraManager.AvailabilityCallback mAvailabilityCallback = new CameraManager.AvailabilityCallback() { @Override @@ -101,6 +108,7 @@ class CameraStateMonitor { void startListeningToCameraState() { mCameraManager.registerAvailabilityCallback( mWmService.mContext.getMainExecutor(), mAvailabilityCallback); + mIsRunning = true; } /** Releases camera callback listener. */ @@ -108,6 +116,12 @@ class CameraStateMonitor { if (mCameraManager != null) { mCameraManager.unregisterAvailabilityCallback(mAvailabilityCallback); } + mIsRunning = false; + } + + @VisibleForTesting + boolean isRunning() { + return mIsRunning; } void addCameraStateListener(CameraCompatStateListener listener) { diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java index 3ebaf03c4a31..9be3f436b819 100644 --- a/services/core/java/com/android/server/wm/ConfigurationContainer.java +++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java @@ -255,8 +255,18 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { inOutConfig.windowConfiguration.setAppBounds( newParentConfiguration.windowConfiguration.getBounds()); outAppBounds = inOutConfig.windowConfiguration.getAppBounds(); - outAppBounds.inset(displayContent.getDisplayPolicy() - .getDecorInsetsInfo(rotation, dw, dh).mOverrideNonDecorInsets); + if (inOutConfig.windowConfiguration.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) { + final DisplayPolicy.DecorInsets.Info decor = + displayContent.getDisplayPolicy().getDecorInsetsInfo(rotation, dw, dh); + if (outAppBounds.contains(decor.mOverrideNonDecorFrame)) { + outAppBounds.intersect(decor.mOverrideNonDecorFrame); + } + } else { + // TODO(b/358509380): Handle other windowing mode like split screen and freeform + // cases correctly. + outAppBounds.inset(displayContent.getDisplayPolicy() + .getDecorInsetsInfo(rotation, dw, dh).mOverrideNonDecorInsets); + } } float density = inOutConfig.densityDpi; if (density == Configuration.DENSITY_DPI_UNDEFINED) { @@ -807,23 +817,23 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { */ @CallSuper protected void dumpDebug(ProtoOutputStream proto, long fieldId, - @WindowTraceLogLevel int logLevel) { + @WindowTracingLogLevel int logLevel) { final long token = proto.start(fieldId); - if (logLevel == WindowTraceLogLevel.ALL || mHasOverrideConfiguration) { + if (logLevel == WindowTracingLogLevel.ALL || mHasOverrideConfiguration) { mRequestedOverrideConfiguration.dumpDebug(proto, OVERRIDE_CONFIGURATION, - logLevel == WindowTraceLogLevel.CRITICAL); + logLevel == WindowTracingLogLevel.CRITICAL); } // Unless trace level is set to `WindowTraceLogLevel.ALL` don't dump anything that isn't // required to mitigate performance overhead - if (logLevel == WindowTraceLogLevel.ALL) { + if (logLevel == WindowTracingLogLevel.ALL) { mFullConfiguration.dumpDebug(proto, FULL_CONFIGURATION, false /* critical */); mMergedOverrideConfiguration.dumpDebug(proto, MERGED_OVERRIDE_CONFIGURATION, false /* critical */); } - if (logLevel == WindowTraceLogLevel.TRIM) { + if (logLevel == WindowTracingLogLevel.TRIM) { // Required for Fass to automatically detect pip transitions in Winscope traces dumpDebugWindowingMode(proto); } diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java index 86f69cd3a802..ca5485e7c570 100644 --- a/services/core/java/com/android/server/wm/DisplayArea.java +++ b/services/core/java/com/android/server/wm/DisplayArea.java @@ -356,7 +356,7 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> { @Override public void dumpDebug(ProtoOutputStream proto, long fieldId, int logLevel) { - if (logLevel == WindowTraceLogLevel.CRITICAL && !isVisible()) { + if (logLevel == WindowTracingLogLevel.CRITICAL && !isVisible()) { return; } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index c44e1b13e246..648f6bda7f98 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -3565,9 +3565,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp @Override public void dumpDebug(ProtoOutputStream proto, long fieldId, - @WindowTraceLogLevel int logLevel) { + @WindowTracingLogLevel int logLevel) { // Critical log level logs only visible elements to mitigate performance overheard - if (logLevel == WindowTraceLogLevel.CRITICAL && !isVisible()) { + if (logLevel == WindowTracingLogLevel.CRITICAL && !isVisible()) { return; } diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java index 762180b1b778..27d97677bb13 100644 --- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java @@ -76,6 +76,11 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp @ScreenOrientation private int mLastReportedOrientation = SCREEN_ORIENTATION_UNSET; + /** + * Value toggled on {@link #start()} to {@code true} and on {@link #dispose()} to {@code false}. + */ + private boolean mIsRunning; + DisplayRotationCompatPolicy(@NonNull DisplayContent displayContent, @NonNull CameraStateMonitor cameraStateMonitor, @NonNull ActivityRefresher activityRefresher) { @@ -90,12 +95,19 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp void start() { mCameraStateMonitor.addCameraStateListener(this); mActivityRefresher.addEvaluator(this); + mIsRunning = true; } /** Releases camera state listener. */ void dispose() { mCameraStateMonitor.removeCameraStateListener(this); mActivityRefresher.removeEvaluator(this); + mIsRunning = false; + } + + @VisibleForTesting + boolean isRunning() { + return mIsRunning; } /** diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettings.java b/services/core/java/com/android/server/wm/DisplayWindowSettings.java index 2f0ee171b5ba..f40f26179f85 100644 --- a/services/core/java/com/android/server/wm/DisplayWindowSettings.java +++ b/services/core/java/com/android/server/wm/DisplayWindowSettings.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.view.Display.REMOVE_MODE_DESTROY_CONTENT; import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY; import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL; import static android.view.WindowManager.REMOVE_CONTENT_MODE_DESTROY; @@ -183,7 +184,7 @@ class DisplayWindowSettings { final DisplayInfo displayInfo = dc.getDisplayInfo(); final SettingsProvider.SettingsEntry settings = mSettingsProvider.getSettings(displayInfo); if (settings.mRemoveContentMode == REMOVE_CONTENT_MODE_UNDEFINED) { - if (dc.isPrivate()) { + if (dc.isPrivate() || dc.getDisplay().getRemoveMode() == REMOVE_MODE_DESTROY_CONTENT) { // For private displays by default content is destroyed on removal. return REMOVE_CONTENT_MODE_DESTROY; } diff --git a/services/core/java/com/android/server/wm/EmbeddedWindowController.java b/services/core/java/com/android/server/wm/EmbeddedWindowController.java index c66d6596226a..169a76fe3afd 100644 --- a/services/core/java/com/android/server/wm/EmbeddedWindowController.java +++ b/services/core/java/com/android/server/wm/EmbeddedWindowController.java @@ -403,7 +403,7 @@ class EmbeddedWindowController { @Override public void dumpProto(ProtoOutputStream proto, long fieldId, - @WindowTraceLogLevel int logLevel) { + @WindowTracingLogLevel int logLevel) { final long token = proto.start(fieldId); final long token2 = proto.start(IDENTIFIER); diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java index 3a5f9b7dd4fc..6b916ef04e38 100644 --- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java @@ -726,7 +726,7 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { } @Override - void dumpDebug(ProtoOutputStream proto, long fieldId, @WindowTraceLogLevel int logLevel) { + void dumpDebug(ProtoOutputStream proto, long fieldId, @WindowTracingLogLevel int logLevel) { final long token = proto.start(fieldId); super.dumpDebug(proto, INSETS_SOURCE_PROVIDER, logLevel); final WindowState imeRequesterWindow = diff --git a/services/core/java/com/android/server/wm/InputTarget.java b/services/core/java/com/android/server/wm/InputTarget.java index baf0db2e0b7e..0c0b794182e7 100644 --- a/services/core/java/com/android/server/wm/InputTarget.java +++ b/services/core/java/com/android/server/wm/InputTarget.java @@ -65,6 +65,6 @@ interface InputTarget { InsetsControlTarget getImeControlTarget(); void dumpProto(ProtoOutputStream proto, long fieldId, - @WindowTraceLogLevel int logLevel); + @WindowTracingLogLevel int logLevel); } diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index f5c92f60b26a..b66b8bc2115b 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -725,7 +725,7 @@ class InsetsSourceProvider { } } - void dumpDebug(ProtoOutputStream proto, long fieldId, @WindowTraceLogLevel int logLevel) { + void dumpDebug(ProtoOutputStream proto, long fieldId, @WindowTracingLogLevel int logLevel) { final long token = proto.start(fieldId); mSource.dumpDebug(proto, SOURCE); mTmpRect.dumpDebug(proto, FRAME); diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java index 9c2a8def4d56..098a691e0490 100644 --- a/services/core/java/com/android/server/wm/InsetsStateController.java +++ b/services/core/java/com/android/server/wm/InsetsStateController.java @@ -462,7 +462,7 @@ class InsetsStateController { } } - void dumpDebug(ProtoOutputStream proto, @WindowTraceLogLevel int logLevel) { + void dumpDebug(ProtoOutputStream proto, @WindowTracingLogLevel int logLevel) { for (int i = mProviders.size() - 1; i >= 0; i--) { final InsetsSourceProvider provider = mProviders.valueAt(i); provider.dumpDebug(proto, diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java deleted file mode 100644 index 0e8291ee9492..000000000000 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm; - -import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_WALLPAPER; -import static com.android.server.wm.AppCompatConfiguration.letterboxBackgroundTypeToString; - -import android.annotation.NonNull; - -import java.io.PrintWriter; - -/** Controls behaviour of the letterbox UI for {@link mActivityRecord}. */ -// TODO(b/185262487): Improve test coverage of this class. Parts of it are tested in -// SizeCompatTests and LetterboxTests but not all. -final class LetterboxUiController { - - private final AppCompatConfiguration mAppCompatConfiguration; - - private final ActivityRecord mActivityRecord; - - // TODO(b/356385137): Remove these we added to make dependencies temporarily explicit. - @NonNull - private final AppCompatReachabilityOverrides mAppCompatReachabilityOverrides; - @NonNull - private final AppCompatLetterboxPolicy mAppCompatLetterboxPolicy; - @NonNull - private final AppCompatLetterboxOverrides mAppCompatLetterboxOverrides; - - LetterboxUiController(WindowManagerService wmService, ActivityRecord activityRecord) { - mAppCompatConfiguration = wmService.mAppCompatConfiguration; - // Given activityRecord may not be fully constructed since LetterboxUiController - // is created in its constructor. It shouldn't be used in this constructor but it's safe - // to use it after since controller is only used in ActivityRecord. - mActivityRecord = activityRecord; - // TODO(b/356385137): Remove these we added to make dependencies temporarily explicit. - mAppCompatReachabilityOverrides = mActivityRecord.mAppCompatController - .getAppCompatReachabilityOverrides(); - mAppCompatLetterboxPolicy = mActivityRecord.mAppCompatController - .getAppCompatLetterboxPolicy(); - mAppCompatLetterboxOverrides = mActivityRecord.mAppCompatController - .getAppCompatLetterboxOverrides(); - } - - void dump(PrintWriter pw, String prefix) { - final WindowState mainWin = mActivityRecord.findMainWindow(); - if (mainWin == null) { - return; - } - - pw.println(prefix + "isTransparentPolicyRunning=" - + mActivityRecord.mAppCompatController.getTransparentPolicy().isRunning()); - - boolean areBoundsLetterboxed = mainWin.areAppWindowBoundsLetterboxed(); - pw.println(prefix + "areBoundsLetterboxed=" + areBoundsLetterboxed); - if (!areBoundsLetterboxed) { - return; - } - - pw.println(prefix + " letterboxReason=" - + AppCompatUtils.getLetterboxReasonString(mActivityRecord, mainWin)); - pw.println(prefix + " activityAspectRatio=" - + AppCompatUtils.computeAspectRatio(mActivityRecord.getBounds())); - - boolean shouldShowLetterboxUi = mAppCompatLetterboxPolicy.shouldShowLetterboxUi(mainWin); - pw.println(prefix + "shouldShowLetterboxUi=" + shouldShowLetterboxUi); - - if (!shouldShowLetterboxUi) { - return; - } - pw.println(prefix + " isVerticalThinLetterboxed=" - + mAppCompatReachabilityOverrides.isVerticalThinLetterboxed()); - pw.println(prefix + " isHorizontalThinLetterboxed=" - + mAppCompatReachabilityOverrides.isHorizontalThinLetterboxed()); - pw.println(prefix + " letterboxBackgroundColor=" + Integer.toHexString( - mAppCompatLetterboxOverrides.getLetterboxBackgroundColor().toArgb())); - pw.println(prefix + " letterboxBackgroundType=" - + letterboxBackgroundTypeToString( - mAppCompatConfiguration.getLetterboxBackgroundType())); - pw.println(prefix + " letterboxCornerRadius=" - + mAppCompatLetterboxPolicy.getRoundedCornersRadius(mainWin)); - if (mAppCompatConfiguration.getLetterboxBackgroundType() - == LETTERBOX_BACKGROUND_WALLPAPER) { - pw.println(prefix + " isLetterboxWallpaperBlurSupported=" - + mAppCompatLetterboxOverrides.isLetterboxWallpaperBlurSupported()); - pw.println(prefix + " letterboxBackgroundWallpaperDarkScrimAlpha=" - + mAppCompatLetterboxOverrides.getLetterboxWallpaperDarkScrimAlpha()); - pw.println(prefix + " letterboxBackgroundWallpaperBlurRadius=" - + mAppCompatLetterboxOverrides.getLetterboxWallpaperBlurRadiusPx()); - } - final AppCompatReachabilityOverrides reachabilityOverrides = mActivityRecord - .mAppCompatController.getAppCompatReachabilityOverrides(); - pw.println(prefix + " isHorizontalReachabilityEnabled=" - + reachabilityOverrides.isHorizontalReachabilityEnabled()); - pw.println(prefix + " isVerticalReachabilityEnabled=" - + reachabilityOverrides.isVerticalReachabilityEnabled()); - pw.println(prefix + " letterboxHorizontalPositionMultiplier=" - + mAppCompatReachabilityOverrides.getHorizontalPositionMultiplier(mActivityRecord - .getParent().getConfiguration())); - pw.println(prefix + " letterboxVerticalPositionMultiplier=" - + mAppCompatReachabilityOverrides.getVerticalPositionMultiplier(mActivityRecord - .getParent().getConfiguration())); - pw.println(prefix + " letterboxPositionForHorizontalReachability=" - + AppCompatConfiguration.letterboxHorizontalReachabilityPositionToString( - mAppCompatConfiguration.getLetterboxPositionForHorizontalReachability(false))); - pw.println(prefix + " letterboxPositionForVerticalReachability=" - + AppCompatConfiguration.letterboxVerticalReachabilityPositionToString( - mAppCompatConfiguration.getLetterboxPositionForVerticalReachability(false))); - pw.println(prefix + " fixedOrientationLetterboxAspectRatio=" - + mAppCompatConfiguration.getFixedOrientationLetterboxAspectRatio()); - pw.println(prefix + " defaultMinAspectRatioForUnresizableApps=" - + mAppCompatConfiguration.getDefaultMinAspectRatioForUnresizableApps()); - pw.println(prefix + " isSplitScreenAspectRatioForUnresizableAppsEnabled=" - + mAppCompatConfiguration.getIsSplitScreenAspectRatioForUnresizableAppsEnabled()); - pw.println(prefix + " isDisplayAspectRatioEnabledForFixedOrientationLetterbox=" - + mAppCompatConfiguration - .getIsDisplayAspectRatioEnabledForFixedOrientationLetterbox()); - } -} diff --git a/services/core/java/com/android/server/wm/OWNERS b/services/core/java/com/android/server/wm/OWNERS index 60454fc5d7c6..781023c688c3 100644 --- a/services/core/java/com/android/server/wm/OWNERS +++ b/services/core/java/com/android/server/wm/OWNERS @@ -30,3 +30,4 @@ per-file ActivityCallerState.java = file:/core/java/android/app/COMPONENT_CALLER # Files related to tracing per-file *TransitionTracer.java = file:platform/development:/tools/winscope/OWNERS +per-file *WindowTracing* = file:platform/development:/tools/winscope/OWNERS diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 6427c3229b50..b528e205eae6 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -1189,8 +1189,8 @@ class RootWindowContainer extends WindowContainer<DisplayContent> @Override public void dumpDebug(ProtoOutputStream proto, long fieldId, - @WindowTraceLogLevel int logLevel) { - if (logLevel == WindowTraceLogLevel.CRITICAL && !isVisible()) { + @WindowTracingLogLevel int logLevel) { + if (logLevel == WindowTracingLogLevel.CRITICAL && !isVisible()) { return; } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 3f0c9fd0bfec..efa9c5345f77 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -4705,6 +4705,15 @@ class Task extends TaskFragment { // it does not follow the ActivityStarter path. if (topActivity.shouldBeVisible()) { mAtmService.resumeAppSwitches(); + // In pip1, when expanding pip to full-screen, the "behind" task is not + // actually becoming invisible since task windowing mode is pinned. + if (!isPip2ExperimentEnabled) { + final ActivityRecord ar = mAtmService.mLastResumedActivity; + if (ar != null && ar.getTask() != null) { + mAtmService.takeTaskSnapshot(ar.getTask().mTaskId, + true /* updateCache */); + } + } } } else if (isPip2ExperimentEnabled) { super.setWindowingMode(windowingMode); @@ -6262,8 +6271,8 @@ class Task extends TaskFragment { @Override public void dumpDebug(ProtoOutputStream proto, long fieldId, - @WindowTraceLogLevel int logLevel) { - if (logLevel == WindowTraceLogLevel.CRITICAL && !isVisible()) { + @WindowTracingLogLevel int logLevel) { + if (logLevel == WindowTracingLogLevel.CRITICAL && !isVisible()) { return; } diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 329d11bfcb95..2fbabc51c534 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -1780,11 +1780,6 @@ class TaskFragment extends WindowContainer<WindowContainer> { if (resuming != null) { // We do not want to trigger auto-PiP upon launch of a translucent activity. final boolean resumingOccludesParent = resuming.occludesParent(); - // Resuming the new resume activity only if the previous activity can't go into Pip - // since we want to give Pip activities a chance to enter Pip before resuming the - // next activity. - final boolean lastResumedCanPip = prev.checkEnterPictureInPictureState( - "shouldAutoPipWhilePausing", userLeaving); if (ActivityTaskManagerService.isPip2ExperimentEnabled()) { // If a new task is being launched, then mark the existing top activity as @@ -1794,6 +1789,12 @@ class TaskFragment extends WindowContainer<WindowContainer> { Task.enableEnterPipOnTaskSwitch(prev, resuming.getTask(), resuming, resuming.getOptions()); } + + // Resuming the new resume activity only if the previous activity can't go into Pip + // since we want to give Pip activities a chance to enter Pip before resuming the + // next activity. + final boolean lastResumedCanPip = prev.checkEnterPictureInPictureState( + "shouldAutoPipWhilePausing", userLeaving); if (prev.supportsEnterPipOnTaskSwitch && userLeaving && resumingOccludesParent && lastResumedCanPip && prev.pictureInPictureArgs.isAutoEnterEnabled()) { @@ -3334,8 +3335,8 @@ class TaskFragment extends WindowContainer<WindowContainer> { @Override public void dumpDebug(ProtoOutputStream proto, long fieldId, - @WindowTraceLogLevel int logLevel) { - if (logLevel == WindowTraceLogLevel.CRITICAL && !isVisible()) { + @WindowTracingLogLevel int logLevel) { + if (logLevel == WindowTracingLogLevel.CRITICAL && !isVisible()) { return; } diff --git a/services/core/java/com/android/server/wm/TransparentPolicy.java b/services/core/java/com/android/server/wm/TransparentPolicy.java index 39b2635eb8ac..36bc84635e94 100644 --- a/services/core/java/com/android/server/wm/TransparentPolicy.java +++ b/services/core/java/com/android/server/wm/TransparentPolicy.java @@ -31,6 +31,7 @@ import android.annotation.Nullable; import android.content.res.Configuration; import android.graphics.Rect; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -188,6 +189,10 @@ class TransparentPolicy { return mTransparentPolicyState.findOpaqueNotFinishingActivityBelow(); } + void dump(@NonNull PrintWriter pw, @NonNull String prefix) { + pw.println(prefix + "isTransparentPolicyRunning=" + isRunning()); + } + // We evaluate the case when the policy should not be applied. private boolean shouldSkipTransparentPolicy(@Nullable ActivityRecord opaqueActivity) { if (opaqueActivity == null || opaqueActivity.isEmbedded()) { diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 15d67eb61a43..9ae881bc8745 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -2784,9 +2784,9 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< @CallSuper @Override public void dumpDebug(ProtoOutputStream proto, long fieldId, - @WindowTraceLogLevel int logLevel) { + @WindowTracingLogLevel int logLevel) { boolean isVisible = isVisible(); - if (logLevel == WindowTraceLogLevel.CRITICAL && !isVisible) { + if (logLevel == WindowTracingLogLevel.CRITICAL && !isVisible) { return; } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 243ab3a6c574..bdb1d43faf79 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -6858,7 +6858,7 @@ public class WindowManagerService extends IWindowManager.Stub * @param proto Stream to write the WindowContainer object to. * @param logLevel Determines the amount of data to be written to the Protobuf. */ - void dumpDebugLocked(ProtoOutputStream proto, @WindowTraceLogLevel int logLevel) { + void dumpDebugLocked(ProtoOutputStream proto, @WindowTracingLogLevel int logLevel) { mPolicy.dumpDebug(proto, POLICY); mRoot.dumpDebug(proto, ROOT_WINDOW_CONTAINER, logLevel); final DisplayContent topFocusedDisplayContent = mRoot.getTopFocusedDisplayContent(); @@ -7217,7 +7217,7 @@ public class WindowManagerService extends IWindowManager.Stub if (useProto) { final ProtoOutputStream proto = new ProtoOutputStream(fd); synchronized (mGlobalLock) { - dumpDebugLocked(proto, WindowTraceLogLevel.ALL); + dumpDebugLocked(proto, WindowTracingLogLevel.ALL); } proto.flush(); return; diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index b6e8977d2e45..923ad4bb51bb 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -4072,9 +4072,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP @CallSuper @Override public void dumpDebug(ProtoOutputStream proto, long fieldId, - @WindowTraceLogLevel int logLevel) { + @WindowTracingLogLevel int logLevel) { boolean isVisible = isVisible(); - if (logLevel == WindowTraceLogLevel.CRITICAL && !isVisible) { + if (logLevel == WindowTracingLogLevel.CRITICAL && !isVisible) { return; } @@ -6140,7 +6140,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP @Override public void dumpProto(ProtoOutputStream proto, long fieldId, - @WindowTraceLogLevel int logLevel) { + @WindowTracingLogLevel int logLevel) { dumpDebug(proto, fieldId, logLevel); } diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java index 11ef2cde65a9..67bd5cb833ae 100644 --- a/services/core/java/com/android/server/wm/WindowToken.java +++ b/services/core/java/com/android/server/wm/WindowToken.java @@ -698,8 +698,8 @@ class WindowToken extends WindowContainer<WindowState> { @CallSuper @Override public void dumpDebug(ProtoOutputStream proto, long fieldId, - @WindowTraceLogLevel int logLevel) { - if (logLevel == WindowTraceLogLevel.CRITICAL && !isVisible()) { + @WindowTracingLogLevel int logLevel) { + if (logLevel == WindowTracingLogLevel.CRITICAL && !isVisible()) { return; } diff --git a/services/core/java/com/android/server/wm/WindowTracing.java b/services/core/java/com/android/server/wm/WindowTracing.java index 04d5c03e64a6..fe267261b9e6 100644 --- a/services/core/java/com/android/server/wm/WindowTracing.java +++ b/services/core/java/com/android/server/wm/WindowTracing.java @@ -112,7 +112,7 @@ abstract class WindowTracing { saveForBugreportInternal(pw); } - abstract void setLogLevel(@WindowTraceLogLevel int logLevel, PrintWriter pw); + abstract void setLogLevel(@WindowTracingLogLevel int logLevel, PrintWriter pw); abstract void setLogFrequency(boolean onFrame, PrintWriter pw); abstract void setBufferCapacity(int capacity, PrintWriter pw); abstract boolean isEnabled(); @@ -158,7 +158,7 @@ abstract class WindowTracing { * @param where Logging point descriptor * @param elapsedRealtimeNanos Timestamp */ - protected void dumpToProto(ProtoOutputStream os, @WindowTraceLogLevel int logLevel, + protected void dumpToProto(ProtoOutputStream os, @WindowTracingLogLevel int logLevel, String where, long elapsedRealtimeNanos) { Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "traceStateLocked"); try { diff --git a/services/core/java/com/android/server/wm/WindowTracingDataSource.java b/services/core/java/com/android/server/wm/WindowTracingDataSource.java index 3d2c0d3f79b9..6984f0dbab72 100644 --- a/services/core/java/com/android/server/wm/WindowTracingDataSource.java +++ b/services/core/java/com/android/server/wm/WindowTracingDataSource.java @@ -50,12 +50,14 @@ public final class WindowTracingDataSource extends DataSource<WindowTracingDataS } public static class Config { - public final @WindowTraceLogLevel int mLogLevel; - public final boolean mLogOnFrame; + public final @WindowTracingLogLevel int mLogLevel; + public final @WindowTracingLogFrequency int mLogFrequency; - private Config(@WindowTraceLogLevel int logLevel, boolean logOnFrame) { + private Config( + @WindowTracingLogLevel int logLevel, + @WindowTracingLogFrequency int logFrequency) { mLogLevel = logLevel; - mLogOnFrame = logOnFrame; + mLogFrequency = logFrequency; } } @@ -68,7 +70,8 @@ public final class WindowTracingDataSource extends DataSource<WindowTracingDataS } } - private static final Config CONFIG_DEFAULT = new Config(WindowTraceLogLevel.TRIM, true); + private static final Config CONFIG_DEFAULT = + new Config(WindowTracingLogLevel.TRIM, WindowTracingLogFrequency.FRAME); private static final int CONFIG_VALUE_UNSPECIFIED = 0; private static final String TAG = "WindowTracingDataSource"; @@ -160,45 +163,48 @@ public final class WindowTracingDataSource extends DataSource<WindowTracingDataS throw new RuntimeException("Failed to parse WindowManagerConfig", e); } - @WindowTraceLogLevel int logLevel; + @WindowTracingLogLevel int logLevel; switch(parsedLogLevel) { case CONFIG_VALUE_UNSPECIFIED: Log.w(TAG, "Unspecified log level. Defaulting to TRIM"); - logLevel = WindowTraceLogLevel.TRIM; + logLevel = WindowTracingLogLevel.TRIM; break; case WindowManagerConfig.LOG_LEVEL_VERBOSE: - logLevel = WindowTraceLogLevel.ALL; + logLevel = WindowTracingLogLevel.ALL; break; case WindowManagerConfig.LOG_LEVEL_DEBUG: - logLevel = WindowTraceLogLevel.TRIM; + logLevel = WindowTracingLogLevel.TRIM; break; case WindowManagerConfig.LOG_LEVEL_CRITICAL: - logLevel = WindowTraceLogLevel.CRITICAL; + logLevel = WindowTracingLogLevel.CRITICAL; break; default: Log.w(TAG, "Unrecognized log level. Defaulting to TRIM"); - logLevel = WindowTraceLogLevel.TRIM; + logLevel = WindowTracingLogLevel.TRIM; break; } - boolean logOnFrame; + @WindowTracingLogFrequency int logFrequency; switch(parsedLogFrequency) { case CONFIG_VALUE_UNSPECIFIED: - Log.w(TAG, "Unspecified log frequency. Defaulting to 'log on frame'"); - logOnFrame = true; + Log.w(TAG, "Unspecified log frequency. Defaulting to 'frame'"); + logFrequency = WindowTracingLogFrequency.FRAME; break; case WindowManagerConfig.LOG_FREQUENCY_FRAME: - logOnFrame = true; + logFrequency = WindowTracingLogFrequency.FRAME; break; case WindowManagerConfig.LOG_FREQUENCY_TRANSACTION: - logOnFrame = false; + logFrequency = WindowTracingLogFrequency.TRANSACTION; + break; + case WindowManagerConfig.LOG_FREQUENCY_SINGLE_DUMP: + logFrequency = WindowTracingLogFrequency.SINGLE_DUMP; break; default: - Log.w(TAG, "Unrecognized log frequency. Defaulting to 'log on frame'"); - logOnFrame = true; + Log.w(TAG, "Unrecognized log frequency. Defaulting to 'frame'"); + logFrequency = WindowTracingLogFrequency.FRAME; break; } - return new Config(logLevel, logOnFrame); + return new Config(logLevel, logFrequency); } } diff --git a/services/core/java/com/android/server/wm/WindowTracingLegacy.java b/services/core/java/com/android/server/wm/WindowTracingLegacy.java index 7a36707fd95a..34fd088cc2e1 100644 --- a/services/core/java/com/android/server/wm/WindowTracingLegacy.java +++ b/services/core/java/com/android/server/wm/WindowTracingLegacy.java @@ -30,6 +30,7 @@ import android.util.Log; import android.util.proto.ProtoOutputStream; import android.view.Choreographer; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.TraceBuffer; import java.io.File; @@ -58,7 +59,7 @@ class WindowTracingLegacy extends WindowTracing { private boolean mEnabled; private volatile boolean mEnabledLockFree; - protected @WindowTraceLogLevel int mLogLevel = WindowTraceLogLevel.TRIM; + protected @WindowTracingLogLevel int mLogLevel = WindowTracingLogLevel.TRIM; protected boolean mLogOnFrame = false; WindowTracingLegacy(WindowManagerService service, Choreographer choreographer) { @@ -66,6 +67,7 @@ class WindowTracingLegacy extends WindowTracing { service.mGlobalLock, BUFFER_CAPACITY_TRIM); } + @VisibleForTesting WindowTracingLegacy(File traceFile, WindowManagerService service, Choreographer choreographer, WindowManagerGlobalLock globalLock, int bufferSize) { super(service, choreographer, globalLock); @@ -74,20 +76,20 @@ class WindowTracingLegacy extends WindowTracing { } @Override - void setLogLevel(@WindowTraceLogLevel int logLevel, PrintWriter pw) { + void setLogLevel(@WindowTracingLogLevel int logLevel, PrintWriter pw) { logAndPrintln(pw, "Setting window tracing log level to " + logLevel); mLogLevel = logLevel; switch (logLevel) { - case WindowTraceLogLevel.ALL: { + case WindowTracingLogLevel.ALL: { setBufferCapacity(BUFFER_CAPACITY_ALL, pw); break; } - case WindowTraceLogLevel.TRIM: { + case WindowTracingLogLevel.TRIM: { setBufferCapacity(BUFFER_CAPACITY_TRIM, pw); break; } - case WindowTraceLogLevel.CRITICAL: { + case WindowTracingLogLevel.CRITICAL: { setBufferCapacity(BUFFER_CAPACITY_CRITICAL, pw); break; } @@ -141,19 +143,19 @@ class WindowTracingLegacy extends WindowTracing { String logLevelStr = shell.getNextArgRequired().toLowerCase(); switch (logLevelStr) { case "all": { - setLogLevel(WindowTraceLogLevel.ALL, pw); + setLogLevel(WindowTracingLogLevel.ALL, pw); break; } case "trim": { - setLogLevel(WindowTraceLogLevel.TRIM, pw); + setLogLevel(WindowTracingLogLevel.TRIM, pw); break; } case "critical": { - setLogLevel(WindowTraceLogLevel.CRITICAL, pw); + setLogLevel(WindowTracingLogLevel.CRITICAL, pw); break; } default: { - setLogLevel(WindowTraceLogLevel.TRIM, pw); + setLogLevel(WindowTracingLogLevel.TRIM, pw); break; } } diff --git a/services/core/java/com/android/server/wm/WindowTracingLogFrequency.java b/services/core/java/com/android/server/wm/WindowTracingLogFrequency.java new file mode 100644 index 000000000000..8e2c308837fb --- /dev/null +++ b/services/core/java/com/android/server/wm/WindowTracingLogFrequency.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import android.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@IntDef({ + WindowTracingLogFrequency.FRAME, + WindowTracingLogFrequency.TRANSACTION, + WindowTracingLogFrequency.SINGLE_DUMP, +}) +@Retention(RetentionPolicy.SOURCE) +@interface WindowTracingLogFrequency { + /** + * Trace state snapshots when a frame is committed. + */ + int FRAME = 0; + /** + * Trace state snapshots when a transaction is committed. + */ + int TRANSACTION = 1; + /** + * Trace single state snapshots when the Perfetto data source is started. + */ + int SINGLE_DUMP = 2; +} diff --git a/services/core/java/com/android/server/wm/WindowTraceLogLevel.java b/services/core/java/com/android/server/wm/WindowTracingLogLevel.java index 2165c66a10e8..4f901c6f5727 100644 --- a/services/core/java/com/android/server/wm/WindowTraceLogLevel.java +++ b/services/core/java/com/android/server/wm/WindowTracingLogLevel.java @@ -22,12 +22,12 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @IntDef({ - WindowTraceLogLevel.ALL, - WindowTraceLogLevel.TRIM, - WindowTraceLogLevel.CRITICAL, + WindowTracingLogLevel.ALL, + WindowTracingLogLevel.TRIM, + WindowTracingLogLevel.CRITICAL, }) @Retention(RetentionPolicy.SOURCE) -@interface WindowTraceLogLevel{ +@interface WindowTracingLogLevel { /** * Logs all elements with maximum amount of information. * diff --git a/services/core/java/com/android/server/wm/WindowTracingPerfetto.java b/services/core/java/com/android/server/wm/WindowTracingPerfetto.java index 653b6dac1537..cf948ca76f99 100644 --- a/services/core/java/com/android/server/wm/WindowTracingPerfetto.java +++ b/services/core/java/com/android/server/wm/WindowTracingPerfetto.java @@ -25,6 +25,8 @@ import android.util.Log; import android.util.proto.ProtoOutputStream; import android.view.Choreographer; +import com.android.internal.annotations.VisibleForTesting; + import java.io.PrintWriter; import java.util.concurrent.atomic.AtomicInteger; @@ -37,11 +39,17 @@ class WindowTracingPerfetto extends WindowTracing { this::onStart, this::onStop); WindowTracingPerfetto(WindowManagerService service, Choreographer choreographer) { - super(service, choreographer, service.mGlobalLock); + this(service, choreographer, service.mGlobalLock); + } + + @VisibleForTesting + WindowTracingPerfetto(WindowManagerService service, Choreographer choreographer, + WindowManagerGlobalLock globalLock) { + super(service, choreographer, globalLock); } @Override - void setLogLevel(@WindowTraceLogLevel int logLevel, PrintWriter pw) { + void setLogLevel(@WindowTracingLogLevel int logLevel, PrintWriter pw) { logAndPrintln(pw, "Log level must be configured through perfetto"); } @@ -110,7 +118,15 @@ class WindowTracingPerfetto extends WindowTracing { if (!isDataSourceStarting) { return; } - } else if (isOnFrameLogEvent != dataSourceConfig.mLogOnFrame) { + } else if (isOnFrameLogEvent) { + boolean isDataSourceLoggingOnFrame = + dataSourceConfig.mLogFrequency == WindowTracingLogFrequency.FRAME; + if (!isDataSourceLoggingOnFrame) { + return; + } + } else if (dataSourceConfig.mLogFrequency + == WindowTracingLogFrequency.SINGLE_DUMP) { + // If it is a dump, write only the start log event and skip the following ones return; } @@ -141,21 +157,21 @@ class WindowTracingPerfetto extends WindowTracing { } private void onStart(WindowTracingDataSource.Config config) { - if (config.mLogOnFrame) { + if (config.mLogFrequency == WindowTracingLogFrequency.FRAME) { mCountSessionsOnFrame.incrementAndGet(); - } else { + } else if (config.mLogFrequency == WindowTracingLogFrequency.TRANSACTION) { mCountSessionsOnTransaction.incrementAndGet(); } Log.i(TAG, "Started with logLevel: " + config.mLogLevel - + " logOnFrame: " + config.mLogOnFrame); + + " logFrequency: " + config.mLogFrequency); log(WHERE_START_TRACING); } private void onStop(WindowTracingDataSource.Config config) { - if (config.mLogOnFrame) { + if (config.mLogFrequency == WindowTracingLogFrequency.FRAME) { mCountSessionsOnFrame.decrementAndGet(); - } else { + } else if (config.mLogFrequency == WindowTracingLogFrequency.TRANSACTION) { mCountSessionsOnTransaction.decrementAndGet(); } Log.i(TAG, "Stopped"); diff --git a/services/core/jni/com_android_server_vibrator_VibratorController.cpp b/services/core/jni/com_android_server_vibrator_VibratorController.cpp index f12930a49ecb..5c5ac28b2169 100644 --- a/services/core/jni/com_android_server_vibrator_VibratorController.cpp +++ b/services/core/jni/com_android_server_vibrator_VibratorController.cpp @@ -198,7 +198,8 @@ static Aidl::CompositeEffect effectFromJavaPrimitive(JNIEnv* env, jobject primit } static Aidl::VendorEffect vendorEffectFromJavaParcel(JNIEnv* env, jobject vendorData, - jlong strength, jfloat scale) { + jlong strength, jfloat scale, + jfloat adaptiveScale) { PersistableBundle bundle; if (AParcel* parcel = AParcel_fromJavaParcel(env, vendorData); parcel != nullptr) { if (binder_status_t status = bundle.readFromParcel(parcel); status == STATUS_OK) { @@ -217,6 +218,7 @@ static Aidl::VendorEffect vendorEffectFromJavaParcel(JNIEnv* env, jobject vendor effect.vendorData = bundle; effect.strength = static_cast<Aidl::EffectStrength>(strength); effect.scale = static_cast<float>(scale); + effect.vendorScale = static_cast<float>(adaptiveScale); return effect; } @@ -319,13 +321,14 @@ static jlong vibratorPerformEffect(JNIEnv* env, jclass /* clazz */, jlong ptr, j static jlong vibratorPerformVendorEffect(JNIEnv* env, jclass /* clazz */, jlong ptr, jobject vendorData, jlong strength, jfloat scale, - jlong vibrationId) { + jfloat adaptiveScale, jlong vibrationId) { VibratorControllerWrapper* wrapper = reinterpret_cast<VibratorControllerWrapper*>(ptr); if (wrapper == nullptr) { ALOGE("vibratorPerformVendorEffect failed because native wrapper was not initialized"); return -1; } - Aidl::VendorEffect effect = vendorEffectFromJavaParcel(env, vendorData, strength, scale); + Aidl::VendorEffect effect = + vendorEffectFromJavaParcel(env, vendorData, strength, scale, adaptiveScale); auto callback = wrapper->createCallback(vibrationId); auto performVendorEffectFn = [&effect, &callback](vibrator::HalWrapper* hal) { return hal->performVendorEffect(effect, callback); @@ -511,7 +514,7 @@ static const JNINativeMethod method_table[] = { {"off", "(J)V", (void*)vibratorOff}, {"setAmplitude", "(JF)V", (void*)vibratorSetAmplitude}, {"performEffect", "(JJJJ)J", (void*)vibratorPerformEffect}, - {"performVendorEffect", "(JLandroid/os/Parcel;JFJ)J", (void*)vibratorPerformVendorEffect}, + {"performVendorEffect", "(JLandroid/os/Parcel;JFFJ)J", (void*)vibratorPerformVendorEffect}, {"performComposedEffect", "(J[Landroid/os/vibrator/PrimitiveSegment;J)J", (void*)vibratorPerformComposedEffect}, {"performPwleEffect", "(J[Landroid/os/vibrator/RampSegment;IJ)J", diff --git a/services/proguard.flags b/services/proguard.flags index 35e27f8ee4af..cdd41abf6c7c 100644 --- a/services/proguard.flags +++ b/services/proguard.flags @@ -114,6 +114,13 @@ -keep public class android.os.** { *; } -keep public class com.android.internal.util.** { *; } -keep public class com.android.modules.utils.build.** { *; } +# Also suppress related duplicate type warnings for the above kept classes. +-dontwarn android.gsi.** +-dontwarn android.hidl.base.** +-dontwarn android.hidl.manager.** +-dontwarn android.os.** +-dontwarn com.android.internal.util.** +-dontwarn com.android.modules.utils.build.** # CoverageService guards optional jacoco class references with a runtime guard, so we can safely # suppress build-time warnings. diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java index ee79d196cfd9..5e240cf66674 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java @@ -23,6 +23,7 @@ import static android.view.Display.Mode.INVALID_MODE_ID; import static com.android.server.display.mode.DisplayModeDirector.SYNCHRONIZED_REFRESH_RATE_TOLERANCE; import static com.android.server.display.mode.Vote.PRIORITY_LIMIT_MODE; import static com.android.server.display.mode.Vote.PRIORITY_SYNCHRONIZED_REFRESH_RATE; +import static com.android.server.display.mode.Vote.PRIORITY_SYNCHRONIZED_RENDER_FRAME_RATE; import static com.android.server.display.mode.Vote.PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE; import static com.android.server.display.mode.VotesStorage.GLOBAL_ID; @@ -360,16 +361,22 @@ public class DisplayObserverTest { .thenReturn(true); init(); assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_REFRESH_RATE)).isEqualTo(null); + assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_RENDER_FRAME_RATE)).isEqualTo(null); mObserver.onDisplayAdded(EXTERNAL_DISPLAY); assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_REFRESH_RATE)).isEqualTo( Vote.forPhysicalRefreshRates( MAX_REFRESH_RATE - SYNCHRONIZED_REFRESH_RATE_TOLERANCE, MAX_REFRESH_RATE + SYNCHRONIZED_REFRESH_RATE_TOLERANCE)); + assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_RENDER_FRAME_RATE)).isEqualTo( + Vote.forRenderFrameRates( + MAX_REFRESH_RATE - SYNCHRONIZED_REFRESH_RATE_TOLERANCE, + MAX_REFRESH_RATE + SYNCHRONIZED_REFRESH_RATE_TOLERANCE)); // Remove external display and check that sync vote is no longer present. mObserver.onDisplayRemoved(EXTERNAL_DISPLAY); assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_REFRESH_RATE)).isEqualTo(null); + assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_RENDER_FRAME_RATE)).isEqualTo(null); } /** External display added, disabled feature refresh rates synchronization */ @@ -383,8 +390,10 @@ public class DisplayObserverTest { .thenReturn(true); init(); assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_REFRESH_RATE)).isEqualTo(null); + assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_RENDER_FRAME_RATE)).isEqualTo(null); mObserver.onDisplayAdded(EXTERNAL_DISPLAY); assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_REFRESH_RATE)).isEqualTo(null); + assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_RENDER_FRAME_RATE)).isEqualTo(null); } /** External display not applied refresh rates synchronization, because @@ -397,8 +406,10 @@ public class DisplayObserverTest { when(mDisplayManagerFlags.isDisplaysRefreshRatesSynchronizationEnabled()).thenReturn(true); init(); assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_REFRESH_RATE)).isEqualTo(null); + assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_RENDER_FRAME_RATE)).isEqualTo(null); mObserver.onDisplayAdded(EXTERNAL_DISPLAY); assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_REFRESH_RATE)).isEqualTo(null); + assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_RENDER_FRAME_RATE)).isEqualTo(null); } private void init() { diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp index a86289b317d0..701c3508be8a 100644 --- a/services/tests/servicestests/Android.bp +++ b/services/tests/servicestests/Android.bp @@ -272,6 +272,10 @@ java_genrule { "$(location soong_zip) -o $(out) -C $(genDir)/res -D $(genDir)/res", } +FLAKY = [ + "androidx.test.filters.FlakyTest", +] + FLAKY_AND_IGNORED = [ "androidx.test.filters.FlakyTest", "org.junit.Ignore", @@ -328,7 +332,7 @@ test_module_config { base: "FrameworksServicesTests", test_suites: ["device-tests"], include_filters: ["com.android.server.recoverysystem."], - exclude_annotations: ["androidx.test.filters.FlakyTest"], + exclude_annotations: FLAKY, } // server pm TEST_MAPPING @@ -357,3 +361,319 @@ test_module_config { test_suites: ["device-tests"], include_filters: ["com.android.server.os."], } + +test_module_config { + name: "FrameworksServicesTests_presubmit", + base: "FrameworksServicesTests", + test_suites: ["device-tests"], + include_annotations: ["android.platform.test.annotations.Presubmit"], + exclude_annotations: FLAKY_AND_IGNORED, +} + +test_module_config { + name: "FrameworksServicesTests_com_android_server_job_Presubmit", + base: "FrameworksServicesTests", + test_suites: ["device-tests"], + include_filters: ["com.android.server.job"], + exclude_annotations: [ + "androidx.test.filters.LargeTest", + "androidx.test.filters.FlakyTest", + ], +} + +test_module_config { + name: "FrameworksServicesTests_com_android_server_job", + base: "FrameworksServicesTests", + test_suites: ["device-tests"], + include_filters: ["com.android.server.job"], +} + +test_module_config { + name: "FrameworksServicesTests_com_android_server_tare_Presubmit", + base: "FrameworksServicesTests", + test_suites: ["device-tests"], + include_filters: ["com.android.server.tare"], + exclude_annotations: FLAKY, +} + +test_module_config { + name: "FrameworksServicesTests_com_android_server_tare", + base: "FrameworksServicesTests", + test_suites: ["device-tests"], + include_filters: ["com.android.server.tare"], +} + +test_module_config { + name: "FrameworksServicesTests_com_android_server_usage_Presubmit", + base: "FrameworksServicesTests", + test_suites: ["device-tests"], + include_filters: ["com.android.server.usage"], + exclude_annotations: FLAKY, +} + +test_module_config { + name: "FrameworksServicesTests_com_android_server_usage", + base: "FrameworksServicesTests", + test_suites: ["device-tests"], + include_filters: ["com.android.server.usage"], +} + +test_module_config { + name: "FrameworksServicesTests_battery_stats", + base: "FrameworksServicesTests", + test_suites: ["device-tests"], + include_filters: ["com.android.server.am.BatteryStatsServiceTest"], +} + +test_module_config { + name: "FrameworksServicesTests_accessibility_Presubmit", + base: "FrameworksServicesTests", + test_suites: ["device-tests"], + include_filters: ["com.android.server.accessibility"], + exclude_annotations: FLAKY, +} + +test_module_config { + name: "FrameworksServicesTests_accessibility", + base: "FrameworksServicesTests", + test_suites: ["device-tests"], + include_filters: ["com.android.server.accessibility"], +} + +test_module_config { + name: "FrameworksServicesTests_binary_transparency", + base: "FrameworksServicesTests", + test_suites: ["device-tests"], + include_filters: ["com.android.server.BinaryTransparencyServiceTest"], +} + +test_module_config { + name: "FrameworksServicesTests_pinner_service", + base: "FrameworksServicesTests", + test_suites: ["device-tests"], + include_filters: ["com.android.server.PinnerServiceTest"], + exclude_annotations: ["org.junit.Ignore"], +} + +test_module_config { + name: "FrameworksServicesTests_android_server_am_Presubmit", + base: "FrameworksServicesTests", + test_suites: ["device-tests"], + include_filters: ["com.android.server.am."], + include_annotations: ["android.platform.test.annotations.Presubmit"], + exclude_annotations: FLAKY, +} + +test_module_config { + name: "FrameworksServicesTests_android_server_am", + base: "FrameworksServicesTests", + test_suites: ["device-tests"], + include_filters: ["com.android.server.am."], +} + +test_module_config { + name: "FrameworksServicesTests_android_server_appop", + base: "FrameworksServicesTests", + test_suites: ["device-tests"], + include_filters: ["com.android.server.appop"], +} + +test_module_config { + name: "FrameworksServicesTests_android_server_audio", + base: "FrameworksServicesTests", + test_suites: ["device-tests"], + include_filters: ["com.android.server.audio"], + include_annotations: ["android.platform.test.annotations.Presubmit"], + exclude_annotations: FLAKY_AND_IGNORED, +} + +test_module_config { + name: "FrameworksServicesTests_android_server_compat", + base: "FrameworksServicesTests", + test_suites: ["device-tests"], + include_filters: ["com.android.server.compat"], +} + +test_module_config { + name: "FrameworksServicesTests_android_server_hdmi_Presubmit", + base: "FrameworksServicesTests", + test_suites: ["device-tests"], + include_filters: ["com.android.server.hdmi"], + include_annotations: ["android.platform.test.annotations.Presubmit"], + exclude_annotations: FLAKY_AND_IGNORED, +} + +test_module_config { + name: "FrameworksServicesTests_android_server_hdmi", + base: "FrameworksServicesTests", + test_suites: ["device-tests"], + include_filters: ["com.android.server.hdmi"], + exclude_annotations: ["org.junit.Ignore"], +} + +test_module_config { + name: "FrameworksServicesTests_android_server_integrity", + base: "FrameworksServicesTests", + test_suites: ["device-tests"], + include_filters: ["com.android.server.integrity."], +} + +test_module_config { + name: "FrameworksServicesTests_android_server_lights", + base: "FrameworksServicesTests", + test_suites: ["device-tests"], + include_filters: ["com.android.server.lights"], + exclude_annotations: FLAKY, +} + +test_module_config { + name: "FrameworksServicesTests_android_server_locales", + base: "FrameworksServicesTests", + test_suites: ["device-tests"], + include_filters: ["com.android.server.locales."], +} + +test_module_config { + name: "FrameworksServicesTests_android_server_location_contexthub_Presubmit", + base: "FrameworksServicesTests", + test_suites: ["device-tests"], + include_filters: ["com.android.server.location.contexthub."], + include_annotations: ["android.platform.test.annotations.Presubmit"], + exclude_annotations: FLAKY_AND_IGNORED, +} + +test_module_config { + name: "FrameworksServicesTests_android_server_locksettings", + base: "FrameworksServicesTests", + test_suites: ["device-tests"], + include_filters: ["com.android.server.locksettings."], + exclude_annotations: FLAKY, +} + +test_module_config { + name: "FrameworksServicesTests_android_server_logcat_Presubmit", + base: "FrameworksServicesTests", + test_suites: ["device-tests"], + include_filters: ["com.android.server.logcat"], + exclude_annotations: FLAKY, +} + +test_module_config { + name: "FrameworksServicesTests_android_server_logcat", + base: "FrameworksServicesTests", + test_suites: ["device-tests"], + include_filters: ["com.android.server.logcat"], +} + +test_module_config { + name: "FrameworksServicesTests_android_server_net_Presubmit", + base: "FrameworksServicesTests", + test_suites: ["device-tests"], + include_filters: ["com.android.server.net."], + include_annotations: ["android.platform.test.annotations.Presubmit"], + exclude_annotations: FLAKY, +} + +test_module_config { + name: "FrameworksServicesTests_android_server_om", + base: "FrameworksServicesTests", + test_suites: ["device-tests"], + include_filters: ["com.android.server.om."], +} + +test_module_config { + name: "FrameworksServicesTests_android_server_pdb", + base: "FrameworksServicesTests", + test_suites: ["device-tests"], + include_filters: ["com.android.server.pdb.PersistentDataBlockServiceTest"], +} + +test_module_config { + name: "FrameworksServicesTests_android_server_pm_dex", + base: "FrameworksServicesTests", + test_suites: ["device-tests"], + include_filters: ["com.android.server.pm.dex"], +} + +test_module_config { + name: "FrameworksServicesTests_android_server_policy_Presubmit", + base: "FrameworksServicesTests", + test_suites: ["device-tests"], + include_filters: ["com.android.server.policy."], + include_annotations: ["android.platform.test.annotations.Presubmit"], + exclude_annotations: FLAKY, +} + +test_module_config { + name: "FrameworksServicesTests_android_server_policy", + base: "FrameworksServicesTests", + test_suites: ["device-tests"], + include_filters: ["com.android.server.policy."], +} + +test_module_config { + name: "FrameworksServicesTests_android_server_power", + base: "FrameworksServicesTests", + test_suites: ["device-tests"], + include_filters: ["com.android.server.power"], +} + +test_module_config { + name: "FrameworksServicesTests_android_server_power_hint", + base: "FrameworksServicesTests", + test_suites: ["device-tests"], + include_filters: ["com.android.server.power.hint"], + exclude_annotations: FLAKY, +} + +test_module_config { + name: "FrameworksServicesTests_android_server_powerstats", + base: "FrameworksServicesTests", + test_suites: ["device-tests"], + include_filters: ["com.android.server.powerstats"], +} + +test_module_config { + name: "FrameworksServicesTests_android_server_rollback", + base: "FrameworksServicesTests", + test_suites: ["device-tests"], + include_filters: ["com.android.server.rollback"], +} + +test_module_config { + name: "FrameworksServicesTests_android_server_uri", + base: "FrameworksServicesTests", + test_suites: ["device-tests"], + include_filters: ["com.android.server.uri."], +} + +test_module_config { + name: "FrameworksServicesTests_com_android_server_location_contexthub", + base: "FrameworksServicesTests", + test_suites: ["device-tests"], + include_filters: ["com.android.server.location.contexthub."], + include_annotations: ["android.platform.test.annotations.Postsubmit"], + exclude_annotations: FLAKY_AND_IGNORED, +} + +test_module_config { + name: "FrameworksServicesTests_android_server_usage", + base: "FrameworksServicesTests", + test_suites: ["device-tests"], + include_filters: ["com.android.server.usage"], + exclude_filters: ["com.android.server.usage.StorageStatsServiceTest"], +} + +test_module_config { + name: "FrameworksServicesTests_android_server_soundtrigger_middleware", + base: "FrameworksServicesTests", + test_suites: ["device-tests"], + include_filters: ["com.android.server.soundtrigger_middleware"], +} + +test_module_config { + name: "FrameworksServicesTests_android_server_input", + base: "FrameworksServicesTests", + test_suites: ["device-tests"], + include_filters: ["com.android.server.input"], +} diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGestureHandlerTest.java index 3931580db47e..d80a1f056e94 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGestureHandlerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGestureHandlerTest.java @@ -18,13 +18,20 @@ package com.android.server.accessibility.magnification; import static android.view.MotionEvent.ACTION_CANCEL; import static android.view.MotionEvent.ACTION_DOWN; +import static android.view.MotionEvent.ACTION_HOVER_MOVE; import static android.view.MotionEvent.ACTION_UP; +import static junit.framework.Assert.assertFalse; + import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.testng.AssertJUnit.assertTrue; import android.annotation.NonNull; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.provider.Settings; import android.view.InputDevice; import android.view.MotionEvent; @@ -32,8 +39,10 @@ import android.view.MotionEvent; import androidx.test.runner.AndroidJUnit4; import com.android.server.accessibility.AccessibilityTraceManager; +import com.android.server.accessibility.Flags; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -45,6 +54,9 @@ import org.mockito.MockitoAnnotations; @RunWith(AndroidJUnit4.class) public class MagnificationGestureHandlerTest { + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + private TestMagnificationGestureHandler mMgh; private static final int DISPLAY_0 = 0; private static final int FULLSCREEN_MODE = @@ -81,6 +93,66 @@ public class MagnificationGestureHandlerTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE) + public void onMotionEvent_isFromMouse_handleMouseOrStylusEvent() { + final MotionEvent mouseEvent = MotionEvent.obtain(0, 0, ACTION_HOVER_MOVE, 0, 0, 0); + mouseEvent.setSource(InputDevice.SOURCE_MOUSE); + + mMgh.onMotionEvent(mouseEvent, mouseEvent, /* policyFlags= */ 0); + + try { + assertTrue(mMgh.mIsHandleMouseOrStylusEventCalled); + } finally { + mouseEvent.recycle(); + } + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE) + public void onMotionEvent_isFromStylus_handleMouseOrStylusEvent() { + final MotionEvent stylusEvent = MotionEvent.obtain(0, 0, ACTION_HOVER_MOVE, 0, 0, 0); + stylusEvent.setSource(InputDevice.SOURCE_STYLUS); + + mMgh.onMotionEvent(stylusEvent, stylusEvent, /* policyFlags= */ 0); + + try { + assertTrue(mMgh.mIsHandleMouseOrStylusEventCalled); + } finally { + stylusEvent.recycle(); + } + } + + @Test + @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE) + public void onMotionEvent_isFromMouse_handleMouseOrStylusEventNotCalled() { + final MotionEvent mouseEvent = MotionEvent.obtain(0, 0, ACTION_HOVER_MOVE, 0, 0, 0); + mouseEvent.setSource(InputDevice.SOURCE_MOUSE); + + mMgh.onMotionEvent(mouseEvent, mouseEvent, /* policyFlags= */ 0); + + try { + assertFalse(mMgh.mIsHandleMouseOrStylusEventCalled); + } finally { + mouseEvent.recycle(); + } + } + + @Test + @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE) + public void onMotionEvent_isFromStylus_handleMouseOrStylusEventNotCalled() { + final MotionEvent stylusEvent = MotionEvent.obtain(0, 0, ACTION_HOVER_MOVE, 0, 0, 0); + stylusEvent.setSource(InputDevice.SOURCE_STYLUS); + + mMgh.onMotionEvent(stylusEvent, stylusEvent, /* policyFlags= */ 0); + + try { + assertFalse(mMgh.mIsHandleMouseOrStylusEventCalled); + } finally { + stylusEvent.recycle(); + } + } + + @Test public void onMotionEvent_downEvent_handleInteractionStart() { final MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); downEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN); @@ -125,6 +197,7 @@ public class MagnificationGestureHandlerTest { private static class TestMagnificationGestureHandler extends MagnificationGestureHandler { boolean mIsInternalMethodCalled = false; + boolean mIsHandleMouseOrStylusEventCalled = false; TestMagnificationGestureHandler(int displayId, boolean detectSingleFingerTripleTap, boolean detectTwoFingerTripleTap, @@ -135,6 +208,11 @@ public class MagnificationGestureHandlerTest { } @Override + void handleMouseOrStylusEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + mIsHandleMouseOrStylusEventCalled = true; + } + + @Override void onMotionEventInternal(MotionEvent event, MotionEvent rawEvent, int policyFlags) { mIsInternalMethodCalled = true; } diff --git a/services/tests/servicestests/src/com/android/server/autofill/PresentationEventLoggerTest.java b/services/tests/servicestests/src/com/android/server/autofill/PresentationEventLoggerTest.java new file mode 100644 index 000000000000..75258f0aa7e0 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/autofill/PresentationEventLoggerTest.java @@ -0,0 +1,132 @@ +/* + * 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.autofill; + + +import static com.google.common.truth.Truth.assertThat; + +import android.view.autofill.AutofillId; +import android.view.autofill.AutofillValue; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class PresentationEventLoggerTest { + + @Test + public void testViewEntered() { + PresentationStatsEventLogger pEventLogger = + PresentationStatsEventLogger.createPresentationLog(1, 1, 1); + + AutofillId id = new AutofillId(13); + AutofillValue initialValue = AutofillValue.forText("hello"); + AutofillValue lastValue = AutofillValue.forText("hello world"); + ViewState vState = new ViewState(id, null, 0, false); + + pEventLogger.startNewEvent(); + pEventLogger.maybeSetFocusedId(id); + pEventLogger.onFieldTextUpdated(vState, initialValue); + pEventLogger.onFieldTextUpdated(vState, lastValue); + + PresentationStatsEventLogger.PresentationStatsEventInternal event = + pEventLogger.getInternalEvent().get(); + assertThat(event).isNotNull(); + assertThat(event.mFieldFirstLength).isEqualTo(initialValue.getTextValue().length()); + assertThat(event.mFieldLastLength).isEqualTo(lastValue.getTextValue().length()); + assertThat(event.mFieldModifiedFirstTimestampMs).isNotEqualTo(-1); + assertThat(event.mFieldModifiedLastTimestampMs).isNotEqualTo(-1); + } + + @Test + public void testViewAutofilled() { + PresentationStatsEventLogger pEventLogger = + PresentationStatsEventLogger.createPresentationLog(1, 1, 1); + + String newTextValue = "hello"; + AutofillValue value = AutofillValue.forText(newTextValue); + AutofillId id = new AutofillId(13); + ViewState vState = new ViewState(id, null, ViewState.STATE_AUTOFILLED, false); + + pEventLogger.startNewEvent(); + pEventLogger.maybeSetFocusedId(id); + pEventLogger.onFieldTextUpdated(vState, value); + + PresentationStatsEventLogger.PresentationStatsEventInternal event = + pEventLogger.getInternalEvent().get(); + assertThat(event).isNotNull(); + assertThat(event.mFieldFirstLength).isEqualTo(newTextValue.length()); + assertThat(event.mFieldLastLength).isEqualTo(newTextValue.length()); + assertThat(event.mAutofilledTimestampMs).isNotEqualTo(-1); + assertThat(event.mFieldModifiedFirstTimestampMs).isEqualTo(-1); + assertThat(event.mFieldModifiedLastTimestampMs).isEqualTo(-1); + } + + @Test + public void testModifiedOnDifferentView() { + PresentationStatsEventLogger pEventLogger = + PresentationStatsEventLogger.createPresentationLog(1, 1, 1); + + String newTextValue = "hello"; + AutofillValue value = AutofillValue.forText(newTextValue); + AutofillId id = new AutofillId(13); + ViewState vState = new ViewState(id, null, ViewState.STATE_AUTOFILLED, false); + + pEventLogger.startNewEvent(); + pEventLogger.onFieldTextUpdated(vState, value); + + PresentationStatsEventLogger.PresentationStatsEventInternal event = + pEventLogger.getInternalEvent().get(); + assertThat(event).isNotNull(); + assertThat(event.mFieldFirstLength).isEqualTo(-1); + assertThat(event.mFieldLastLength).isEqualTo(-1); + assertThat(event.mFieldModifiedFirstTimestampMs).isEqualTo(-1); + assertThat(event.mFieldModifiedLastTimestampMs).isEqualTo(-1); + assertThat(event.mAutofilledTimestampMs).isEqualTo(-1); + } + + @Test + public void testSetCountShown() { + PresentationStatsEventLogger pEventLogger = + PresentationStatsEventLogger.createPresentationLog(1, 1, 1); + + pEventLogger.startNewEvent(); + pEventLogger.logWhenDatasetShown(7); + + PresentationStatsEventLogger.PresentationStatsEventInternal event = + pEventLogger.getInternalEvent().get(); + assertThat(event).isNotNull(); + assertThat(event.mCountShown).isEqualTo(7); + assertThat(event.mNoPresentationReason) + .isEqualTo(PresentationStatsEventLogger.NOT_SHOWN_REASON_ANY_SHOWN); + } + + @Test + public void testFillDialogShownThenInline() { + PresentationStatsEventLogger pEventLogger = + PresentationStatsEventLogger.createPresentationLog(1, 1, 1); + + pEventLogger.startNewEvent(); + pEventLogger.maybeSetDisplayPresentationType(3); + pEventLogger.maybeSetDisplayPresentationType(2); + + PresentationStatsEventLogger.PresentationStatsEventInternal event = + pEventLogger.getInternalEvent().get(); + assertThat(event).isNotNull(); + assertThat(event.mDisplayPresentationType).isEqualTo(3); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java index a4222ff5650b..d2961bc8a90f 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java @@ -79,6 +79,7 @@ import android.os.IBinder; import android.os.RemoteException; import android.os.UserManager; import android.platform.test.annotations.Presubmit; +import android.platform.test.annotations.RequiresFlagsDisabled; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; @@ -1488,15 +1489,30 @@ public class BiometricServiceTest { } @Test + @RequiresFlagsDisabled(Flags.FLAG_MANDATORY_BIOMETRICS) public void testCanAuthenticate_whenLockoutTimed() throws Exception { testCanAuthenticate_whenLockedOut(LockoutTracker.LOCKOUT_TIMED); } @Test + @RequiresFlagsDisabled(Flags.FLAG_MANDATORY_BIOMETRICS) public void testCanAuthenticate_whenLockoutPermanent() throws Exception { testCanAuthenticate_whenLockedOut(LockoutTracker.LOCKOUT_PERMANENT); } + @Test + @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS) + public void testCanAuthenticate_whenLockoutTimed_returnsLockoutError() throws Exception { + testCanAuthenticate_whenLockedOut_returnLockoutError(LockoutTracker.LOCKOUT_TIMED); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS) + public void testCanAuthenticate_whenLockoutPermanent_returnsLockoutError() throws Exception { + testCanAuthenticate_whenLockedOut_returnLockoutError(LockoutTracker.LOCKOUT_PERMANENT); + } + + @RequiresFlagsDisabled(Flags.FLAG_MANDATORY_BIOMETRICS) private void testCanAuthenticate_whenLockedOut(@LockoutTracker.LockoutMode int lockoutMode) throws Exception { // When only biometric is requested, and sensor is strong enough @@ -1510,6 +1526,21 @@ public class BiometricServiceTest { invokeCanAuthenticate(mBiometricService, Authenticators.BIOMETRIC_STRONG)); } + @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS) + private void testCanAuthenticate_whenLockedOut_returnLockoutError( + @LockoutTracker.LockoutMode int lockoutMode) + throws Exception { + // When only biometric is requested, and sensor is strong enough + setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); + + when(mFingerprintAuthenticator.getLockoutModeForUser(anyInt())) + .thenReturn(lockoutMode); + + // Lockout is not considered an error for BiometricManager#canAuthenticate + assertEquals(BiometricManager.BIOMETRIC_ERROR_LOCKOUT, + invokeCanAuthenticate(mBiometricService, Authenticators.BIOMETRIC_STRONG)); + } + @Test @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS) public void testCanAuthenticate_whenMandatoryBiometricsRequested() @@ -1529,7 +1560,7 @@ public class BiometricServiceTest { when(mTrustManager.isInSignificantPlace()).thenReturn(true); - assertEquals(BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE, + assertEquals(BiometricManager.BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE, invokeCanAuthenticate(mBiometricService, Authenticators.MANDATORY_BIOMETRICS)); } @@ -1572,7 +1603,7 @@ public class BiometricServiceTest { setupAuthForOnly(TYPE_CREDENTIAL, Authenticators.DEVICE_CREDENTIAL); - assertEquals(BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE, + assertEquals(BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE, invokeCanAuthenticate(mBiometricService, Authenticators.MANDATORY_BIOMETRICS)); when(mTrustManager.isInSignificantPlace()).thenReturn(true); diff --git a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java index 240da9fe46bd..4c3a233fdd97 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java @@ -231,7 +231,7 @@ public class PreAuthInfoTest { @Test @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS) - public void testMandatoryBiometricsStatus_whenRequirementsNotSatisfiedAndSensorAvailable() + public void testMandatoryBiometricsAndStrongBiometricsStatus_whenRequirementsNotSatisfied() throws Exception { when(mTrustManager.isInSignificantPlace()).thenReturn(true); @@ -246,6 +246,24 @@ public class PreAuthInfoTest { assertThat(preAuthInfo.eligibleSensors).hasSize(1); } + @Test + @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS) + public void testMandatoryBiometricsStatus_whenRequirementsNotSatisfiedAndSensorAvailable() + throws Exception { + when(mTrustManager.isInSignificantPlace()).thenReturn(true); + + final BiometricSensor sensor = getFaceSensor(); + final PromptInfo promptInfo = new PromptInfo(); + promptInfo.setAuthenticators(BiometricManager.Authenticators.MANDATORY_BIOMETRICS); + final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager, + mSettingObserver, List.of(sensor), 0 /* userId */, promptInfo, TEST_PACKAGE_NAME, + false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager); + + assertThat(preAuthInfo.getCanAuthenticateResult()).isEqualTo( + BiometricManager.BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE); + assertThat(preAuthInfo.eligibleSensors).hasSize(0); + } + private BiometricSensor getFingerprintSensor() { BiometricSensor sensor = new BiometricSensor(mContext, SENSOR_ID_FINGERPRINT, TYPE_FINGERPRINT, BiometricManager.Authenticators.BIOMETRIC_STRONG, diff --git a/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java b/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java index cb75e1ab6cce..14cb22d7698e 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java @@ -26,16 +26,25 @@ import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricPrompt; +import android.hardware.biometrics.Flags; import android.hardware.biometrics.PromptInfo; import android.platform.test.annotations.Presubmit; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import androidx.test.filters.SmallTest; +import org.junit.Rule; import org.junit.Test; @Presubmit @SmallTest public class UtilsTest { + @Rule + public final CheckFlagsRule mCheckFlagsRule = + DeviceFlagsValueProvider.createCheckFlagsRule(); @Test public void testCombineAuthenticatorBundles_withKeyDeviceCredential_andKeyAuthenticators() { @@ -215,7 +224,8 @@ public class UtilsTest { } @Test - public void testBiometricConstantsConversion() { + @RequiresFlagsDisabled(Flags.FLAG_MANDATORY_BIOMETRICS) + public void testBiometricConstantsConversionLegacy() { final int[][] testCases = { {BiometricConstants.BIOMETRIC_SUCCESS, BiometricManager.BIOMETRIC_SUCCESS}, @@ -240,6 +250,34 @@ public class UtilsTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS) + public void testBiometricConstantsConversion() { + final int[][] testCases = { + {BiometricConstants.BIOMETRIC_SUCCESS, + BiometricManager.BIOMETRIC_SUCCESS}, + {BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS, + BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED}, + {BiometricConstants.BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL, + BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED}, + {BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE, + BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE}, + {BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT, + BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE}, + {BiometricConstants.BIOMETRIC_ERROR_LOCKOUT, + BiometricManager.BIOMETRIC_ERROR_LOCKOUT}, + {BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT, + BiometricManager.BIOMETRIC_ERROR_LOCKOUT}, + {BiometricConstants.BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE, + BiometricManager.BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE} + }; + + for (int i = 0; i < testCases.length; i++) { + assertEquals(testCases[i][1], + Utils.biometricConstantsToBiometricManager(testCases[i][0])); + } + } + + @Test public void testGetAuthenticationTypeForResult_getsCorrectType() { assertEquals(Utils.getAuthenticationTypeForResult( BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED), diff --git a/services/tests/servicestests/src/com/android/server/contentprotection/OWNERS b/services/tests/servicestests/src/com/android/server/contentprotection/OWNERS index 24561c59bba6..3d09da303b0f 100644 --- a/services/tests/servicestests/src/com/android/server/contentprotection/OWNERS +++ b/services/tests/servicestests/src/com/android/server/contentprotection/OWNERS @@ -1,3 +1,4 @@ -# Bug component: 544200 +# Bug component: 1040349 + +include /core/java/android/view/contentprotection/OWNERS -include /core/java/android/view/contentcapture/OWNERS diff --git a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java index 963b27e010fa..8e3633509cd3 100644 --- a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java @@ -119,8 +119,7 @@ public class TunerResourceManagerServiceTest { tunerFrontendInfo(1 /*handle*/, FrontendSettings.TYPE_DVBT, 1 /*exclusiveGroupId*/); mTunerResourceManagerService.setFrontendInfoListInternal(infos); - Map<Integer, FrontendResource> resources = - mTunerResourceManagerService.getFrontendResources(); + Map<Long, FrontendResource> resources = mTunerResourceManagerService.getFrontendResources(); for (int id = 0; id < infos.length; id++) { assertThat(resources.get(infos[id].handle) .getExclusiveGroupMemberFeHandles().size()).isEqualTo(0); @@ -147,15 +146,14 @@ public class TunerResourceManagerServiceTest { tunerFrontendInfo(3 /*handle*/, FrontendSettings.TYPE_ATSC, 1 /*exclusiveGroupId*/); mTunerResourceManagerService.setFrontendInfoListInternal(infos); - Map<Integer, FrontendResource> resources = - mTunerResourceManagerService.getFrontendResources(); + Map<Long, FrontendResource> resources = mTunerResourceManagerService.getFrontendResources(); assertThat(resources.values()).comparingElementsUsing(FR_TFI_COMPARE) .containsExactlyElementsIn(Arrays.asList(infos)); - assertThat(resources.get(0).getExclusiveGroupMemberFeHandles()).isEmpty(); - assertThat(resources.get(1).getExclusiveGroupMemberFeHandles()).containsExactly(2, 3); - assertThat(resources.get(2).getExclusiveGroupMemberFeHandles()).containsExactly(1, 3); - assertThat(resources.get(3).getExclusiveGroupMemberFeHandles()).containsExactly(1, 2); + assertThat(resources.get(0L).getExclusiveGroupMemberFeHandles()).isEmpty(); + assertThat(resources.get(1L).getExclusiveGroupMemberFeHandles()).containsExactly(2L, 3L); + assertThat(resources.get(2L).getExclusiveGroupMemberFeHandles()).containsExactly(1L, 3L); + assertThat(resources.get(3L).getExclusiveGroupMemberFeHandles()).containsExactly(1L, 2L); } @Test @@ -168,11 +166,11 @@ public class TunerResourceManagerServiceTest { tunerFrontendInfo(1 /*handle*/, FrontendSettings.TYPE_DVBS, 1 /*exclusiveGroupId*/); mTunerResourceManagerService.setFrontendInfoListInternal(infos); - Map<Integer, FrontendResource> resources0 = + Map<Long, FrontendResource> resources0 = mTunerResourceManagerService.getFrontendResources(); mTunerResourceManagerService.setFrontendInfoListInternal(infos); - Map<Integer, FrontendResource> resources1 = + Map<Long, FrontendResource> resources1 = mTunerResourceManagerService.getFrontendResources(); assertThat(resources0).isEqualTo(resources1); @@ -195,8 +193,7 @@ public class TunerResourceManagerServiceTest { tunerFrontendInfo(1 /*handle*/, FrontendSettings.TYPE_DVBT, 1 /*exclusiveGroupId*/); mTunerResourceManagerService.setFrontendInfoListInternal(infos1); - Map<Integer, FrontendResource> resources = - mTunerResourceManagerService.getFrontendResources(); + Map<Long, FrontendResource> resources = mTunerResourceManagerService.getFrontendResources(); for (int id = 0; id < infos1.length; id++) { assertThat(resources.get(infos1[id].handle) .getExclusiveGroupMemberFeHandles().size()).isEqualTo(0); @@ -222,8 +219,7 @@ public class TunerResourceManagerServiceTest { tunerFrontendInfo(1 /*handle*/, FrontendSettings.TYPE_DVBT, 1 /*exclusiveGroupId*/); mTunerResourceManagerService.setFrontendInfoListInternal(infos1); - Map<Integer, FrontendResource> resources = - mTunerResourceManagerService.getFrontendResources(); + Map<Long, FrontendResource> resources = mTunerResourceManagerService.getFrontendResources(); for (int id = 0; id < infos1.length; id++) { assertThat(resources.get(infos1[id].handle) .getExclusiveGroupMemberFeHandles().size()).isEqualTo(0); @@ -240,7 +236,7 @@ public class TunerResourceManagerServiceTest { mTunerResourceManagerService.setFrontendInfoListInternal(infos0); TunerFrontendRequest request = tunerFrontendRequest(0 /*clientId*/, FrontendSettings.TYPE_DVBT); - int[] frontendHandle = new int[1]; + long[] frontendHandle = new long[1]; assertThat(mTunerResourceManagerService .requestFrontendInternal(request, frontendHandle)).isFalse(); assertThat(frontendHandle[0]).isEqualTo(TunerResourceManager.INVALID_RESOURCE_HANDLE); @@ -263,7 +259,7 @@ public class TunerResourceManagerServiceTest { TunerFrontendRequest request = tunerFrontendRequest(clientId[0] /*clientId*/, FrontendSettings.TYPE_DVBT); - int[] frontendHandle = new int[1]; + long[] frontendHandle = new long[1]; assertThat(mTunerResourceManagerService .requestFrontendInternal(request, frontendHandle)).isFalse(); assertThat(frontendHandle[0]).isEqualTo(TunerResourceManager.INVALID_RESOURCE_HANDLE); @@ -296,7 +292,7 @@ public class TunerResourceManagerServiceTest { TunerFrontendRequest request = tunerFrontendRequest(clientId[0] /*clientId*/, FrontendSettings.TYPE_DVBT); - int[] frontendHandle = new int[1]; + long[] frontendHandle = new long[1]; assertThat(mTunerResourceManagerService .requestFrontendInternal(request, frontendHandle)).isTrue(); assertThat(frontendHandle[0]).isEqualTo(0); @@ -333,7 +329,7 @@ public class TunerResourceManagerServiceTest { 1 /*exclusiveGroupId*/); mTunerResourceManagerService.setFrontendInfoListInternal(infos); - int[] frontendHandle = new int[1]; + long[] frontendHandle = new long[1]; TunerFrontendRequest request = tunerFrontendRequest(clientId1[0] /*clientId*/, FrontendSettings.TYPE_DVBT); assertThat(mTunerResourceManagerService @@ -385,7 +381,7 @@ public class TunerResourceManagerServiceTest { TunerFrontendRequest request = tunerFrontendRequest(clientId0[0] /*clientId*/, FrontendSettings.TYPE_DVBT); - int[] frontendHandle = new int[1]; + long[] frontendHandle = new long[1]; assertThat(mTunerResourceManagerService .requestFrontendInternal(request, frontendHandle)).isTrue(); @@ -435,13 +431,13 @@ public class TunerResourceManagerServiceTest { TunerFrontendRequest request = tunerFrontendRequest(clientId0[0] /*clientId*/, FrontendSettings.TYPE_DVBT); - int[] frontendHandle = new int[1]; + long[] frontendHandle = new long[1]; assertThat(mTunerResourceManagerService .requestFrontendInternal(request, frontendHandle)).isTrue(); assertThat(frontendHandle[0]).isEqualTo(infos[0].handle); assertThat(mTunerResourceManagerService.getClientProfile(clientId0[0]) - .getInUseFrontendHandles()).isEqualTo(new HashSet<Integer>(Arrays.asList( - infos[0].handle, infos[1].handle))); + .getInUseFrontendHandles()) + .isEqualTo(new HashSet<Long>(Arrays.asList(infos[0].handle, infos[1].handle))); request = tunerFrontendRequest(clientId1[0] /*clientId*/, FrontendSettings.TYPE_DVBS); @@ -480,7 +476,7 @@ public class TunerResourceManagerServiceTest { TunerFrontendRequest request = tunerFrontendRequest(clientId[0] /*clientId*/, FrontendSettings.TYPE_DVBT); - int[] frontendHandle = new int[1]; + long[] frontendHandle = new long[1]; assertThat(mTunerResourceManagerService .requestFrontendInternal(request, frontendHandle)).isTrue(); assertThat(frontendHandle[0]).isEqualTo(infos[0].handle); @@ -525,7 +521,7 @@ public class TunerResourceManagerServiceTest { mTunerResourceManagerService.updateCasInfoInternal(1 /*casSystemId*/, 2 /*maxSessionNum*/); CasSessionRequest request = casSessionRequest(clientId0[0], 1 /*casSystemId*/); - int[] casSessionHandle = new int[1]; + long[] casSessionHandle = new long[1]; // Request for 2 cas sessions. assertThat(mTunerResourceManagerService .requestCasSessionInternal(request, casSessionHandle)).isTrue(); @@ -581,7 +577,7 @@ public class TunerResourceManagerServiceTest { mTunerResourceManagerService.updateCasInfoInternal(1 /*casSystemId*/, 2 /*maxSessionNum*/); TunerCiCamRequest request = tunerCiCamRequest(clientId0[0], 1 /*ciCamId*/); - int[] ciCamHandle = new int[1]; + long[] ciCamHandle = new long[1]; // Request for 2 ciCam sessions. assertThat(mTunerResourceManagerService .requestCiCamInternal(request, ciCamHandle)).isTrue(); @@ -625,7 +621,7 @@ public class TunerResourceManagerServiceTest { mTunerResourceManagerService.updateCasInfoInternal(1 /*casSystemId*/, 2 /*maxSessionNum*/); CasSessionRequest request = casSessionRequest(clientId[0], 1 /*casSystemId*/); - int[] casSessionHandle = new int[1]; + long[] casSessionHandle = new long[1]; // Request for 1 cas sessions. assertThat(mTunerResourceManagerService .requestCasSessionInternal(request, casSessionHandle)).isTrue(); @@ -662,7 +658,7 @@ public class TunerResourceManagerServiceTest { mTunerResourceManagerService.updateCasInfoInternal(1 /*casSystemId*/, 2 /*maxSessionNum*/); TunerCiCamRequest request = tunerCiCamRequest(clientId[0], 1 /*ciCamId*/); - int[] ciCamHandle = new int[1]; + long[] ciCamHandle = new long[1]; // Request for 1 ciCam sessions. assertThat(mTunerResourceManagerService .requestCiCamInternal(request, ciCamHandle)).isTrue(); @@ -708,17 +704,17 @@ public class TunerResourceManagerServiceTest { clientId1[0], clientPriorities[1], 0/*niceValue*/); // Init lnb resources. - int[] lnbHandles = {1}; + long[] lnbHandles = {1}; mTunerResourceManagerService.setLnbInfoListInternal(lnbHandles); TunerLnbRequest request = new TunerLnbRequest(); request.clientId = clientId0[0]; - int[] lnbHandle = new int[1]; + long[] lnbHandle = new long[1]; assertThat(mTunerResourceManagerService .requestLnbInternal(request, lnbHandle)).isTrue(); assertThat(lnbHandle[0]).isEqualTo(lnbHandles[0]); assertThat(mTunerResourceManagerService.getClientProfile(clientId0[0]).getInUseLnbHandles()) - .isEqualTo(new HashSet<Integer>(Arrays.asList(lnbHandles[0]))); + .isEqualTo(new HashSet<Long>(Arrays.asList(lnbHandles[0]))); request = new TunerLnbRequest(); request.clientId = clientId1[0]; @@ -747,12 +743,12 @@ public class TunerResourceManagerServiceTest { assertThat(clientId[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID); // Init lnb resources. - int[] lnbHandles = {0}; + long[] lnbHandles = {0}; mTunerResourceManagerService.setLnbInfoListInternal(lnbHandles); TunerLnbRequest request = new TunerLnbRequest(); request.clientId = clientId[0]; - int[] lnbHandle = new int[1]; + long[] lnbHandle = new long[1]; assertThat(mTunerResourceManagerService .requestLnbInternal(request, lnbHandle)).isTrue(); assertThat(lnbHandle[0]).isEqualTo(lnbHandles[0]); @@ -786,7 +782,7 @@ public class TunerResourceManagerServiceTest { TunerFrontendRequest request = tunerFrontendRequest(clientId[0] /*clientId*/, FrontendSettings.TYPE_DVBT); - int[] frontendHandle = new int[1]; + long[] frontendHandle = new long[1]; assertThat(mTunerResourceManagerService .requestFrontendInternal(request, frontendHandle)).isTrue(); assertThat(frontendHandle[0]).isEqualTo(infos[0].handle); @@ -823,12 +819,12 @@ public class TunerResourceManagerServiceTest { infos[2] = tunerDemuxInfo(2 /* handle */, Filter.TYPE_TS); mTunerResourceManagerService.setDemuxInfoListInternal(infos); - int[] demuxHandle0 = new int[1]; + long[] demuxHandle0 = new long[1]; // first with undefined type (should be the first one with least # of caps) TunerDemuxRequest request = tunerDemuxRequest(clientId0[0], Filter.TYPE_UNDEFINED); assertThat(mTunerResourceManagerService.requestDemuxInternal(request, demuxHandle0)) .isTrue(); - assertThat(demuxHandle0[0]).isEqualTo(1); + assertThat(demuxHandle0[0]).isEqualTo(1L); DemuxResource dr = mTunerResourceManagerService.getDemuxResource(demuxHandle0[0]); mTunerResourceManagerService.releaseDemuxInternal(dr); @@ -837,20 +833,20 @@ public class TunerResourceManagerServiceTest { demuxHandle0[0] = -1; assertThat(mTunerResourceManagerService.requestDemuxInternal(request, demuxHandle0)) .isFalse(); - assertThat(demuxHandle0[0]).isEqualTo(-1); + assertThat(demuxHandle0[0]).isEqualTo(-1L); // now with TS (should be the one with least # of caps that supports TS) request.desiredFilterTypes = Filter.TYPE_TS; assertThat(mTunerResourceManagerService.requestDemuxInternal(request, demuxHandle0)) .isTrue(); - assertThat(demuxHandle0[0]).isEqualTo(2); + assertThat(demuxHandle0[0]).isEqualTo(2L); // request for another TS int[] clientId1 = new int[1]; mTunerResourceManagerService.registerClientProfileInternal( profile1, null /*listener*/, clientId1); assertThat(clientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID); - int[] demuxHandle1 = new int[1]; + long[] demuxHandle1 = new long[1]; TunerDemuxRequest request1 = tunerDemuxRequest(clientId1[0], Filter.TYPE_TS); assertThat(mTunerResourceManagerService.requestDemuxInternal(request1, demuxHandle1)) .isTrue(); @@ -899,14 +895,14 @@ public class TunerResourceManagerServiceTest { // let clientId0(prio:100) request for IP - should succeed TunerDemuxRequest request0 = tunerDemuxRequest(clientId0[0], Filter.TYPE_IP); - int[] demuxHandle0 = new int[1]; + long[] demuxHandle0 = new long[1]; assertThat(mTunerResourceManagerService .requestDemuxInternal(request0, demuxHandle0)).isTrue(); assertThat(demuxHandle0[0]).isEqualTo(0); // let clientId1(prio:50) request for IP - should fail TunerDemuxRequest request1 = tunerDemuxRequest(clientId1[0], Filter.TYPE_IP); - int[] demuxHandle1 = new int[1]; + long[] demuxHandle1 = new long[1]; demuxHandle1[0] = -1; assertThat(mTunerResourceManagerService .requestDemuxInternal(request1, demuxHandle1)).isFalse(); @@ -926,7 +922,7 @@ public class TunerResourceManagerServiceTest { // let clientId2(prio:50) request for TS - should succeed TunerDemuxRequest request2 = tunerDemuxRequest(clientId2[0], Filter.TYPE_TS); - int[] demuxHandle2 = new int[1]; + long[] demuxHandle2 = new long[1]; assertThat(mTunerResourceManagerService .requestDemuxInternal(request2, demuxHandle2)).isTrue(); assertThat(demuxHandle2[0]).isEqualTo(0); @@ -951,7 +947,7 @@ public class TunerResourceManagerServiceTest { profile, null /*listener*/, clientId); assertThat(clientId[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID); - int[] desHandle = new int[1]; + long[] desHandle = new long[1]; TunerDescramblerRequest request = new TunerDescramblerRequest(); request.clientId = clientId[0]; assertThat(mTunerResourceManagerService.requestDescramblerInternal(request, desHandle)) @@ -1061,7 +1057,7 @@ public class TunerResourceManagerServiceTest { 1 /*exclusiveGroupId*/); /**** Init Lnb Resources ****/ - int[] lnbHandles = {1}; + long[] lnbHandles = {1}; mTunerResourceManagerService.setLnbInfoListInternal(lnbHandles); // Update frontend list in TRM @@ -1070,7 +1066,7 @@ public class TunerResourceManagerServiceTest { /**** Request Frontend ****/ // Predefined frontend request and array to save returned frontend handle - int[] frontendHandle = new int[1]; + long[] frontendHandle = new long[1]; TunerFrontendRequest request = tunerFrontendRequest( ownerClientId0[0] /*clientId*/, FrontendSettings.TYPE_DVBT); @@ -1080,12 +1076,9 @@ public class TunerResourceManagerServiceTest { .requestFrontendInternal(request, frontendHandle)) .isTrue(); assertThat(frontendHandle[0]).isEqualTo(infos[0].handle); - assertThat(mTunerResourceManagerService - .getClientProfile(ownerClientId0[0]) - .getInUseFrontendHandles()) - .isEqualTo(new HashSet<Integer>(Arrays.asList( - infos[0].handle, - infos[1].handle))); + assertThat(mTunerResourceManagerService.getClientProfile(ownerClientId0[0]) + .getInUseFrontendHandles()) + .isEqualTo(new HashSet<Long>(Arrays.asList(infos[0].handle, infos[1].handle))); /**** Share Frontend ****/ @@ -1113,24 +1106,15 @@ public class TunerResourceManagerServiceTest { shareClientId0[0], shareClientId1[0]))); // Verify in use frontend list in all the primary owner and share owner clients - assertThat(mTunerResourceManagerService - .getClientProfile(ownerClientId0[0]) - .getInUseFrontendHandles()) - .isEqualTo(new HashSet<Integer>(Arrays.asList( - infos[0].handle, - infos[1].handle))); - assertThat(mTunerResourceManagerService - .getClientProfile(shareClientId0[0]) - .getInUseFrontendHandles()) - .isEqualTo(new HashSet<Integer>(Arrays.asList( - infos[0].handle, - infos[1].handle))); - assertThat(mTunerResourceManagerService - .getClientProfile(shareClientId1[0]) - .getInUseFrontendHandles()) - .isEqualTo(new HashSet<Integer>(Arrays.asList( - infos[0].handle, - infos[1].handle))); + assertThat(mTunerResourceManagerService.getClientProfile(ownerClientId0[0]) + .getInUseFrontendHandles()) + .isEqualTo(new HashSet<Long>(Arrays.asList(infos[0].handle, infos[1].handle))); + assertThat(mTunerResourceManagerService.getClientProfile(shareClientId0[0]) + .getInUseFrontendHandles()) + .isEqualTo(new HashSet<Long>(Arrays.asList(infos[0].handle, infos[1].handle))); + assertThat(mTunerResourceManagerService.getClientProfile(shareClientId1[0]) + .getInUseFrontendHandles()) + .isEqualTo(new HashSet<Long>(Arrays.asList(infos[0].handle, infos[1].handle))); /**** Remove Frontend Share Owner ****/ @@ -1142,18 +1126,12 @@ public class TunerResourceManagerServiceTest { .getShareFeClientIds()) .isEqualTo(new HashSet<Integer>(Arrays.asList( shareClientId0[0]))); - assertThat(mTunerResourceManagerService - .getClientProfile(ownerClientId0[0]) - .getInUseFrontendHandles()) - .isEqualTo(new HashSet<Integer>(Arrays.asList( - infos[0].handle, - infos[1].handle))); - assertThat(mTunerResourceManagerService - .getClientProfile(shareClientId0[0]) - .getInUseFrontendHandles()) - .isEqualTo(new HashSet<Integer>(Arrays.asList( - infos[0].handle, - infos[1].handle))); + assertThat(mTunerResourceManagerService.getClientProfile(ownerClientId0[0]) + .getInUseFrontendHandles()) + .isEqualTo(new HashSet<Long>(Arrays.asList(infos[0].handle, infos[1].handle))); + assertThat(mTunerResourceManagerService.getClientProfile(shareClientId0[0]) + .getInUseFrontendHandles()) + .isEqualTo(new HashSet<Long>(Arrays.asList(infos[0].handle, infos[1].handle))); /**** Request Shared Frontend with Higher Priority Client ****/ @@ -1173,12 +1151,9 @@ public class TunerResourceManagerServiceTest { .getOwnerClientId()).isEqualTo(ownerClientId1[0]); assertThat(mTunerResourceManagerService.getFrontendResource(infos[1].handle) .getOwnerClientId()).isEqualTo(ownerClientId1[0]); - assertThat(mTunerResourceManagerService - .getClientProfile(ownerClientId1[0]) - .getInUseFrontendHandles()) - .isEqualTo(new HashSet<Integer>(Arrays.asList( - infos[0].handle, - infos[1].handle))); + assertThat(mTunerResourceManagerService.getClientProfile(ownerClientId1[0]) + .getInUseFrontendHandles()) + .isEqualTo(new HashSet<Long>(Arrays.asList(infos[0].handle, infos[1].handle))); assertThat(mTunerResourceManagerService .getClientProfile(ownerClientId0[0]) .getInUseFrontendHandles() @@ -1235,7 +1210,7 @@ public class TunerResourceManagerServiceTest { // Predefined Lnb request and handle array TunerLnbRequest requestLnb = new TunerLnbRequest(); requestLnb.clientId = shareClientId0[0]; - int[] lnbHandle = new int[1]; + long[] lnbHandle = new long[1]; // Request for an Lnb assertThat(mTunerResourceManagerService @@ -1264,15 +1239,13 @@ public class TunerResourceManagerServiceTest { .getInUseFrontendHandles() .isEmpty()) .isTrue(); - assertThat(mTunerResourceManagerService - .getClientProfile(shareClientId0[0]) - .getInUseLnbHandles()) - .isEqualTo(new HashSet<Integer>(Arrays.asList( - lnbHandles[0]))); + assertThat(mTunerResourceManagerService.getClientProfile(shareClientId0[0]) + .getInUseLnbHandles()) + .isEqualTo(new HashSet<Long>(Arrays.asList(lnbHandles[0]))); } private TunerFrontendInfo tunerFrontendInfo( - int handle, int frontendType, int exclusiveGroupId) { + long handle, int frontendType, int exclusiveGroupId) { TunerFrontendInfo info = new TunerFrontendInfo(); info.handle = handle; info.type = frontendType; diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java index 643ee4aadd80..62e5b9a3dccc 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java @@ -2007,6 +2007,25 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { } @Test + public void testCanInterruptNonRingtoneInsistentBuzzWithOtherBuzzyNotification() { + NotificationRecord r = getInsistentBuzzyNotification(); + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + verifyVibrateLooped(); + assertTrue(r.isInterruptive()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); + Mockito.reset(mVibrator); + + // New buzzy notification stops previous looping vibration + NotificationRecord interrupter = getBuzzyOtherNotification(); + mAttentionHelper.buzzBeepBlinkLocked(interrupter, DEFAULT_SIGNALS); + verifyStopVibrate(); + // And then vibrates itself + verifyVibrate(1); + assertTrue(interrupter.isInterruptive()); + assertNotEquals(-1, interrupter.getLastAudiblyAlertedMs()); + } + + @Test public void testRingtoneInsistentBeep_doesNotBlockFutureSoundsOnceStopped() throws Exception { NotificationChannel ringtoneChannel = new NotificationChannel("ringtone", "", IMPORTANCE_HIGH); diff --git a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java index 240bd1ec56e3..8797e63dab27 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java @@ -16,8 +16,6 @@ package com.android.server.vibrator; -import static android.os.VibrationAttributes.CATEGORY_KEYBOARD; -import static android.os.VibrationAttributes.CATEGORY_UNKNOWN; import static android.os.VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY; import static android.os.VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF; import static android.os.VibrationAttributes.USAGE_IME_FEEDBACK; @@ -255,7 +253,7 @@ public class HapticFeedbackVibrationProviderTest { } @Test - public void testKeyboardHaptic_fixAmplitude_keyboardCategoryOn_keyboardVibrationReturned() { + public void testKeyboardHaptic_fixAmplitude_keyboardVibrationReturned() { mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_TICK); mockKeyboardVibrationFixedAmplitude(KEYBOARD_VIBRATION_FIXED_AMPLITUDE); @@ -330,7 +328,7 @@ public class HapticFeedbackVibrationProviderTest { } @Test - public void testVibrationAttribute_keyboardCategoryOn_notIme_useTouchUsage() { + public void testVibrationAttribute_notIme_useTouchUsage() { HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations(); for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) { @@ -338,13 +336,11 @@ public class HapticFeedbackVibrationProviderTest { effectId, /* flags */ 0, /* privFlags */ 0); assertWithMessage("Expected USAGE_TOUCH for effect " + effectId) .that(attrs.getUsage()).isEqualTo(USAGE_TOUCH); - assertWithMessage("Expected CATEGORY_KEYBOARD for effect " + effectId) - .that(attrs.getCategory()).isEqualTo(CATEGORY_UNKNOWN); } } @Test - public void testVibrationAttribute_keyboardCategoryOn_isIme_useImeFeedbackUsage() { + public void testVibrationAttribute_isIme_useImeFeedbackUsage() { HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations(); for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) { @@ -353,8 +349,6 @@ public class HapticFeedbackVibrationProviderTest { HapticFeedbackConstants.PRIVATE_FLAG_APPLY_INPUT_METHOD_SETTINGS); assertWithMessage("Expected USAGE_IME_FEEDBACK for effect " + effectId) .that(attrs.getUsage()).isEqualTo(USAGE_IME_FEEDBACK); - assertWithMessage("Expected CATEGORY_KEYBOARD for effect " + effectId) - .that(attrs.getCategory()).isEqualTo(CATEGORY_KEYBOARD); } } diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java index 470469114dfa..9681d74eb9b3 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java @@ -281,8 +281,8 @@ public class VibrationScalerTest { @Test @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS) - public void scale_withVendorEffect_setsEffectStrengthBasedOnSettings() { - setDefaultIntensity(USAGE_NOTIFICATION, VIBRATION_INTENSITY_LOW); + public void scale_withVendorEffect_setsEffectStrengthAndScaleBasedOnSettings() { + setDefaultIntensity(USAGE_NOTIFICATION, VIBRATION_INTENSITY_MEDIUM); setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_HIGH); PersistableBundle vendorData = new PersistableBundle(); vendorData.putString("key", "value"); @@ -291,20 +291,27 @@ public class VibrationScalerTest { VibrationEffect.VendorEffect scaled = (VibrationEffect.VendorEffect) mVibrationScaler.scale(effect, USAGE_NOTIFICATION); assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG); + // Notification scales up. + assertTrue(scaled.getScale() > 1); setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_MEDIUM); scaled = (VibrationEffect.VendorEffect) mVibrationScaler.scale(effect, USAGE_NOTIFICATION); assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_MEDIUM); + // Notification does not scale. + assertEquals(1, scaled.getScale(), TOLERANCE); setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW); scaled = (VibrationEffect.VendorEffect) mVibrationScaler.scale(effect, USAGE_NOTIFICATION); assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_LIGHT); + // Notification scales down. + assertTrue(scaled.getScale() < 1); setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF); scaled = (VibrationEffect.VendorEffect) mVibrationScaler.scale(effect, USAGE_NOTIFICATION); // Vibration setting being bypassed will use default setting. - assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_LIGHT); + assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_MEDIUM); + assertEquals(1, scaled.getScale(), TOLERANCE); } @Test @@ -348,7 +355,7 @@ public class VibrationScalerTest { scaled = getFirstSegment(mVibrationScaler.scale(VibrationEffect.createOneShot(128, 128), USAGE_TOUCH)); // Haptic feedback does not scale. - assertEquals(128f / 255, scaled.getAmplitude(), 1e-5); + assertEquals(128f / 255, scaled.getAmplitude(), TOLERANCE); } @Test @@ -373,7 +380,7 @@ public class VibrationScalerTest { scaled = getFirstSegment(mVibrationScaler.scale(composed, USAGE_TOUCH)); // Haptic feedback does not scale. - assertEquals(0.5, scaled.getScale(), 1e-5); + assertEquals(0.5, scaled.getScale(), TOLERANCE); } @Test @@ -446,7 +453,7 @@ public class VibrationScalerTest { android.os.vibrator.Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED, android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS, }) - public void scale_adaptiveHapticsOnVendorEffect_setsLinearScaleParameter() { + public void scale_adaptiveHapticsOnVendorEffect_setsAdaptiveScaleParameter() { setDefaultIntensity(USAGE_RINGTONE, VIBRATION_INTENSITY_HIGH); mVibrationScaler.updateAdaptiveHapticsScale(USAGE_RINGTONE, 0.5f); @@ -457,12 +464,12 @@ public class VibrationScalerTest { VibrationEffect.VendorEffect scaled = (VibrationEffect.VendorEffect) mVibrationScaler.scale(effect, USAGE_RINGTONE); - assertEquals(scaled.getLinearScale(), 0.5f); + assertEquals(scaled.getAdaptiveScale(), 0.5f); mVibrationScaler.removeAdaptiveHapticsScale(USAGE_RINGTONE); scaled = (VibrationEffect.VendorEffect) mVibrationScaler.scale(effect, USAGE_RINGTONE); - assertEquals(scaled.getLinearScale(), 1.0f); + assertEquals(scaled.getAdaptiveScale(), 1.0f); } private void setDefaultIntensity(@VibrationAttributes.Usage int usage, diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java index 6f06050f55ff..38cd49daf99d 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java @@ -610,7 +610,6 @@ public class VibrationSettingsTest { assertVibrationIgnoredForAttributes( new VibrationAttributes.Builder() .setUsage(USAGE_IME_FEEDBACK) - .setCategory(VibrationAttributes.CATEGORY_KEYBOARD) .build(), Vibration.Status.IGNORED_FOR_SETTINGS); @@ -619,7 +618,6 @@ public class VibrationSettingsTest { assertVibrationNotIgnoredForAttributes( new VibrationAttributes.Builder() .setUsage(USAGE_IME_FEEDBACK) - .setCategory(VibrationAttributes.CATEGORY_KEYBOARD) .setFlags(VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF) .build()); } @@ -637,7 +635,6 @@ public class VibrationSettingsTest { assertVibrationNotIgnoredForAttributes( new VibrationAttributes.Builder() .setUsage(USAGE_IME_FEEDBACK) - .setCategory(VibrationAttributes.CATEGORY_KEYBOARD) .build()); } @@ -654,7 +651,6 @@ public class VibrationSettingsTest { assertVibrationIgnoredForAttributes( new VibrationAttributes.Builder() .setUsage(USAGE_IME_FEEDBACK) - .setCategory(VibrationAttributes.CATEGORY_KEYBOARD) .build(), Vibration.Status.IGNORED_FOR_SETTINGS); } diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java index 0fbdce4ce61a..bfdaa785a669 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java @@ -34,13 +34,15 @@ import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.ComponentName; -import android.content.Context; +import android.content.ContentResolver; +import android.content.ContextWrapper; import android.content.pm.PackageManagerInternal; import android.hardware.vibrator.Braking; import android.hardware.vibrator.IVibrator; @@ -52,6 +54,7 @@ import android.os.PersistableBundle; import android.os.PowerManager; import android.os.Process; import android.os.SystemClock; +import android.os.UserHandle; import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.os.Vibrator; @@ -66,11 +69,14 @@ import android.os.vibrator.VibrationEffectSegment; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.provider.Settings; import android.util.SparseArray; import androidx.test.InstrumentationRegistry; import androidx.test.filters.LargeTest; +import com.android.internal.util.test.FakeSettingsProvider; +import com.android.internal.util.test.FakeSettingsProviderRule; import com.android.server.LocalServices; import org.junit.After; @@ -105,10 +111,12 @@ public class VibrationThreadTest { private static final int TEST_DEFAULT_AMPLITUDE = 255; private static final float TEST_DEFAULT_SCALE_LEVEL_GAIN = 1.4f; - @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule(); @Rule - public final CheckFlagsRule mCheckFlagsRule = - DeviceFlagsValueProvider.createCheckFlagsRule(); + public MockitoRule mMockitoRule = MockitoJUnit.rule(); + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + @Rule + public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule(); @Mock private PackageManagerInternal mPackageManagerInternalMock; @Mock private VibrationThread.VibratorManagerHooks mManagerHooks; @@ -117,6 +125,7 @@ public class VibrationThreadTest { @Mock private VibrationConfig mVibrationConfigMock; @Mock private VibratorFrameworkStatsLogger mStatsLoggerMock; + private ContextWrapper mContextSpy; private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>(); private VibrationSettings mVibrationSettings; private VibrationScaler mVibrationScaler; @@ -149,14 +158,16 @@ public class VibrationThreadTest { LocalServices.removeServiceForTest(PackageManagerInternal.class); LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternalMock); - Context context = InstrumentationRegistry.getContext(); - mVibrationSettings = new VibrationSettings(context, new Handler(mTestLooper.getLooper()), - mVibrationConfigMock); + mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext())); + ContentResolver contentResolver = mSettingsProviderRule.mockContentResolver(mContextSpy); + when(mContextSpy.getContentResolver()).thenReturn(contentResolver); + mVibrationSettings = new VibrationSettings(mContextSpy, + new Handler(mTestLooper.getLooper()), mVibrationConfigMock); mVibrationScaler = new VibrationScaler(mVibrationConfigMock, mVibrationSettings); mockVibrators(VIBRATOR_ID); - PowerManager.WakeLock wakeLock = context.getSystemService( + PowerManager.WakeLock wakeLock = mContextSpy.getSystemService( PowerManager.class).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*"); mThread = new VibrationThread(wakeLock, mManagerHooks); mThread.start(); @@ -254,6 +265,9 @@ public class VibrationThreadTest { @Test @RequiresFlagsEnabled(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED) public void vibrate_singleWaveformWithAdaptiveHapticsScaling_scalesAmplitudesProperly() { + // No user settings scale. + setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, + Vibrator.VIBRATION_INTENSITY_MEDIUM); mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); VibrationEffect effect = VibrationEffect.createWaveform( @@ -277,6 +291,9 @@ public class VibrationThreadTest { @Test @RequiresFlagsEnabled(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED) public void vibrate_withVibrationParamsRequestStalling_timeoutRequestAndApplyNoScaling() { + // No user settings scale. + setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, + Vibrator.VIBRATION_INTENSITY_MEDIUM); mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); VibrationEffect effect = VibrationEffect.createWaveform( new long[]{5, 5, 5}, new int[]{1, 1, 1}, -1); @@ -1864,6 +1881,13 @@ public class VibrationThreadTest { } } + private void setUserSetting(String settingName, int value) { + Settings.System.putIntForUser( + mContextSpy.getContentResolver(), settingName, value, UserHandle.USER_CURRENT); + // FakeSettingsProvider doesn't support testing triggering ContentObserver yet. + mVibrationSettings.mSettingObserver.onChange(false); + } + private long startThreadAndDispatcher(VibrationEffect effect) { return startThreadAndDispatcher(CombinedVibration.createParallel(effect)); } diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java index f009229e216d..40135876303b 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java @@ -1622,7 +1622,12 @@ public class VibratorManagerServiceTest { vibrateAndWaitUntilFinished(service, vendorEffect, RINGTONE_ATTRS); - assertThat(fakeVibrator.getAllVendorEffects()).containsExactly(vendorEffect); + // Compare vendor data only, ignore scale applied by device settings in this test. + assertThat(fakeVibrator.getAllVendorEffects()).hasSize(1); + assertThat(fakeVibrator.getAllVendorEffects().get(0).getVendorData().keySet()) + .containsExactly("key"); + assertThat(fakeVibrator.getAllVendorEffects().get(0).getVendorData().getString("key")) + .isEqualTo("value"); } @Test @@ -1765,7 +1770,8 @@ public class VibratorManagerServiceTest { assertThat(fakeVibrator.getAllVendorEffects()).hasSize(1); VibrationEffect.VendorEffect scaled = fakeVibrator.getAllVendorEffects().get(0); assertThat(scaled.getEffectStrength()).isEqualTo(VibrationEffect.EFFECT_STRENGTH_LIGHT); - assertThat(scaled.getLinearScale()).isEqualTo(0.4f); + assertThat(scaled.getScale()).isAtMost(1); // Scale down or none if default is LOW + assertThat(scaled.getAdaptiveScale()).isEqualTo(0.4f); } @Test diff --git a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java index 96c3e97bc819..031d1c29a215 100644 --- a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java +++ b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java @@ -140,13 +140,13 @@ public final class FakeVibratorControllerProvider { @Override public long performVendorEffect(Parcel vendorData, long strength, float scale, - long vibrationId) { + float adaptiveScale, long vibrationId) { if ((mCapabilities & IVibrator.CAP_PERFORM_VENDOR_EFFECTS) == 0) { return 0; } PersistableBundle bundle = PersistableBundle.CREATOR.createFromParcel(vendorData); recordVendorEffect(vibrationId, - new VibrationEffect.VendorEffect(bundle, (int) strength, scale)); + new VibrationEffect.VendorEffect(bundle, (int) strength, scale, adaptiveScale)); applyLatency(mOnLatency); scheduleListener(mVendorEffectDuration, vibrationId); // HAL has unknown duration for vendor effects. diff --git a/services/tests/wmtests/Android.bp b/services/tests/wmtests/Android.bp index 6ba2c7010cf3..604869c7f0ca 100644 --- a/services/tests/wmtests/Android.bp +++ b/services/tests/wmtests/Android.bp @@ -57,6 +57,7 @@ android_test { "service-permission.stubs.system_server", "androidx.test.runner", "androidx.test.rules", + "flickerlib", "junit-params", "mockito-target-extended-minus-junit4", "platform-test-annotations", diff --git a/services/tests/wmtests/src/com/android/server/policy/KeyboardSystemShortcutTests.java b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java index e26f3e0f699a..8f3adba81be4 100644 --- a/services/tests/wmtests/src/com/android/server/policy/KeyboardSystemShortcutTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java @@ -22,7 +22,7 @@ import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_HOME_ASSIS import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_HOME_NOTIFICATION_PANEL; import static com.android.server.policy.PhoneWindowManager.SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL; -import android.hardware.input.KeyboardSystemShortcut; +import android.hardware.input.KeyGestureEvent; import android.platform.test.annotations.Presubmit; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; @@ -44,7 +44,7 @@ import org.junit.runner.RunWith; @Presubmit @MediumTest @RunWith(JUnitParamsRunner.class) -public class KeyboardSystemShortcutTests extends ShortcutKeyTestBase { +public class KeyGestureEventTests extends ShortcutKeyTestBase { @Rule public final CheckFlagsRule mCheckFlagsRule = @@ -56,315 +56,313 @@ public class KeyboardSystemShortcutTests extends ShortcutKeyTestBase { private static final int ALT_ON = MODIFIER.get(KeyEvent.KEYCODE_ALT_LEFT); private static final int CTRL_KEY = KeyEvent.KEYCODE_CTRL_LEFT; private static final int CTRL_ON = MODIFIER.get(KeyEvent.KEYCODE_CTRL_LEFT); - private static final int SHIFT_KEY = KeyEvent.KEYCODE_SHIFT_LEFT; - private static final int SHIFT_ON = MODIFIER.get(KeyEvent.KEYCODE_SHIFT_LEFT); @Keep private static Object[][] shortcutTestArguments() { - // testName, testKeys, expectedSystemShortcut, expectedKey, expectedModifierState + // testName, testKeys, expectedKeyGestureType, expectedKey, expectedModifierState return new Object[][]{ {"Meta + H -> Open Home", new int[]{META_KEY, KeyEvent.KEYCODE_H}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_HOME, KeyEvent.KEYCODE_H, META_ON}, + KeyGestureEvent.KEY_GESTURE_TYPE_HOME, KeyEvent.KEYCODE_H, META_ON}, {"Meta + Enter -> Open Home", new int[]{META_KEY, KeyEvent.KEYCODE_ENTER}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_HOME, KeyEvent.KEYCODE_ENTER, + KeyGestureEvent.KEY_GESTURE_TYPE_HOME, KeyEvent.KEYCODE_ENTER, META_ON}, {"HOME key -> Open Home", new int[]{KeyEvent.KEYCODE_HOME}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_HOME, + KeyGestureEvent.KEY_GESTURE_TYPE_HOME, KeyEvent.KEYCODE_HOME, 0}, {"RECENT_APPS key -> Open Overview", new int[]{KeyEvent.KEYCODE_RECENT_APPS}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_RECENT_APPS, + KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS, KeyEvent.KEYCODE_RECENT_APPS, 0}, {"Meta + Tab -> Open Overview", new int[]{META_KEY, KeyEvent.KEYCODE_TAB}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_RECENT_APPS, KeyEvent.KEYCODE_TAB, + KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS, KeyEvent.KEYCODE_TAB, META_ON}, {"Alt + Tab -> Open Overview", new int[]{ALT_KEY, KeyEvent.KEYCODE_TAB}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_RECENT_APPS, KeyEvent.KEYCODE_TAB, + KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS, KeyEvent.KEYCODE_TAB, ALT_ON}, {"BACK key -> Go back", new int[]{KeyEvent.KEYCODE_BACK}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_BACK, + KeyGestureEvent.KEY_GESTURE_TYPE_BACK, KeyEvent.KEYCODE_BACK, 0}, {"Meta + Escape -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_ESCAPE}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_BACK, KeyEvent.KEYCODE_ESCAPE, + KeyGestureEvent.KEY_GESTURE_TYPE_BACK, KeyEvent.KEYCODE_ESCAPE, META_ON}, {"Meta + Left arrow -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_DPAD_LEFT}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_BACK, KeyEvent.KEYCODE_DPAD_LEFT, + KeyGestureEvent.KEY_GESTURE_TYPE_BACK, KeyEvent.KEYCODE_DPAD_LEFT, META_ON}, {"Meta + Del -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_DEL}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_BACK, KeyEvent.KEYCODE_DEL, META_ON}, + KeyGestureEvent.KEY_GESTURE_TYPE_BACK, KeyEvent.KEYCODE_DEL, META_ON}, {"APP_SWITCH key -> Open App switcher", new int[]{KeyEvent.KEYCODE_APP_SWITCH}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_APP_SWITCH, + KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH, KeyEvent.KEYCODE_APP_SWITCH, 0}, {"ASSIST key -> Launch assistant", new int[]{KeyEvent.KEYCODE_ASSIST}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_ASSISTANT, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT, KeyEvent.KEYCODE_ASSIST, 0}, {"Meta + A -> Launch assistant", new int[]{META_KEY, KeyEvent.KEYCODE_A}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_ASSISTANT, KeyEvent.KEYCODE_A, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT, KeyEvent.KEYCODE_A, META_ON}, {"VOICE_ASSIST key -> Launch Voice Assistant", new int[]{KeyEvent.KEYCODE_VOICE_ASSIST}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT, KeyEvent.KEYCODE_VOICE_ASSIST, 0}, {"Meta + I -> Launch System Settings", new int[]{META_KEY, KeyEvent.KEYCODE_I}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS, KeyEvent.KEYCODE_I, META_ON}, {"Meta + N -> Toggle Notification panel", new int[]{META_KEY, KeyEvent.KEYCODE_N}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL, + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL, KeyEvent.KEYCODE_N, META_ON}, {"NOTIFICATION key -> Toggle Notification Panel", new int[]{KeyEvent.KEYCODE_NOTIFICATION}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL, + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL, KeyEvent.KEYCODE_NOTIFICATION, 0}, {"Meta + Ctrl + S -> Take Screenshot", new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_S}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_TAKE_SCREENSHOT, KeyEvent.KEYCODE_S, + KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT, KeyEvent.KEYCODE_S, META_ON | CTRL_ON}, {"Meta + / -> Open Shortcut Helper", new int[]{META_KEY, KeyEvent.KEYCODE_SLASH}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER, + KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER, KeyEvent.KEYCODE_SLASH, META_ON}, {"BRIGHTNESS_UP key -> Increase Brightness", new int[]{KeyEvent.KEYCODE_BRIGHTNESS_UP}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_BRIGHTNESS_UP, + KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_UP, KeyEvent.KEYCODE_BRIGHTNESS_UP, 0}, {"BRIGHTNESS_DOWN key -> Decrease Brightness", new int[]{KeyEvent.KEYCODE_BRIGHTNESS_DOWN}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_BRIGHTNESS_DOWN, + KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_DOWN, KeyEvent.KEYCODE_BRIGHTNESS_DOWN, 0}, {"KEYBOARD_BACKLIGHT_UP key -> Increase Keyboard Backlight", new int[]{KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP, + KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP, KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP, 0}, {"KEYBOARD_BACKLIGHT_DOWN key -> Decrease Keyboard Backlight", new int[]{KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN, + KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN, KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN, 0}, {"KEYBOARD_BACKLIGHT_TOGGLE key -> Toggle Keyboard Backlight", new int[]{KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE, + KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE, KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE, 0}, {"VOLUME_UP key -> Increase Volume", new int[]{KeyEvent.KEYCODE_VOLUME_UP}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_VOLUME_UP, + KeyGestureEvent.KEY_GESTURE_TYPE_VOLUME_UP, KeyEvent.KEYCODE_VOLUME_UP, 0}, {"VOLUME_DOWN key -> Decrease Volume", new int[]{KeyEvent.KEYCODE_VOLUME_DOWN}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_VOLUME_DOWN, + KeyGestureEvent.KEY_GESTURE_TYPE_VOLUME_DOWN, KeyEvent.KEYCODE_VOLUME_DOWN, 0}, {"VOLUME_MUTE key -> Mute Volume", new int[]{KeyEvent.KEYCODE_VOLUME_MUTE}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_VOLUME_MUTE, + KeyGestureEvent.KEY_GESTURE_TYPE_VOLUME_MUTE, KeyEvent.KEYCODE_VOLUME_MUTE, 0}, {"ALL_APPS key -> Open App Drawer in Accessibility mode", new int[]{KeyEvent.KEYCODE_ALL_APPS}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS, + KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS, KeyEvent.KEYCODE_ALL_APPS, 0}, {"SEARCH key -> Launch Search Activity", new int[]{KeyEvent.KEYCODE_SEARCH}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_SEARCH, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SEARCH, KeyEvent.KEYCODE_SEARCH, 0}, {"LANGUAGE_SWITCH key -> Switch Keyboard Language", new int[]{KeyEvent.KEYCODE_LANGUAGE_SWITCH}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_LANGUAGE_SWITCH, + KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH, KeyEvent.KEYCODE_LANGUAGE_SWITCH, 0}, {"META key -> Open App Drawer in Accessibility mode", new int[]{META_KEY}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS, META_KEY, + KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS, META_KEY, META_ON}, {"Meta + Alt -> Toggle CapsLock", new int[]{META_KEY, ALT_KEY}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK, ALT_KEY, + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK, ALT_KEY, META_ON | ALT_ON}, {"Alt + Meta -> Toggle CapsLock", new int[]{ALT_KEY, META_KEY}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK, META_KEY, + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK, META_KEY, META_ON | ALT_ON}, {"CAPS_LOCK key -> Toggle CapsLock", new int[]{KeyEvent.KEYCODE_CAPS_LOCK}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK, + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK, KeyEvent.KEYCODE_CAPS_LOCK, 0}, {"MUTE key -> Mute System Microphone", new int[]{KeyEvent.KEYCODE_MUTE}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_SYSTEM_MUTE, KeyEvent.KEYCODE_MUTE, + KeyGestureEvent.KEY_GESTURE_TYPE_SYSTEM_MUTE, KeyEvent.KEYCODE_MUTE, 0}, {"Meta + Ctrl + DPAD_UP -> Split screen navigation", new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_UP}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION, + KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION, KeyEvent.KEYCODE_DPAD_UP, META_ON | CTRL_ON}, {"Meta + Ctrl + DPAD_LEFT -> Split screen navigation", new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_LEFT}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION, + KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION, KeyEvent.KEYCODE_DPAD_LEFT, META_ON | CTRL_ON}, {"Meta + Ctrl + DPAD_RIGHT -> Split screen navigation", new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_RIGHT}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION, + KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION, KeyEvent.KEYCODE_DPAD_RIGHT, META_ON | CTRL_ON}, {"Meta + L -> Lock Homescreen", new int[]{META_KEY, KeyEvent.KEYCODE_L}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_LOCK_SCREEN, KeyEvent.KEYCODE_L, + KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN, KeyEvent.KEYCODE_L, META_ON}, {"Meta + Ctrl + N -> Open Notes", new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_N}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_OPEN_NOTES, KeyEvent.KEYCODE_N, + KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES, KeyEvent.KEYCODE_N, META_ON | CTRL_ON}, {"POWER key -> Toggle Power", new int[]{KeyEvent.KEYCODE_POWER}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_POWER, KeyEvent.KEYCODE_POWER, + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_POWER, KeyEvent.KEYCODE_POWER, 0}, {"TV_POWER key -> Toggle Power", new int[]{KeyEvent.KEYCODE_TV_POWER}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_POWER, + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_POWER, KeyEvent.KEYCODE_TV_POWER, 0}, {"SYSTEM_NAVIGATION_DOWN key -> System Navigation", new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_SYSTEM_NAVIGATION, + KeyGestureEvent.KEY_GESTURE_TYPE_SYSTEM_NAVIGATION, KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN, 0}, {"SYSTEM_NAVIGATION_UP key -> System Navigation", new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_SYSTEM_NAVIGATION, + KeyGestureEvent.KEY_GESTURE_TYPE_SYSTEM_NAVIGATION, KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP, 0}, {"SYSTEM_NAVIGATION_LEFT key -> System Navigation", new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_LEFT}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_SYSTEM_NAVIGATION, + KeyGestureEvent.KEY_GESTURE_TYPE_SYSTEM_NAVIGATION, KeyEvent.KEYCODE_SYSTEM_NAVIGATION_LEFT, 0}, {"SYSTEM_NAVIGATION_RIGHT key -> System Navigation", new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_RIGHT}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_SYSTEM_NAVIGATION, + KeyGestureEvent.KEY_GESTURE_TYPE_SYSTEM_NAVIGATION, KeyEvent.KEYCODE_SYSTEM_NAVIGATION_RIGHT, 0}, {"SLEEP key -> System Sleep", new int[]{KeyEvent.KEYCODE_SLEEP}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_SLEEP, KeyEvent.KEYCODE_SLEEP, 0}, + KeyGestureEvent.KEY_GESTURE_TYPE_SLEEP, KeyEvent.KEYCODE_SLEEP, 0}, {"SOFT_SLEEP key -> System Sleep", new int[]{KeyEvent.KEYCODE_SOFT_SLEEP}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_SLEEP, KeyEvent.KEYCODE_SOFT_SLEEP, + KeyGestureEvent.KEY_GESTURE_TYPE_SLEEP, KeyEvent.KEYCODE_SOFT_SLEEP, 0}, {"WAKEUP key -> System Wakeup", new int[]{KeyEvent.KEYCODE_WAKEUP}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_WAKEUP, KeyEvent.KEYCODE_WAKEUP, 0}, + KeyGestureEvent.KEY_GESTURE_TYPE_WAKEUP, KeyEvent.KEYCODE_WAKEUP, 0}, {"MEDIA_PLAY key -> Media Control", new int[]{KeyEvent.KEYCODE_MEDIA_PLAY}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_MEDIA_KEY, + KeyGestureEvent.KEY_GESTURE_TYPE_MEDIA_KEY, KeyEvent.KEYCODE_MEDIA_PLAY, 0}, {"MEDIA_PAUSE key -> Media Control", new int[]{KeyEvent.KEYCODE_MEDIA_PAUSE}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_MEDIA_KEY, + KeyGestureEvent.KEY_GESTURE_TYPE_MEDIA_KEY, KeyEvent.KEYCODE_MEDIA_PAUSE, 0}, {"MEDIA_PLAY_PAUSE key -> Media Control", new int[]{KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_MEDIA_KEY, + KeyGestureEvent.KEY_GESTURE_TYPE_MEDIA_KEY, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, 0}, {"Meta + B -> Launch Default Browser", new int[]{META_KEY, KeyEvent.KEYCODE_B}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER, KeyEvent.KEYCODE_B, META_ON}, {"EXPLORER key -> Launch Default Browser", new int[]{KeyEvent.KEYCODE_EXPLORER}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER, KeyEvent.KEYCODE_EXPLORER, 0}, {"Meta + C -> Launch Default Contacts", new int[]{META_KEY, KeyEvent.KEYCODE_C}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS, KeyEvent.KEYCODE_C, META_ON}, {"CONTACTS key -> Launch Default Contacts", new int[]{KeyEvent.KEYCODE_CONTACTS}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS, KeyEvent.KEYCODE_CONTACTS, 0}, {"Meta + E -> Launch Default Email", new int[]{META_KEY, KeyEvent.KEYCODE_E}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL, KeyEvent.KEYCODE_E, META_ON}, {"ENVELOPE key -> Launch Default Email", new int[]{KeyEvent.KEYCODE_ENVELOPE}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL, KeyEvent.KEYCODE_ENVELOPE, 0}, {"Meta + K -> Launch Default Calendar", new int[]{META_KEY, KeyEvent.KEYCODE_K}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR, KeyEvent.KEYCODE_K, META_ON}, {"CALENDAR key -> Launch Default Calendar", new int[]{KeyEvent.KEYCODE_CALENDAR}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR, KeyEvent.KEYCODE_CALENDAR, 0}, {"Meta + P -> Launch Default Music", new int[]{META_KEY, KeyEvent.KEYCODE_P}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC, KeyEvent.KEYCODE_P, META_ON}, {"MUSIC key -> Launch Default Music", new int[]{KeyEvent.KEYCODE_MUSIC}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC, KeyEvent.KEYCODE_MUSIC, 0}, {"Meta + U -> Launch Default Calculator", new int[]{META_KEY, KeyEvent.KEYCODE_U}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR, KeyEvent.KEYCODE_U, META_ON}, {"CALCULATOR key -> Launch Default Calculator", new int[]{KeyEvent.KEYCODE_CALCULATOR}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR, KeyEvent.KEYCODE_CALCULATOR, 0}, {"Meta + M -> Launch Default Maps", new int[]{META_KEY, KeyEvent.KEYCODE_M}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS, KeyEvent.KEYCODE_M, META_ON}, {"Meta + S -> Launch Default Messaging App", new int[]{META_KEY, KeyEvent.KEYCODE_S}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING, KeyEvent.KEYCODE_S, META_ON}, {"Meta + Ctrl + DPAD_DOWN -> Enter desktop mode", new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_DOWN}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_DESKTOP_MODE, + KeyGestureEvent.KEY_GESTURE_TYPE_DESKTOP_MODE, KeyEvent.KEYCODE_DPAD_DOWN, META_ON | CTRL_ON}}; } @Keep private static Object[][] longPressOnHomeTestArguments() { - // testName, testKeys, longPressOnHomeBehavior, expectedSystemShortcut, expectedKey, + // testName, testKeys, longPressOnHomeBehavior, expectedKeyGestureType, expectedKey, // expectedModifierState return new Object[][]{ {"Long press HOME key -> Toggle Notification panel", new int[]{KeyEvent.KEYCODE_HOME}, LONG_PRESS_HOME_NOTIFICATION_PANEL, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL, + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL, KeyEvent.KEYCODE_HOME, 0}, {"Long press META + ENTER -> Toggle Notification panel", new int[]{META_KEY, KeyEvent.KEYCODE_ENTER}, LONG_PRESS_HOME_NOTIFICATION_PANEL, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL, + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL, KeyEvent.KEYCODE_ENTER, META_ON}, {"Long press META + H -> Toggle Notification panel", new int[]{META_KEY, KeyEvent.KEYCODE_H}, LONG_PRESS_HOME_NOTIFICATION_PANEL, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL, + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL, KeyEvent.KEYCODE_H, META_ON}, {"Long press HOME key -> Launch assistant", new int[]{KeyEvent.KEYCODE_HOME}, LONG_PRESS_HOME_ASSIST, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_ASSISTANT, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT, KeyEvent.KEYCODE_HOME, 0}, {"Long press META + ENTER -> Launch assistant", new int[]{META_KEY, KeyEvent.KEYCODE_ENTER}, LONG_PRESS_HOME_ASSIST, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_ASSISTANT, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT, KeyEvent.KEYCODE_ENTER, META_ON}, {"Long press META + H -> Launch assistant", new int[]{META_KEY, KeyEvent.KEYCODE_H}, LONG_PRESS_HOME_ASSIST, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_ASSISTANT, KeyEvent.KEYCODE_H, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT, KeyEvent.KEYCODE_H, META_ON}, {"Long press HOME key -> Open App Drawer in Accessibility mode", new int[]{KeyEvent.KEYCODE_HOME}, LONG_PRESS_HOME_ALL_APPS, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS, + KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS, KeyEvent.KEYCODE_HOME, 0}, {"Long press META + ENTER -> Open App Drawer in Accessibility mode", new int[]{META_KEY, KeyEvent.KEYCODE_ENTER}, LONG_PRESS_HOME_ALL_APPS, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS, + KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS, KeyEvent.KEYCODE_ENTER, META_ON}, {"Long press META + H -> Open App Drawer in Accessibility mode", new int[]{META_KEY, KeyEvent.KEYCODE_H}, LONG_PRESS_HOME_ALL_APPS, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS, + KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS, KeyEvent.KEYCODE_H, META_ON}}; } @Keep private static Object[][] doubleTapOnHomeTestArguments() { - // testName, testKeys, doubleTapOnHomeBehavior, expectedSystemShortcut, expectedKey, + // testName, testKeys, doubleTapOnHomeBehavior, expectedKeyGestureType, expectedKey, // expectedModifierState return new Object[][]{ {"Double tap HOME -> Open App switcher", new int[]{KeyEvent.KEYCODE_HOME}, DOUBLE_TAP_HOME_RECENT_SYSTEM_UI, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_APP_SWITCH, KeyEvent.KEYCODE_HOME, + KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH, KeyEvent.KEYCODE_HOME, 0}, {"Double tap META + ENTER -> Open App switcher", new int[]{META_KEY, KeyEvent.KEYCODE_ENTER}, DOUBLE_TAP_HOME_RECENT_SYSTEM_UI, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_APP_SWITCH, + KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH, KeyEvent.KEYCODE_ENTER, META_ON}, {"Double tap META + H -> Open App switcher", new int[]{META_KEY, KeyEvent.KEYCODE_H}, DOUBLE_TAP_HOME_RECENT_SYSTEM_UI, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_APP_SWITCH, KeyEvent.KEYCODE_H, + KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH, KeyEvent.KEYCODE_H, META_ON}}; } @Keep private static Object[][] settingsKeyTestArguments() { - // testName, testKeys, settingsKeyBehavior, expectedSystemShortcut, expectedKey, + // testName, testKeys, settingsKeyBehavior, expectedKeyGestureType, expectedKey, // expectedModifierState return new Object[][]{ {"SETTINGS key -> Toggle Notification panel", new int[]{KeyEvent.KEYCODE_SETTINGS}, SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL, + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL, KeyEvent.KEYCODE_SETTINGS, 0}}; } @@ -387,21 +385,21 @@ public class KeyboardSystemShortcutTests extends ShortcutKeyTestBase { @Test @Parameters(method = "shortcutTestArguments") public void testShortcut(String testName, int[] testKeys, - @KeyboardSystemShortcut.SystemShortcut int expectedSystemShortcut, int expectedKey, + @KeyGestureEvent.KeyGestureType int expectedKeyGestureType, int expectedKey, int expectedModifierState) { - testShortcutInternal(testName, testKeys, expectedSystemShortcut, expectedKey, + testShortcutInternal(testName, testKeys, expectedKeyGestureType, expectedKey, expectedModifierState); } @Test @Parameters(method = "longPressOnHomeTestArguments") public void testLongPressOnHome(String testName, int[] testKeys, int longPressOnHomeBehavior, - @KeyboardSystemShortcut.SystemShortcut int expectedSystemShortcut, int expectedKey, + @KeyGestureEvent.KeyGestureType int expectedKeyGestureType, int expectedKey, int expectedModifierState) { mPhoneWindowManager.overrideLongPressOnHomeBehavior(longPressOnHomeBehavior); sendLongPressKeyCombination(testKeys); - mPhoneWindowManager.assertKeyboardShortcutTriggered( - new int[]{expectedKey}, expectedModifierState, expectedSystemShortcut, + mPhoneWindowManager.assertKeyGestureCompleted( + new int[]{expectedKey}, expectedModifierState, expectedKeyGestureType, "Failed while executing " + testName); } @@ -409,23 +407,23 @@ public class KeyboardSystemShortcutTests extends ShortcutKeyTestBase { @Parameters(method = "doubleTapOnHomeTestArguments") public void testDoubleTapOnHomeBehavior(String testName, int[] testKeys, int doubleTapOnHomeBehavior, - @KeyboardSystemShortcut.SystemShortcut int expectedSystemShortcut, int expectedKey, + @KeyGestureEvent.KeyGestureType int expectedKeyGestureType, int expectedKey, int expectedModifierState) { mPhoneWindowManager.overriderDoubleTapOnHomeBehavior(doubleTapOnHomeBehavior); sendKeyCombination(testKeys, 0 /* duration */); sendKeyCombination(testKeys, 0 /* duration */); - mPhoneWindowManager.assertKeyboardShortcutTriggered( - new int[]{expectedKey}, expectedModifierState, expectedSystemShortcut, + mPhoneWindowManager.assertKeyGestureCompleted( + new int[]{expectedKey}, expectedModifierState, expectedKeyGestureType, "Failed while executing " + testName); } @Test @Parameters(method = "settingsKeyTestArguments") public void testSettingsKey(String testName, int[] testKeys, int settingsKeyBehavior, - @KeyboardSystemShortcut.SystemShortcut int expectedSystemShortcut, int expectedKey, + @KeyGestureEvent.KeyGestureType int expectedKeyGestureType, int expectedKey, int expectedModifierState) { mPhoneWindowManager.overrideSettingsKeyBehavior(settingsKeyBehavior); - testShortcutInternal(testName, testKeys, expectedSystemShortcut, expectedKey, + testShortcutInternal(testName, testKeys, expectedKeyGestureType, expectedKey, expectedModifierState); } @@ -434,16 +432,16 @@ public class KeyboardSystemShortcutTests extends ShortcutKeyTestBase { public void testBugreportShortcutPress() { testShortcutInternal("Meta + Ctrl + Del -> Trigger bug report", new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DEL}, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT, KeyEvent.KEYCODE_DEL, + KeyGestureEvent.KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT, KeyEvent.KEYCODE_DEL, META_ON | CTRL_ON); } private void testShortcutInternal(String testName, int[] testKeys, - @KeyboardSystemShortcut.SystemShortcut int expectedSystemShortcut, int expectedKey, + @KeyGestureEvent.KeyGestureType int expectedKeyGestureType, int expectedKey, int expectedModifierState) { sendKeyCombination(testKeys, 0 /* duration */); - mPhoneWindowManager.assertKeyboardShortcutTriggered( - new int[]{expectedKey}, expectedModifierState, expectedSystemShortcut, + mPhoneWindowManager.assertKeyGestureCompleted( + new int[]{expectedKey}, expectedModifierState, expectedKeyGestureType, "Failed while executing " + testName); } } diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java index f9b5c2a6c77f..43b065dbb6a5 100644 --- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java +++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java @@ -70,7 +70,6 @@ import android.hardware.SensorPrivacyManager; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerInternal; import android.hardware.input.InputManager; -import android.hardware.input.KeyboardSystemShortcut; import android.media.AudioManagerInternal; import android.os.Handler; import android.os.HandlerThread; @@ -804,11 +803,11 @@ class TestPhoneWindowManager { Assert.assertEquals(targetActivity, intentCaptor.getValue().getComponent()); } - void assertKeyboardShortcutTriggered(int[] keycodes, int modifierState, int systemShortcut, + void assertKeyGestureCompleted(int[] keycodes, int modifierState, int gestureType, String errorMsg) { mTestLooper.dispatchAll(); - verify(mInputManagerInternal, description(errorMsg)).notifyKeyboardShortcutTriggered( - anyInt(), eq(keycodes), eq(modifierState), eq(systemShortcut)); + verify(mInputManagerInternal, description(errorMsg)).notifyKeyGestureCompleted( + anyInt(), eq(keycodes), eq(modifierState), eq(gestureType)); } void assertSwitchToTask(int persistentId) throws RemoteException { diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java index 14fbbe4cf2ef..cb17f35f64d7 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java @@ -212,7 +212,6 @@ public class ActivityRefresherTests extends WindowTestsBase { .build() .getTopMostActivity(); - spyOn(mActivity.mLetterboxUiController); spyOn(mActivity.mAppCompatController.getAppCompatCameraOverrides()); doReturn(true).when(mActivity).inFreeformWindowingMode(); doReturn(true).when(mActivity.mAppCompatController diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java index a74572431d6b..1fa68686850e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java @@ -420,7 +420,6 @@ class AppCompatActivityRobot { */ @CallSuper void onPostActivityCreation(@NonNull ActivityRecord activity) { - spyOn(activity.mLetterboxUiController); if (mOnPostActivityCreation != null) { mOnPostActivityCreation.accept(activity); } diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java index 84ffcb8956a9..ba2a7335824c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java @@ -374,7 +374,6 @@ public class AppCompatCameraOverridesTest extends WindowTestsBase { * Runs a test scenario providing a Robot. */ void runTestScenario(@NonNull Consumer<CameraOverridesRobotTest> consumer) { - spyOn(mWm.mAppCompatConfiguration); final CameraOverridesRobotTest robot = new CameraOverridesRobotTest(mWm, mAtm, mSupervisor); consumer.accept(robot); } diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java index c42228dcc6ba..2ae23f88812a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java @@ -20,6 +20,8 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.window.flags.Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import android.compat.testing.PlatformCompatChangeRule; @@ -29,7 +31,6 @@ import android.platform.test.annotations.Presubmit; import androidx.annotation.NonNull; -import org.junit.Assert; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TestRule; @@ -53,18 +54,28 @@ public class AppCompatCameraPolicyTest extends WindowTestsBase { @Test public void testDisplayRotationCompatPolicy_presentWhenEnabled() { runTestScenario((robot) -> { - robot.conf().enableCameraCompatTreatmentAtBuildTime(true); + robot.conf().enableCameraCompatTreatmentAtBuildTime(/* enabled= */ true); robot.activity().createActivityWithComponentInNewTaskAndDisplay(); - robot.checkTopActivityHasDisplayRotationCompatPolicy(true); + robot.checkTopActivityHasDisplayRotationCompatPolicy(/* exists= */ true); }); } @Test public void testDisplayRotationCompatPolicy_notPresentWhenDisabled() { runTestScenario((robot) -> { - robot.conf().enableCameraCompatTreatmentAtBuildTime(false); + robot.conf().enableCameraCompatTreatmentAtBuildTime(/* enabled= */ false); robot.activity().createActivityWithComponentInNewTaskAndDisplay(); - robot.checkTopActivityHasDisplayRotationCompatPolicy(false); + robot.checkTopActivityHasDisplayRotationCompatPolicy(/* exists= */ false); + }); + } + + @Test + public void testDisplayRotationCompatPolicy_startedWhenEnabled() { + runTestScenario((robot) -> { + robot.conf().enableCameraCompatTreatmentAtBuildTime(/* enabled= */ true); + robot.activity().createActivityWithComponentInNewTaskAndDisplay(); + robot.checkTopActivityHasDisplayRotationCompatPolicy(/* exists= */ true); + robot.checkTopActivityDisplayRotationCompatPolicyIsRunning(); }); } @@ -72,9 +83,9 @@ public class AppCompatCameraPolicyTest extends WindowTestsBase { @EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM) public void testCameraCompatFreeformPolicy_presentWhenEnabledAndDW() { runTestScenario((robot) -> { - robot.allowEnterDesktopMode(true); + robot.allowEnterDesktopMode(/* isAllowed= */ true); robot.activity().createActivityWithComponentInNewTaskAndDisplay(); - robot.checkTopActivityHasCameraCompatFreeformPolicy(true); + robot.checkTopActivityHasCameraCompatFreeformPolicy(/* exists= */ true); }); } @@ -82,9 +93,9 @@ public class AppCompatCameraPolicyTest extends WindowTestsBase { @EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM) public void testCameraCompatFreeformPolicy_notPresentWhenNoDW() { runTestScenario((robot) -> { - robot.allowEnterDesktopMode(false); + robot.allowEnterDesktopMode(/* isAllowed= */ false); robot.activity().createActivityWithComponentInNewTaskAndDisplay(); - robot.checkTopActivityHasCameraCompatFreeformPolicy(false); + robot.checkTopActivityHasCameraCompatFreeformPolicy(/* exists= */ false); }); } @@ -92,9 +103,9 @@ public class AppCompatCameraPolicyTest extends WindowTestsBase { @DisableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM) public void testCameraCompatFreeformPolicy_notPresentWhenNoFlag() { runTestScenario((robot) -> { - robot.allowEnterDesktopMode(true); + robot.allowEnterDesktopMode(/* isAllowed= */ true); robot.activity().createActivityWithComponentInNewTaskAndDisplay(); - robot.checkTopActivityHasCameraCompatFreeformPolicy(false); + robot.checkTopActivityHasCameraCompatFreeformPolicy(/* exists= */ false); }); } @@ -102,19 +113,86 @@ public class AppCompatCameraPolicyTest extends WindowTestsBase { @EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM) public void testCameraCompatFreeformPolicy_notPresentWhenNoFlagAndNoDW() { runTestScenario((robot) -> { - robot.allowEnterDesktopMode(false); + robot.allowEnterDesktopMode(/* isAllowed= */ false); + robot.activity().createActivityWithComponentInNewTaskAndDisplay(); + robot.checkTopActivityHasCameraCompatFreeformPolicy(/* exists= */ false); + }); + } + + @Test + @EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM) + public void testCameraCompatFreeformPolicy_startedWhenEnabledAndDW() { + runTestScenario((robot) -> { + robot.allowEnterDesktopMode(/* isAllowed= */ true); robot.activity().createActivityWithComponentInNewTaskAndDisplay(); - robot.checkTopActivityHasCameraCompatFreeformPolicy(false); + robot.checkTopActivityHasCameraCompatFreeformPolicy(/* exists= */ true); + robot.checkTopActivityCameraCompatFreeformPolicyIsRunning(); + }); + } + + @Test + @EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM) + public void testCameraStateManager_existsWhenCameraCompatFreeformExists() { + runTestScenario((robot) -> { + robot.allowEnterDesktopMode(true); + robot.activity().createActivityWithComponentInNewTaskAndDisplay(); + robot.checkTopActivityHasCameraCompatFreeformPolicy(/* exists= */ true); + robot.checkTopActivityHasCameraStateMonitor(/* exists= */ true); + }); + } + + @Test + @EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM) + public void testCameraStateManager_startedWhenCameraCompatFreeformExists() { + runTestScenario((robot) -> { + robot.allowEnterDesktopMode(true); + robot.activity().createActivityWithComponentInNewTaskAndDisplay(); + robot.checkTopActivityHasCameraCompatFreeformPolicy(/* exists= */ true); + robot.checkTopActivityHasCameraStateMonitor(/* exists= */ true); + robot.checkTopActivityCameraStateMonitorIsRunning(); + }); + } + + @Test + public void testCameraStateManager_existsWhenDisplayRotationCompatPolicyExists() { + runTestScenario((robot) -> { + robot.conf().enableCameraCompatTreatmentAtBuildTime(/* enabled= */ true); + robot.activity().createActivityWithComponentInNewTaskAndDisplay(); + robot.checkTopActivityHasDisplayRotationCompatPolicy(/* exists= */ true); + robot.checkTopActivityHasCameraStateMonitor(/* exists= */ true); + }); + } + + @Test + public void testCameraStateManager_startedWhenDisplayRotationCompatPolicyExists() { + runTestScenario((robot) -> { + robot.conf().enableCameraCompatTreatmentAtBuildTime(/* enabled= */ true); + robot.activity().createActivityWithComponentInNewTaskAndDisplay(); + robot.checkTopActivityHasDisplayRotationCompatPolicy(/* exists= */ true); + robot.checkTopActivityHasCameraStateMonitor(/* exists= */ true); + robot.checkTopActivityCameraStateMonitorIsRunning(); + }); + } + + @Test + @DisableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM) + public void testCameraStateManager_doesNotExistWhenNoPolicyExists() { + runTestScenario((robot) -> { + robot.conf().enableCameraCompatTreatmentAtBuildTime(/* enabled= */ false); + robot.activity().createActivityWithComponentInNewTaskAndDisplay(); + robot.checkTopActivityHasDisplayRotationCompatPolicy(/* exists= */ false); + robot.checkTopActivityHasCameraCompatFreeformPolicy(/* exists= */ false); + robot.checkTopActivityHasCameraStateMonitor(/* exists= */ false); }); } /** * Runs a test scenario providing a Robot. */ - void runTestScenario(@NonNull Consumer<DisplayRotationPolicyRobotTest> consumer) { + void runTestScenario(@NonNull Consumer<AppCompatCameraPolicyRobotTest> consumer) { spyOn(mWm.mAppCompatConfiguration); - final DisplayRotationPolicyRobotTest robot = - new DisplayRotationPolicyRobotTest(mWm, mAtm, mSupervisor); + final AppCompatCameraPolicyRobotTest robot = + new AppCompatCameraPolicyRobotTest(mWm, mAtm, mSupervisor); consumer.accept(robot); } @@ -142,9 +220,8 @@ public class AppCompatCameraPolicyTest extends WindowTestsBase { }); } - private static class DisplayRotationPolicyRobotTest extends AppCompatRobotBase { - - DisplayRotationPolicyRobotTest(@NonNull WindowManagerService wm, + private static class AppCompatCameraPolicyRobotTest extends AppCompatRobotBase { + AppCompatCameraPolicyRobotTest(@NonNull WindowManagerService wm, @NonNull ActivityTaskManagerService atm, @NonNull ActivityTaskSupervisor supervisor) { super(wm, atm, supervisor); @@ -157,17 +234,37 @@ public class AppCompatCameraPolicyTest extends WindowTestsBase { } void checkTopActivityHasDisplayRotationCompatPolicy(boolean exists) { - Assert.assertEquals(exists, activity().top().mDisplayContent - .mAppCompatCameraPolicy.hasDisplayRotationCompatPolicy()); + assertEquals(exists, activity().top().mDisplayContent.mAppCompatCameraPolicy + .hasDisplayRotationCompatPolicy()); } void checkTopActivityHasCameraCompatFreeformPolicy(boolean exists) { - Assert.assertEquals(exists, activity().top().mDisplayContent - .mAppCompatCameraPolicy.hasCameraCompatFreeformPolicy()); + assertEquals(exists, activity().top().mDisplayContent.mAppCompatCameraPolicy + .hasCameraCompatFreeformPolicy()); + } + + void checkTopActivityHasCameraStateMonitor(boolean exists) { + assertEquals(exists, activity().top().mDisplayContent.mAppCompatCameraPolicy + .hasCameraStateMonitor()); + } + + void checkTopActivityDisplayRotationCompatPolicyIsRunning() { + assertTrue(activity().top().mDisplayContent.mAppCompatCameraPolicy + .mDisplayRotationCompatPolicy.isRunning()); + } + + void checkTopActivityCameraCompatFreeformPolicyIsRunning() { + assertTrue(activity().top().mDisplayContent.mAppCompatCameraPolicy + .mCameraCompatFreeformPolicy.isRunning()); + } + + void checkTopActivityCameraStateMonitorIsRunning() { + assertTrue(activity().top().mDisplayContent.mAppCompatCameraPolicy + .mCameraStateMonitor.isRunning()); } void checkIsCameraCompatTreatmentActiveForTopActivity(boolean active) { - Assert.assertEquals(getTopAppCompatCameraPolicy() + assertEquals(getTopAppCompatCameraPolicy() .isTreatmentEnabledForActivity(activity().top()), active); } diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatFocusOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatFocusOverridesTest.java index 27c5e4ebb397..d8f845389727 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppCompatFocusOverridesTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatFocusOverridesTest.java @@ -19,8 +19,6 @@ package com.android.server.wm; import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS; import static android.view.WindowManager.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; - import android.compat.testing.PlatformCompatChangeRule; import android.platform.test.annotations.Presubmit; @@ -177,7 +175,6 @@ public class AppCompatFocusOverridesTest extends WindowTestsBase { * Runs a test scenario providing a Robot. */ void runTestScenario(@NonNull Consumer<FocusOverridesRobotTest> consumer) { - spyOn(mWm.mAppCompatConfiguration); final FocusOverridesRobotTest robot = new FocusOverridesRobotTest(mWm, mAtm, mSupervisor); consumer.accept(robot); } diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java index f6d0744a10c4..9057b6cb99ea 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java @@ -514,7 +514,6 @@ public class AppCompatOrientationPolicyTest extends WindowTestsBase { */ void runTestScenario(boolean withActivity, @NonNull Consumer<OrientationPolicyRobotTest> consumer) { - spyOn(mWm.mAppCompatConfiguration); final OrientationPolicyRobotTest robot = new OrientationPolicyRobotTest(mWm, mAtm, mSupervisor, withActivity); consumer.accept(robot); diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityOverridesTest.java index 5ff8f0200fa3..1edbcd527bf4 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityOverridesTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityOverridesTest.java @@ -164,7 +164,6 @@ public class AppCompatReachabilityOverridesTest extends WindowTestsBase { * Runs a test scenario providing a Robot. */ void runTestScenario(@NonNull Consumer<ReachabilityOverridesRobotTest> consumer) { - spyOn(mWm.mAppCompatConfiguration); final ReachabilityOverridesRobotTest robot = new ReachabilityOverridesRobotTest(mWm, mAtm, mSupervisor); consumer.accept(robot); diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityPolicyTest.java index 96734b389947..ddc4de9cfd8a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityPolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityPolicyTest.java @@ -228,7 +228,6 @@ public class AppCompatReachabilityPolicyTest extends WindowTestsBase { * Runs a test scenario providing a Robot. */ void runTestScenario(@NonNull Consumer<ReachabilityPolicyRobotTest> consumer) { - spyOn(mWm.mAppCompatConfiguration); final ReachabilityPolicyRobotTest robot = new ReachabilityPolicyRobotTest(mWm, mAtm, mSupervisor); consumer.accept(robot); diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatResizeOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatResizeOverridesTest.java index cade213ca3d7..b8d554b405d1 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppCompatResizeOverridesTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatResizeOverridesTest.java @@ -20,8 +20,6 @@ import static android.content.pm.ActivityInfo.FORCE_NON_RESIZE_APP; import static android.content.pm.ActivityInfo.FORCE_RESIZE_APP; import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; - import android.compat.testing.PlatformCompatChangeRule; import android.platform.test.annotations.Presubmit; @@ -171,7 +169,6 @@ public class AppCompatResizeOverridesTest extends WindowTestsBase { * Runs a test scenario providing a Robot. */ void runTestScenario(@NonNull Consumer<ResizeOverridesRobotTest> consumer) { - spyOn(mWm.mAppCompatConfiguration); final ResizeOverridesRobotTest robot = new ResizeOverridesRobotTest(mWm, mAtm, mSupervisor); consumer.accept(robot); } diff --git a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java index f2592d2361f5..5a3ae769ef62 100644 --- a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java @@ -276,7 +276,6 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { .setTask(mTask) .build(); - spyOn(mActivity.mLetterboxUiController); spyOn(mActivity.mAppCompatController.getAppCompatCameraOverrides()); spyOn(mActivity.info); diff --git a/services/tests/wmtests/src/com/android/server/wm/CameraStateMonitorTests.java b/services/tests/wmtests/src/com/android/server/wm/CameraStateMonitorTests.java index d2232729f271..ad80f82c8ea8 100644 --- a/services/tests/wmtests/src/com/android/server/wm/CameraStateMonitorTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/CameraStateMonitorTests.java @@ -189,7 +189,6 @@ public final class CameraStateMonitorTests extends WindowTestsBase { .build(); spyOn(mActivity.mAtmService.getLifecycleManager()); - spyOn(mActivity.mLetterboxUiController); doReturn(mActivity).when(mDisplayContent).topRunningActivity(anyBoolean()); } diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java index 2dea6ba2b8aa..8cf593fd21db 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java @@ -597,7 +597,6 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase { .build(); spyOn(mActivity.mAtmService.getLifecycleManager()); - spyOn(mActivity.mLetterboxUiController); spyOn(mActivity.mAppCompatController.getAppCompatCameraOverrides()); doReturn(mActivity).when(mDisplayContent).topRunningActivity(anyBoolean()); diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java index cf321ed89c89..894752218fcd 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java @@ -77,7 +77,6 @@ public class LetterboxUiControllerTest extends WindowTestsBase { private ActivityRecord mActivity; private Task mTask; private DisplayContent mDisplayContent; - private LetterboxUiController mController; private AppCompatConfiguration mAppCompatConfiguration; private final Rect mLetterboxedPortraitTaskBounds = new Rect(); @@ -87,8 +86,6 @@ public class LetterboxUiControllerTest extends WindowTestsBase { mAppCompatConfiguration = mWm.mAppCompatConfiguration; spyOn(mAppCompatConfiguration); - - mController = new LetterboxUiController(mWm, mActivity); } @Test @@ -276,10 +273,13 @@ public class LetterboxUiControllerTest extends WindowTestsBase { final Resources resources = mWm.mContext.getResources(); final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(); + final AppCompatAspectRatioPolicy aspectRatioPolicy = mActivity.mAppCompatController + .getAppCompatAspectRatioPolicy(); + mainWindow.mInvGlobalScale = 1f; spyOn(resources); spyOn(mActivity); - spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()); + spyOn(aspectRatioPolicy); if (taskbar != null) { taskbar.setVisible(true); @@ -288,8 +288,8 @@ public class LetterboxUiControllerTest extends WindowTestsBase { doReturn(mLetterboxedPortraitTaskBounds).when(mActivity).getBounds(); doReturn(false).when(mActivity).isInLetterboxAnimation(); doReturn(true).when(mActivity).isVisible(); - doReturn(true).when(mActivity.mAppCompatController - .getAppCompatAspectRatioPolicy()).isLetterboxedForFixedOrientationAndAspectRatio(); + doReturn(true).when(aspectRatioPolicy) + .isLetterboxedForFixedOrientationAndAspectRatio(); doReturn(insets).when(mainWindow).getInsetsState(); doReturn(attrs).when(mainWindow).getAttrs(); doReturn(true).when(mainWindow).isDrawn(); @@ -300,9 +300,6 @@ public class LetterboxUiControllerTest extends WindowTestsBase { doReturn(TASKBAR_EXPANDED_HEIGHT).when(resources).getDimensionPixelSize( R.dimen.taskbar_frame_height); - // Need to reinitialise due to the change in resources getDimensionPixelSize output. - mController = new LetterboxUiController(mWm, mActivity); - return mainWindow; } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTracingLegacyTest.java b/services/tests/wmtests/src/com/android/server/wm/WindowTracingLegacyTest.java index 48a8d5502c64..a1d35a7d447c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTracingLegacyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTracingLegacyTest.java @@ -125,7 +125,7 @@ public class WindowTracingLegacyTest { public void trace_dumpsWindowManagerState_whenTracing() throws Exception { mWindowTracing.startTrace(mock(PrintWriter.class)); mWindowTracing.logState("where"); - verify(mWmMock, times(2)).dumpDebugLocked(any(), eq(WindowTraceLogLevel.TRIM)); + verify(mWmMock, times(2)).dumpDebugLocked(any(), eq(WindowTracingLogLevel.TRIM)); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTracingPerfettoTest.java b/services/tests/wmtests/src/com/android/server/wm/WindowTracingPerfettoTest.java new file mode 100644 index 000000000000..1d567b1169bd --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTracingPerfettoTest.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyZeroInteractions; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; + +import static java.io.File.createTempFile; +import static java.nio.file.Files.createTempDirectory; + +import android.platform.test.annotations.Presubmit; +import android.tools.ScenarioBuilder; +import android.tools.traces.io.ResultWriter; +import android.tools.traces.monitors.PerfettoTraceMonitor; +import android.view.Choreographer; + +import androidx.test.filters.SmallTest; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import perfetto.protos.PerfettoConfig.WindowManagerConfig.LogFrequency; + +/** + * Test class for {@link WindowTracingPerfetto}. + */ +@SmallTest +@Presubmit +public class WindowTracingPerfettoTest { + @Mock + private WindowManagerService mWmMock; + @Mock + private Choreographer mChoreographer; + private WindowTracing mWindowTracing; + private PerfettoTraceMonitor mTraceMonitor; + private ResultWriter mWriter; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mWindowTracing = new WindowTracingPerfetto(mWmMock, mChoreographer, + new WindowManagerGlobalLock()); + + mWriter = new ResultWriter() + .forScenario(new ScenarioBuilder() + .forClass(createTempFile("temp", "").getName()).build()) + .withOutputDir(createTempDirectory("temp").toFile()) + .setRunComplete(); + } + + @After + public void tearDown() throws Exception { + stopTracing(); + } + + @Test + public void isEnabled_returnsFalseByDefault() { + assertFalse(mWindowTracing.isEnabled()); + } + + @Test + public void isEnabled_returnsTrueAfterStartThenFalseAfterStop() { + startTracing(false); + assertTrue(mWindowTracing.isEnabled()); + + stopTracing(); + assertFalse(mWindowTracing.isEnabled()); + } + + @Test + public void trace_ignoresLogStateCalls_ifTracingIsDisabled() { + mWindowTracing.logState("where"); + verifyZeroInteractions(mWmMock); + } + + @Test + public void trace_writesInitialStateSnapshot_whenTracingStarts() throws Exception { + startTracing(false); + verify(mWmMock, times(1)).dumpDebugLocked(any(), eq(WindowTracingLogLevel.ALL)); + } + + @Test + public void trace_writesStateSnapshot_onLogStateCall() throws Exception { + startTracing(false); + mWindowTracing.logState("where"); + verify(mWmMock, times(2)).dumpDebugLocked(any(), eq(WindowTracingLogLevel.ALL)); + } + + @Test + public void dump_writesOneSingleStateSnapshot() throws Exception { + startTracing(true); + mWindowTracing.logState("where"); + verify(mWmMock, times(1)).dumpDebugLocked(any(), eq(WindowTracingLogLevel.ALL)); + } + + private void startTracing(boolean isDump) { + if (isDump) { + mTraceMonitor = PerfettoTraceMonitor + .newBuilder() + .enableWindowManagerDump() + .build(); + } else { + mTraceMonitor = PerfettoTraceMonitor + .newBuilder() + .enableWindowManagerTrace(LogFrequency.LOG_FREQUENCY_TRANSACTION) + .build(); + } + mTraceMonitor.start(); + } + + private void stopTracing() { + if (mTraceMonitor == null || !mTraceMonitor.isEnabled()) { + return; + } + mTraceMonitor.stop(mWriter); + } +} diff --git a/telephony/java/android/telephony/CarrierRestrictionRules.java b/telephony/java/android/telephony/CarrierRestrictionRules.java index 65e8e13036a6..ddf5ef21face 100644 --- a/telephony/java/android/telephony/CarrierRestrictionRules.java +++ b/telephony/java/android/telephony/CarrierRestrictionRules.java @@ -463,8 +463,9 @@ public final class CarrierRestrictionRules implements Parcelable { public String toString() { return "CarrierRestrictionRules(allowed:" + mAllowedCarriers + ", excluded:" + mExcludedCarriers + ", default:" + mCarrierRestrictionDefault - + ", MultiSim policy:" + mMultiSimPolicy + getCarrierInfoList() + - " mIsCarrierLockInfoSupported = " + mUseCarrierLockInfo + ")"; + + ", MultiSim policy:" + mMultiSimPolicy + getCarrierInfoList() + + ", mIsCarrierLockInfoSupported = " + mUseCarrierLockInfo + + getCarrierRestrictionStatusToLog() + ")"; } private String getCarrierInfoList() { @@ -476,6 +477,13 @@ public final class CarrierRestrictionRules implements Parcelable { } } + private String getCarrierRestrictionStatusToLog() { + if(android.os.Build.isDebuggable()) { + return ", CarrierRestrictionStatus = " + mCarrierRestrictionStatus; + } + return ""; + } + /** * Builder for a {@link CarrierRestrictionRules}. */ diff --git a/telephony/java/android/telephony/satellite/ISatelliteProvisionStateCallback.aidl b/telephony/java/android/telephony/satellite/ISatelliteProvisionStateCallback.aidl index f981fb1d67c7..5f0d986912a1 100644 --- a/telephony/java/android/telephony/satellite/ISatelliteProvisionStateCallback.aidl +++ b/telephony/java/android/telephony/satellite/ISatelliteProvisionStateCallback.aidl @@ -16,6 +16,8 @@ package android.telephony.satellite; +import android.telephony.satellite.SatelliteSubscriberProvisionStatus; + /** * Interface for satellite provision state callback. * @hide @@ -27,4 +29,14 @@ oneway interface ISatelliteProvisionStateCallback { * @param provisioned True means the service is provisioned and false means it is not. */ void onSatelliteProvisionStateChanged(in boolean provisioned); + + /** + * Called when the provisioning state of one or more SatelliteSubscriberInfos changes. + * + * @param satelliteSubscriberProvisionStatus The List contains the latest provisioning states of + * the SatelliteSubscriberInfos. + * @hide + */ + void onSatelliteSubscriptionProvisionStateChanged(in List<SatelliteSubscriberProvisionStatus> + satelliteSubscriberProvisionStatus); } diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java index e657d7faad15..6ef953c505cb 100644 --- a/telephony/java/android/telephony/satellite/SatelliteManager.java +++ b/telephony/java/android/telephony/satellite/SatelliteManager.java @@ -234,7 +234,7 @@ public final class SatelliteManager { /** * Bundle key to get the response from - * {@link #requestProvisionSubscriberIds(Executor, OutcomeReceiver)}. + * {@link #requestSatelliteSubscriberProvisionStatus(Executor, OutcomeReceiver)}. * @hide */ public static final String KEY_REQUEST_PROVISION_SUBSCRIBER_ID_TOKEN = @@ -242,13 +242,6 @@ public final class SatelliteManager { /** * Bundle key to get the response from - * {@link #requestIsProvisioned(String, Executor, OutcomeReceiver)}. - * @hide - */ - public static final String KEY_IS_SATELLITE_PROVISIONED = "request_is_satellite_provisioned"; - - /** - * Bundle key to get the response from * {@link #provisionSatellite(List, Executor, OutcomeReceiver)}. * @hide */ @@ -1404,6 +1397,16 @@ public final class SatelliteManager { () -> callback.onSatelliteProvisionStateChanged( provisioned))); } + + @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN) + @Override + public void onSatelliteSubscriptionProvisionStateChanged( + @NonNull List<SatelliteSubscriberProvisionStatus> + satelliteSubscriberProvisionStatus) { + executor.execute(() -> Binder.withCleanCallingIdentity(() -> + callback.onSatelliteSubscriptionProvisionStateChanged( + satelliteSubscriberProvisionStatus))); + } }; sSatelliteProvisionStateCallbackMap.put(callback, internalCallback); return telephony.registerForSatelliteProvisionStateChanged( @@ -2641,8 +2644,10 @@ public final class SatelliteManager { */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN) - public void requestProvisionSubscriberIds(@NonNull @CallbackExecutor Executor executor, - @NonNull OutcomeReceiver<List<SatelliteSubscriberInfo>, SatelliteException> callback) { + public void requestSatelliteSubscriberProvisionStatus( + @NonNull @CallbackExecutor Executor executor, + @NonNull OutcomeReceiver<List<SatelliteSubscriberProvisionStatus>, + SatelliteException> callback) { Objects.requireNonNull(executor); Objects.requireNonNull(callback); @@ -2654,10 +2659,10 @@ public final class SatelliteManager { protected void onReceiveResult(int resultCode, Bundle resultData) { if (resultCode == SATELLITE_RESULT_SUCCESS) { if (resultData.containsKey(KEY_REQUEST_PROVISION_SUBSCRIBER_ID_TOKEN)) { - List<SatelliteSubscriberInfo> list = + List<SatelliteSubscriberProvisionStatus> list = resultData.getParcelableArrayList( KEY_REQUEST_PROVISION_SUBSCRIBER_ID_TOKEN, - SatelliteSubscriberInfo.class); + SatelliteSubscriberProvisionStatus.class); executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onResult(list))); } else { @@ -2672,70 +2677,14 @@ public final class SatelliteManager { } } }; - telephony.requestProvisionSubscriberIds(receiver); - } else { - loge("requestProvisionSubscriberIds() invalid telephony"); - executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError( - new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE)))); - } - } catch (RemoteException ex) { - loge("requestProvisionSubscriberIds() RemoteException: " + ex); - executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError( - new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE)))); - } - } - - /** - * Request to get provisioned status for given a satellite subscriber id. - * - * @param satelliteSubscriberId Satellite subscriber id requiring provisioned status check. - * @param executor The executor on which the callback will be called. - * @param callback callback. - * - * @throws SecurityException if the caller doesn't have required permission. - * @hide - */ - @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN) - public void requestIsProvisioned(@NonNull String satelliteSubscriberId, - @NonNull @CallbackExecutor Executor executor, - @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) { - Objects.requireNonNull(satelliteSubscriberId); - Objects.requireNonNull(executor); - Objects.requireNonNull(callback); - - try { - ITelephony telephony = getITelephony(); - if (telephony != null) { - ResultReceiver receiver = new ResultReceiver(null) { - @Override - protected void onReceiveResult(int resultCode, Bundle resultData) { - if (resultCode == SATELLITE_RESULT_SUCCESS) { - if (resultData.containsKey(KEY_IS_SATELLITE_PROVISIONED)) { - boolean isIsProvisioned = - resultData.getBoolean(KEY_IS_SATELLITE_PROVISIONED); - executor.execute(() -> Binder.withCleanCallingIdentity(() -> - callback.onResult(isIsProvisioned))); - } else { - loge("KEY_IS_SATELLITE_PROVISIONED does not exist."); - executor.execute(() -> Binder.withCleanCallingIdentity(() -> - callback.onError(new SatelliteException( - SATELLITE_RESULT_REQUEST_FAILED)))); - } - } else { - executor.execute(() -> Binder.withCleanCallingIdentity(() -> - callback.onError(new SatelliteException(resultCode)))); - } - } - }; - telephony.requestIsProvisioned(satelliteSubscriberId, receiver); + telephony.requestSatelliteSubscriberProvisionStatus(receiver); } else { - loge("requestIsSatelliteProvisioned() invalid telephony"); + loge("requestSatelliteSubscriberProvisionStatus() invalid telephony"); executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError( new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE)))); } } catch (RemoteException ex) { - loge("requestIsSatelliteProvisioned() RemoteException: " + ex); + loge("requestSatelliteSubscriberProvisionStatus() RemoteException: " + ex); executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError( new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE)))); } diff --git a/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java b/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java index a12952be7620..e8ae0f56ee9e 100644 --- a/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java +++ b/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java @@ -17,10 +17,13 @@ package android.telephony.satellite; import android.annotation.FlaggedApi; +import android.annotation.NonNull; import android.annotation.SystemApi; import com.android.internal.telephony.flags.Flags; +import java.util.List; + /** * A callback class for monitoring satellite provision state change events. * @@ -39,4 +42,16 @@ public interface SatelliteProvisionStateCallback { */ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) void onSatelliteProvisionStateChanged(boolean provisioned); + + /** + * Called when the provisioning state of one or more SatelliteSubscriberInfos changes. + * + * @param satelliteSubscriberProvisionStatus The List contains the latest provisioning states + * of the SatelliteSubscriberInfos. + * @hide + */ + @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN) + default void onSatelliteSubscriptionProvisionStateChanged( + @NonNull List<SatelliteSubscriberProvisionStatus> + satelliteSubscriberProvisionStatus) {}; } diff --git a/telephony/java/android/telephony/satellite/SatelliteSubscriberInfo.java b/telephony/java/android/telephony/satellite/SatelliteSubscriberInfo.java index f26219bd0885..50ed6270ccfc 100644 --- a/telephony/java/android/telephony/satellite/SatelliteSubscriberInfo.java +++ b/telephony/java/android/telephony/satellite/SatelliteSubscriberInfo.java @@ -17,12 +17,15 @@ package android.telephony.satellite; import android.annotation.FlaggedApi; +import android.annotation.IntDef; import android.annotation.NonNull; import android.os.Parcel; import android.os.Parcelable; import com.android.internal.telephony.flags.Flags; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Objects; /** @@ -39,25 +42,115 @@ public final class SatelliteSubscriberInfo implements Parcelable { /** provision subscriberId */ @NonNull private String mSubscriberId; - /** carrier id */ private int mCarrierId; /** apn */ private String mNiddApn; + private int mSubId; + + /** SubscriberId format is the ICCID. */ + @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN) + public static final int ICCID = 0; + /** SubscriberId format is the 6 digit of IMSI + MSISDN. */ + @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN) + public static final int IMSI_MSISDN = 1; + + /** Type of subscriber id */ + @SubscriberIdType private int mSubscriberIdType; + /** @hide */ + @IntDef(prefix = "SubscriberId_Type_", value = { + ICCID, + IMSI_MSISDN + }) + @Retention(RetentionPolicy.SOURCE) + public @interface SubscriberIdType {} + + private SatelliteSubscriberInfo(Parcel in) { + readFromParcel(in); + } + + public SatelliteSubscriberInfo(@NonNull Builder builder) { + this.mSubscriberId = builder.mSubscriberId; + this.mCarrierId = builder.mCarrierId; + this.mNiddApn = builder.mNiddApn; + this.mSubId = builder.mSubId; + this.mSubscriberIdType = builder.mSubscriberIdType; + } /** + * Builder class for constructing SatelliteSubscriberInfo objects + * * @hide */ - public SatelliteSubscriberInfo(@NonNull String subscriberId, @NonNull int carrierId, - @NonNull String niddApn) { - this.mCarrierId = carrierId; - this.mSubscriberId = subscriberId; - this.mNiddApn = niddApn; - } + @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN) + public static class Builder { + @NonNull private String mSubscriberId; + private int mCarrierId; + @NonNull + private String mNiddApn; + private int mSubId; + @SubscriberIdType + private int mSubscriberIdType; - private SatelliteSubscriberInfo(Parcel in) { - readFromParcel(in); + /** + * Set the SubscriberId and returns the Builder class. + * + * @hide + */ + public Builder setSubscriberId(String subscriberId) { + mSubscriberId = subscriberId; + return this; + } + + /** + * Set the CarrierId and returns the Builder class. + * @hide + */ + @NonNull + public Builder setCarrierId(int carrierId) { + mCarrierId = carrierId; + return this; + } + + /** + * Set the niddApn and returns the Builder class. + * @hide + */ + @NonNull + public Builder setNiddApn(String niddApn) { + mNiddApn = niddApn; + return this; + } + + /** + * Set the subId and returns the Builder class. + * @hide + */ + @NonNull + public Builder setSubId(int subId) { + mSubId = subId; + return this; + } + + /** + * Set the SubscriberIdType and returns the Builder class. + * @hide + */ + @NonNull + public Builder setSubscriberIdType(@SubscriberIdType int subscriberIdType) { + mSubscriberIdType = subscriberIdType; + return this; + } + + /** + * Returns SatelliteSubscriberInfo object. + * @hide + */ + @NonNull + public SatelliteSubscriberInfo build() { + return new SatelliteSubscriberInfo(this); + } } /** @@ -69,6 +162,8 @@ public final class SatelliteSubscriberInfo implements Parcelable { out.writeString(mSubscriberId); out.writeInt(mCarrierId); out.writeString(mNiddApn); + out.writeInt(mSubId); + out.writeInt(mSubscriberIdType); } @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN) @@ -121,6 +216,24 @@ public final class SatelliteSubscriberInfo implements Parcelable { return mNiddApn; } + /** + * @return subId. + * @hide + */ + @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN) + public int getSubId() { + return mSubId; + } + + /** + * @return subscriberIdType. + * @hide + */ + @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN) + public @SubscriberIdType int getSubscriberIdType() { + return mSubscriberIdType; + } + @NonNull @Override public String toString() { @@ -136,26 +249,37 @@ public final class SatelliteSubscriberInfo implements Parcelable { sb.append("NiddApn:"); sb.append(mNiddApn); + sb.append(","); + + sb.append("SubId:"); + sb.append(mSubId); + sb.append(","); + + sb.append("SubscriberIdType:"); + sb.append(mSubscriberIdType); return sb.toString(); } @Override public int hashCode() { - return Objects.hash(mSubscriberId, mCarrierId, mNiddApn); + return Objects.hash(mSubscriberId, mCarrierId, mNiddApn, mSubId, mSubscriberIdType); } @Override public boolean equals(Object o) { if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (!(o instanceof SatelliteSubscriberProvisionStatus)) return false; SatelliteSubscriberInfo that = (SatelliteSubscriberInfo) o; - return mSubscriberId.equals(that.mSubscriberId) && mCarrierId - == that.mCarrierId && mNiddApn.equals(that.mNiddApn); + return Objects.equals(mSubscriberId, that.mSubscriberId) && mCarrierId == that.mCarrierId + && Objects.equals(mNiddApn, that.mNiddApn) && mSubId == that.mSubId + && mSubscriberIdType == that.mSubscriberIdType; } private void readFromParcel(Parcel in) { mSubscriberId = in.readString(); mCarrierId = in.readInt(); mNiddApn = in.readString(); + mSubId = in.readInt(); + mSubscriberIdType = in.readInt(); } } diff --git a/telephony/java/android/telephony/satellite/SatelliteSubscriberProvisionStatus.aidl b/telephony/java/android/telephony/satellite/SatelliteSubscriberProvisionStatus.aidl new file mode 100644 index 000000000000..80de77905f80 --- /dev/null +++ b/telephony/java/android/telephony/satellite/SatelliteSubscriberProvisionStatus.aidl @@ -0,0 +1,19 @@ +/* + * Copyright 2024, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony.satellite; + +parcelable SatelliteSubscriberProvisionStatus;
\ No newline at end of file diff --git a/telephony/java/android/telephony/satellite/SatelliteSubscriberProvisionStatus.java b/telephony/java/android/telephony/satellite/SatelliteSubscriberProvisionStatus.java new file mode 100644 index 000000000000..e3d619ea0fc8 --- /dev/null +++ b/telephony/java/android/telephony/satellite/SatelliteSubscriberProvisionStatus.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony.satellite; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.telephony.flags.Flags; + +import java.util.Objects; + +/** + * Represents the provisioning state of SatelliteSubscriberInfo. + * + * @hide + */ +@FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN) +public class SatelliteSubscriberProvisionStatus implements Parcelable { + private SatelliteSubscriberInfo mSubscriberInfo; + /** {@code true} mean the satellite subscriber is provisioned, {@code false} otherwise. */ + private boolean mProvisionStatus; + + public SatelliteSubscriberProvisionStatus(@NonNull Builder builder) { + mSubscriberInfo = builder.mSubscriberInfo; + mProvisionStatus = builder.mProvisionStatus; + } + + /** + * Builder class for constructing SatelliteSubscriberProvisionStatus objects + * + * @hide + */ + @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN) + public static class Builder { + private SatelliteSubscriberInfo mSubscriberInfo; + private boolean mProvisionStatus; + + /** + * Set the SatelliteSubscriberInfo and returns the Builder class. + * @hide + */ + public Builder setSatelliteSubscriberInfo(SatelliteSubscriberInfo satelliteSubscriberInfo) { + mSubscriberInfo = satelliteSubscriberInfo; + return this; + } + + /** + * Set the SatelliteSubscriberInfo's provisionStatus and returns the Builder class. + * @hide + */ + @NonNull + public Builder setProvisionStatus(boolean provisionStatus) { + mProvisionStatus = provisionStatus; + return this; + } + + /** + * Returns SatelliteSubscriberProvisionStatus object. + * @hide + */ + @NonNull + public SatelliteSubscriberProvisionStatus build() { + return new SatelliteSubscriberProvisionStatus(this); + } + } + + private SatelliteSubscriberProvisionStatus(Parcel in) { + readFromParcel(in); + } + + /** + * @hide + */ + @Override + @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN) + public void writeToParcel(@NonNull Parcel out, int flags) { + mSubscriberInfo.writeToParcel(out, flags); + out.writeBoolean(mProvisionStatus); + } + + @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN) + public static final @android.annotation.NonNull Creator<SatelliteSubscriberProvisionStatus> + CREATOR = + new Creator<SatelliteSubscriberProvisionStatus>() { + @Override + public SatelliteSubscriberProvisionStatus createFromParcel(Parcel in) { + return new SatelliteSubscriberProvisionStatus(in); + } + + @Override + public SatelliteSubscriberProvisionStatus[] newArray(int size) { + return new SatelliteSubscriberProvisionStatus[size]; + } + }; + + /** + * @hide + */ + @Override + @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN) + public int describeContents() { + return 0; + } + + /** + * SatelliteSubscriberInfo that has a provisioning state. + * @return SatelliteSubscriberInfo. + * @hide + */ + @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN) + public @NonNull SatelliteSubscriberInfo getSatelliteSubscriberInfo() { + return mSubscriberInfo; + } + + /** + * SatelliteSubscriberInfo's provisioning state. + * @return {@code true} means provisioning. {@code false} means deprovisioning. + * @hide + */ + @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN) + public @NonNull boolean getProvisionStatus() { + return mProvisionStatus; + } + + @NonNull + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + + sb.append("SatelliteSubscriberInfo:"); + sb.append(mSubscriberInfo); + sb.append(","); + + sb.append("ProvisionStatus:"); + sb.append(mProvisionStatus); + return sb.toString(); + } + + @Override + public int hashCode() { + return Objects.hash(mSubscriberInfo, mProvisionStatus); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof SatelliteSubscriberProvisionStatus)) return false; + SatelliteSubscriberProvisionStatus that = (SatelliteSubscriberProvisionStatus) o; + return Objects.equals(mSubscriberInfo, that.mSubscriberInfo) + && mProvisionStatus == that.mProvisionStatus; + } + + private void readFromParcel(Parcel in) { + mSubscriberInfo = in.readParcelable(SatelliteSubscriberInfo.class.getClassLoader(), + SatelliteSubscriberInfo.class); + mProvisionStatus = in.readBoolean(); + } +} diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 89197032dcef..0c5f30f96360 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -3413,19 +3413,7 @@ interface ITelephony { */ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(" + "android.Manifest.permission.SATELLITE_COMMUNICATION)") - void requestProvisionSubscriberIds(in ResultReceiver result); - - /** - * Request to get provisioned status for given a satellite subscriber id. - * - * @param satelliteSubscriberId Satellite subscriber id requiring provisioned status check. - * @param result The result receiver, which returns the provisioned status of the token if the - * request is successful or an error code if the request failed. - * @hide - */ - @JavaPassthrough(annotation="@android.annotation.RequiresPermission(" - + "android.Manifest.permission.SATELLITE_COMMUNICATION)") - void requestIsProvisioned(in String satelliteSubscriberId, in ResultReceiver result); + void requestSatelliteSubscriberProvisionStatus(in ResultReceiver result); /** * Deliver the list of provisioned satellite subscriber infos. diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt index 638d594b0a48..eb63e4985a9f 100644 --- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt +++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt @@ -28,6 +28,7 @@ import com.android.server.wm.flicker.BaseTest import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper import com.android.server.wm.flicker.navBarLayerIsVisibleAtStartAndEnd import com.android.server.wm.flicker.statusBarLayerIsVisibleAtStartAndEnd +import com.android.server.wm.flicker.taskBarLayerIsVisibleAtStartAndEnd import org.junit.Assume import org.junit.FixMethodOrder import org.junit.Ignore @@ -114,28 +115,28 @@ class ShowImeWhileEnteringOverviewTest(flicker: LegacyFlickerTest) : BaseTest(fl /** * In the legacy transitions, the nav bar is not marked as invisible. In the new transitions - * this is fixed and the nav bar shows as invisible + * this is fixed and the status bar shows as invisible */ @Presubmit @Test fun statusBarLayerIsInvisibleInLandscapePhone() { Assume.assumeTrue(flicker.scenario.isLandscapeOrSeascapeAtStart) Assume.assumeTrue(flicker.scenario.isGesturalNavigation) - Assume.assumeFalse(usesTaskbar) + Assume.assumeFalse(flicker.scenario.isTablet) flicker.assertLayersStart { this.isVisible(ComponentNameMatcher.STATUS_BAR) } flicker.assertLayersEnd { this.isInvisible(ComponentNameMatcher.STATUS_BAR) } } /** * In the legacy transitions, the nav bar is not marked as invisible. In the new transitions - * this is fixed and the nav bar shows as invisible + * this is fixed and the status bar shows as invisible */ @Presubmit @Test fun statusBarLayerIsInvisibleInLandscapeTablet() { Assume.assumeTrue(flicker.scenario.isLandscapeOrSeascapeAtStart) Assume.assumeTrue(flicker.scenario.isGesturalNavigation) - Assume.assumeTrue(usesTaskbar) + Assume.assumeTrue(flicker.scenario.isTablet) flicker.statusBarLayerIsVisibleAtStartAndEnd() } @@ -149,6 +150,10 @@ class ShowImeWhileEnteringOverviewTest(flicker: LegacyFlickerTest) : BaseTest(fl @Ignore("Visibility changes depending on orientation and navigation mode") override fun navBarLayerPositionAtStartAndEnd() {} + @Test + @Ignore("Visibility changes depending on orientation and navigation mode") + override fun taskBarLayerIsVisibleAtStartAndEnd() {} + /** {@inheritDoc} */ @Test @Ignore("Visibility changes depending on orientation and navigation mode") @@ -161,7 +166,10 @@ class ShowImeWhileEnteringOverviewTest(flicker: LegacyFlickerTest) : BaseTest(fl @Presubmit @Test - override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd() + fun taskBarLayerIsVisibleAtStartAndEndForTablets() { + Assume.assumeTrue(flicker.scenario.isTablet) + flicker.taskBarLayerIsVisibleAtStartAndEnd() + } @Presubmit @Test diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt index 70d762e02af5..851ce022bd81 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt @@ -137,8 +137,6 @@ constructor( /** * Checks that the [ComponentNameMatcher.TASK_BAR] window is visible at the start and end of the * transition - * - * Note: Large screen only */ @Presubmit @Test @@ -149,8 +147,6 @@ constructor( /** * Checks that the [ComponentNameMatcher.TASK_BAR] window is visible during the whole transition - * - * Note: Large screen only */ @Presubmit @Test diff --git a/tests/Input/src/android/hardware/input/KeyboardSystemShortcutListenerTest.kt b/tests/Input/src/android/hardware/input/KeyGestureEventListenerTest.kt index 24d7291bec87..14aac6637d4f 100644 --- a/tests/Input/src/android/hardware/input/KeyboardSystemShortcutListenerTest.kt +++ b/tests/Input/src/android/hardware/input/KeyGestureEventListenerTest.kt @@ -42,21 +42,22 @@ import kotlin.test.assertNull import kotlin.test.fail /** - * Tests for [InputManager.KeyboardSystemShortcutListener]. + * Tests for [InputManager.KeyGestureEventListener]. * * Build/Install/Run: - * atest InputTests:KeyboardSystemShortcutListenerTest + * atest InputTests:KeyGestureEventListenerTest */ @Presubmit @RunWith(MockitoJUnitRunner::class) -class KeyboardSystemShortcutListenerTest { +class KeyGestureEventListenerTest { companion object { const val DEVICE_ID = 1 - val HOME_SHORTCUT = KeyboardSystemShortcut( + val HOME_GESTURE_EVENT = KeyGestureEvent( + DEVICE_ID, intArrayOf(KeyEvent.KEYCODE_H), KeyEvent.META_META_ON or KeyEvent.META_META_LEFT_ON, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_HOME + KeyGestureEvent.KEY_GESTURE_TYPE_HOME ) } @@ -65,7 +66,7 @@ class KeyboardSystemShortcutListenerTest { private val testLooper = TestLooper() private val executor = HandlerExecutor(Handler(testLooper.looper)) - private var registeredListener: IKeyboardSystemShortcutListener? = null + private var registeredListener: IKeyGestureEventListener? = null private lateinit var context: Context private lateinit var inputManager: InputManager private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession @@ -81,28 +82,28 @@ class KeyboardSystemShortcutListenerTest { `when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE))) .thenReturn(inputManager) - // Handle keyboard system shortcut listener registration. + // Handle key gesture event listener registration. doAnswer { - val listener = it.getArgument(0) as IKeyboardSystemShortcutListener + val listener = it.getArgument(0) as IKeyGestureEventListener if (registeredListener != null && registeredListener!!.asBinder() != listener.asBinder()) { - // There can only be one registered keyboard system shortcut listener per process. + // There can only be one registered key gesture event listener per process. fail("Trying to register a new listener when one already exists") } registeredListener = listener null - }.`when`(iInputManagerMock).registerKeyboardSystemShortcutListener(any()) + }.`when`(iInputManagerMock).registerKeyGestureEventListener(any()) - // Handle keyboard system shortcut listener being unregistered. + // Handle key gesture event listener being unregistered. doAnswer { - val listener = it.getArgument(0) as IKeyboardSystemShortcutListener + val listener = it.getArgument(0) as IKeyGestureEventListener if (registeredListener == null || registeredListener!!.asBinder() != listener.asBinder()) { fail("Trying to unregister a listener that is not registered") } registeredListener = null null - }.`when`(iInputManagerMock).unregisterKeyboardSystemShortcutListener(any()) + }.`when`(iInputManagerMock).unregisterKeyGestureEventListener(any()) } @After @@ -112,29 +113,28 @@ class KeyboardSystemShortcutListenerTest { } } - private fun notifyKeyboardSystemShortcutTriggered(id: Int, shortcut: KeyboardSystemShortcut) { - registeredListener!!.onKeyboardSystemShortcutTriggered( - id, - shortcut.keycodes, - shortcut.modifierState, - shortcut.systemShortcut + private fun notifyKeyGestureEvent(event: KeyGestureEvent) { + registeredListener!!.onKeyGestureEvent( + event.deviceId, + event.keycodes, + event.modifierState, + event.keyGestureType ) } @Test - fun testListenerHasCorrectSystemShortcutNotified() { + fun testListenerHasCorrectGestureNotified() { var callbackCount = 0 - // Add a keyboard system shortcut listener - inputManager.registerKeyboardSystemShortcutListener(executor) { - deviceId: Int, systemShortcut: KeyboardSystemShortcut -> - assertEquals(DEVICE_ID, deviceId) - assertEquals(HOME_SHORTCUT, systemShortcut) + // Add a key gesture event listener + inputManager.registerKeyGestureEventListener(executor) { + event: KeyGestureEvent -> + assertEquals(HOME_GESTURE_EVENT, event) callbackCount++ } - // Notifying keyboard system shortcut triggered will notify the listener. - notifyKeyboardSystemShortcutTriggered(DEVICE_ID, HOME_SHORTCUT) + // Notifying key gesture event will notify the listener. + notifyKeyGestureEvent(HOME_GESTURE_EVENT) testLooper.dispatchNext() assertEquals(1, callbackCount) } @@ -142,34 +142,34 @@ class KeyboardSystemShortcutListenerTest { @Test fun testAddingListenersRegistersInternalCallbackListener() { // Set up two callbacks. - val callback1 = InputManager.KeyboardSystemShortcutListener {_, _ -> } - val callback2 = InputManager.KeyboardSystemShortcutListener {_, _ -> } + val callback1 = InputManager.KeyGestureEventListener { _ -> } + val callback2 = InputManager.KeyGestureEventListener { _ -> } assertNull(registeredListener) // Adding the listener should register the callback with InputManagerService. - inputManager.registerKeyboardSystemShortcutListener(executor, callback1) + inputManager.registerKeyGestureEventListener(executor, callback1) assertNotNull(registeredListener) // Adding another listener should not register new internal listener. val currListener = registeredListener - inputManager.registerKeyboardSystemShortcutListener(executor, callback2) + inputManager.registerKeyGestureEventListener(executor, callback2) assertEquals(currListener, registeredListener) } @Test fun testRemovingListenersUnregistersInternalCallbackListener() { // Set up two callbacks. - val callback1 = InputManager.KeyboardSystemShortcutListener {_, _ -> } - val callback2 = InputManager.KeyboardSystemShortcutListener {_, _ -> } + val callback1 = InputManager.KeyGestureEventListener { _ -> } + val callback2 = InputManager.KeyGestureEventListener { _ -> } - inputManager.registerKeyboardSystemShortcutListener(executor, callback1) - inputManager.registerKeyboardSystemShortcutListener(executor, callback2) + inputManager.registerKeyGestureEventListener(executor, callback1) + inputManager.registerKeyGestureEventListener(executor, callback2) // Only removing all listeners should remove the internal callback - inputManager.unregisterKeyboardSystemShortcutListener(callback1) + inputManager.unregisterKeyGestureEventListener(callback1) assertNotNull(registeredListener) - inputManager.unregisterKeyboardSystemShortcutListener(callback2) + inputManager.unregisterKeyGestureEventListener(callback2) assertNull(registeredListener) } @@ -178,23 +178,23 @@ class KeyboardSystemShortcutListenerTest { // Set up two callbacks. var callbackCount1 = 0 var callbackCount2 = 0 - val callback1 = InputManager.KeyboardSystemShortcutListener { _, _ -> callbackCount1++ } - val callback2 = InputManager.KeyboardSystemShortcutListener { _, _ -> callbackCount2++ } + val callback1 = InputManager.KeyGestureEventListener { _ -> callbackCount1++ } + val callback2 = InputManager.KeyGestureEventListener { _ -> callbackCount2++ } - // Add both keyboard system shortcut listeners - inputManager.registerKeyboardSystemShortcutListener(executor, callback1) - inputManager.registerKeyboardSystemShortcutListener(executor, callback2) + // Add both key gesture event listeners + inputManager.registerKeyGestureEventListener(executor, callback1) + inputManager.registerKeyGestureEventListener(executor, callback2) - // Notifying keyboard system shortcut triggered, should notify both the callbacks. - notifyKeyboardSystemShortcutTriggered(DEVICE_ID, HOME_SHORTCUT) + // Notifying key gesture event, should notify both the callbacks. + notifyKeyGestureEvent(HOME_GESTURE_EVENT) testLooper.dispatchAll() assertEquals(1, callbackCount1) assertEquals(1, callbackCount2) - inputManager.unregisterKeyboardSystemShortcutListener(callback2) - // Notifying keyboard system shortcut triggered, should still trigger callback1 but not + inputManager.unregisterKeyGestureEventListener(callback2) + // Notifying key gesture event, should still trigger callback1 but not // callback2. - notifyKeyboardSystemShortcutTriggered(DEVICE_ID, HOME_SHORTCUT) + notifyKeyGestureEvent(HOME_GESTURE_EVENT) testLooper.dispatchAll() assertEquals(2, callbackCount1) assertEquals(1, callbackCount2) diff --git a/tests/Input/src/com/android/server/input/KeyboardShortcutCallbackHandlerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt index 5a40a1c8201e..3f611e0ead53 100644 --- a/tests/Input/src/com/android/server/input/KeyboardShortcutCallbackHandlerTests.kt +++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt @@ -18,8 +18,8 @@ package com.android.server.input import android.content.Context import android.content.ContextWrapper -import android.hardware.input.IKeyboardSystemShortcutListener -import android.hardware.input.KeyboardSystemShortcut +import android.hardware.input.IKeyGestureEventListener +import android.hardware.input.KeyGestureEvent import android.platform.test.annotations.Presubmit import android.view.KeyEvent import androidx.test.core.app.ApplicationProvider @@ -32,65 +32,65 @@ import org.mockito.Mockito import org.mockito.junit.MockitoJUnit /** - * Tests for {@link KeyboardShortcutCallbackHandler}. + * Tests for {@link KeyGestureController}. * * Build/Install/Run: - * atest InputTests:KeyboardShortcutCallbackHandlerTests + * atest InputTests:KeyGestureControllerTests */ @Presubmit -class KeyboardShortcutCallbackHandlerTests { +class KeyGestureControllerTests { companion object { val DEVICE_ID = 1 - val HOME_SHORTCUT = KeyboardSystemShortcut( + val HOME_GESTURE_EVENT = KeyGestureEvent( + DEVICE_ID, intArrayOf(KeyEvent.KEYCODE_H), KeyEvent.META_META_ON or KeyEvent.META_META_LEFT_ON, - KeyboardSystemShortcut.SYSTEM_SHORTCUT_HOME + KeyGestureEvent.KEY_GESTURE_TYPE_HOME ) } @get:Rule val rule = MockitoJUnit.rule()!! - private lateinit var keyboardShortcutCallbackHandler: KeyboardShortcutCallbackHandler + private lateinit var keyGestureController: KeyGestureController private lateinit var context: Context - private var lastShortcut: KeyboardSystemShortcut? = null + private var lastEvent: KeyGestureEvent? = null @Before fun setup() { context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext())) - keyboardShortcutCallbackHandler = KeyboardShortcutCallbackHandler() + keyGestureController = KeyGestureController() } @Test - fun testKeyboardSystemShortcutTriggered_registerUnregisterListener() { - val listener = KeyboardSystemShortcutListener() + fun testKeyGestureEvent_registerUnregisterListener() { + val listener = KeyGestureEventListener() - // Register keyboard system shortcut listener - keyboardShortcutCallbackHandler.registerKeyboardSystemShortcutListener(listener, 0) - keyboardShortcutCallbackHandler.onKeyboardSystemShortcutTriggered(DEVICE_ID, HOME_SHORTCUT) + // Register key gesture event listener + keyGestureController.registerKeyGestureEventListener(listener, 0) + keyGestureController.onKeyGestureEvent(HOME_GESTURE_EVENT) assertEquals( - "Listener should get callback on keyboard system shortcut triggered", - HOME_SHORTCUT, - lastShortcut!! + "Listener should get callback on key gesture event", + HOME_GESTURE_EVENT, + lastEvent!! ) // Unregister listener - lastShortcut = null - keyboardShortcutCallbackHandler.unregisterKeyboardSystemShortcutListener(listener, 0) - keyboardShortcutCallbackHandler.onKeyboardSystemShortcutTriggered(DEVICE_ID, HOME_SHORTCUT) - assertNull("Listener should not get callback after being unregistered", lastShortcut) + lastEvent = null + keyGestureController.unregisterKeyGestureEventListener(listener, 0) + keyGestureController.onKeyGestureEvent(HOME_GESTURE_EVENT) + assertNull("Listener should not get callback after being unregistered", lastEvent) } - inner class KeyboardSystemShortcutListener : IKeyboardSystemShortcutListener.Stub() { - override fun onKeyboardSystemShortcutTriggered( + inner class KeyGestureEventListener : IKeyGestureEventListener.Stub() { + override fun onKeyGestureEvent( deviceId: Int, keycodes: IntArray, modifierState: Int, - shortcut: Int + gestureType: Int ) { - assertEquals(DEVICE_ID, deviceId) - lastShortcut = KeyboardSystemShortcut(keycodes, modifierState, shortcut) + lastEvent = KeyGestureEvent(deviceId, keycodes, modifierState, gestureType) } } }
\ No newline at end of file diff --git a/tests/Internal/AndroidTest.xml b/tests/Internal/AndroidTest.xml index 7b67e9ebcced..2d6c650eb2dc 100644 --- a/tests/Internal/AndroidTest.xml +++ b/tests/Internal/AndroidTest.xml @@ -26,4 +26,12 @@ <option name="package" value="com.android.internal.tests" /> <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> </test> + + <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> + <option name="pull-pattern-keys" value="perfetto_file_path"/> + <option name="directory-keys" + value="/data/user/0/com.android.internal.tests/files"/> + <option name="collect-on-run-ended-only" value="true"/> + <option name="clean-up" value="true"/> + </metrics_collector> </configuration>
\ No newline at end of file diff --git a/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java b/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java index 5a48327e7576..9657225588b7 100644 --- a/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java +++ b/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java @@ -214,6 +214,13 @@ public class LegacyProtoLogImplTest { verify(mReader, never()).getViewerString(anyLong()); } + @Test + public void loadViewerConfigOnLogcatGroupRegistration() { + TestProtoLogGroup.TEST_GROUP.setLogToLogcat(true); + mProtoLog.registerGroups(TestProtoLogGroup.TEST_GROUP); + verify(mReader).loadViewerConfig(any(), any()); + } + private static class ProtoLogData { Long mMessageHash = null; Long mElapsedTime = null; diff --git a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java b/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java index 7d0c5966b5dc..4b745b289d33 100644 --- a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java +++ b/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java @@ -29,7 +29,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static java.io.File.createTempFile; -import static java.nio.file.Files.createTempDirectory; import android.content.Context; import android.os.SystemClock; @@ -45,6 +44,7 @@ import android.tracing.perfetto.DataSource; import android.util.proto.ProtoInputStream; import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; import com.android.internal.protolog.common.IProtoLogGroup; import com.android.internal.protolog.common.LogDataType; @@ -67,7 +67,6 @@ import java.io.File; import java.io.IOException; import java.util.List; import java.util.Random; -import java.util.TreeMap; import java.util.concurrent.atomic.AtomicInteger; /** @@ -78,7 +77,8 @@ import java.util.concurrent.atomic.AtomicInteger; @Presubmit @RunWith(JUnit4.class) public class PerfettoProtoLogImplTest { - private final File mTracingDirectory = createTempDirectory("temp").toFile(); + private final File mTracingDirectory = InstrumentationRegistry.getInstrumentation() + .getTargetContext().getFilesDir(); private final ResultWriter mWriter = new ResultWriter() .forScenario(new ScenarioBuilder() @@ -384,7 +384,7 @@ public class PerfettoProtoLogImplTest { new Object[]{5}); verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq( - LogLevel.INFO), eq("UNKNOWN MESSAGE#1234 (5)")); + LogLevel.INFO), eq("UNKNOWN MESSAGE args = (5)")); verify(mReader).getViewerString(eq(1234L)); } @@ -451,8 +451,8 @@ public class PerfettoProtoLogImplTest { before = SystemClock.elapsedRealtimeNanos(); mProtoLog.log( LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, - "My test message :: %s, %d, %o, %x, %f, %b", - "test", 1, 2, 3, 0.4, true); + "My test message :: %s, %d, %x, %f, %b", + "test", 1, 3, 0.4, true); after = SystemClock.elapsedRealtimeNanos(); } finally { traceMonitor.stop(mWriter); @@ -467,7 +467,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, 4, 6, 0.400000, true"); + .isEqualTo("My test message :: test, 2, 6, 0.400000, true"); } @Test diff --git a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostSyncTest.java b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostSyncTest.java index 359eb35384c7..5012c235a2a5 100644 --- a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostSyncTest.java +++ b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostSyncTest.java @@ -84,6 +84,7 @@ public class SurfaceControlViewHostSyncTest extends Activity implements SurfaceH content.addView(enableSyncButton, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.RIGHT | Gravity.BOTTOM)); + content.setFitsSystemWindows(true); setContentView(content); mSv.setZOrderOnTop(false); diff --git a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostTest.java b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostTest.java index 73e01634709e..4119ea2c73c3 100644 --- a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostTest.java +++ b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostTest.java @@ -37,6 +37,7 @@ public class SurfaceControlViewHostTest extends Activity implements SurfaceHolde protected void onCreate(Bundle savedInstanceState) { FrameLayout content = new FrameLayout(this); + content.setFitsSystemWindows(true); super.onCreate(savedInstanceState); mView = new SurfaceView(this); content.addView(mView, new FrameLayout.LayoutParams( diff --git a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceInputTestActivity.java b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceInputTestActivity.java index ac7dc9e2f31f..528706860b31 100644 --- a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceInputTestActivity.java +++ b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceInputTestActivity.java @@ -88,6 +88,7 @@ public class SurfaceInputTestActivity extends Activity { protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); LinearLayout content = new LinearLayout(this); + content.setFitsSystemWindows(true); mLocalSurfaceView = new SurfaceView(this); content.addView(mLocalSurfaceView, new LinearLayout.LayoutParams( 500, 500, Gravity.CENTER_HORIZONTAL | Gravity.TOP)); diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp index 1c85e9ff231b..a5aecc855707 100644 --- a/tools/aapt2/ResourceParser.cpp +++ b/tools/aapt2/ResourceParser.cpp @@ -151,6 +151,7 @@ static bool AddResourcesToTable(ResourceTable* table, android::IDiagnostics* dia } if (res->value != nullptr) { + res->value->SetFlagStatus(res->flag_status); // Attach the comment, source and config to the value. res->value->SetComment(std::move(res->comment)); res->value->SetSource(std::move(res->source)); @@ -546,30 +547,11 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser, }); std::string resource_type = parser->element_name(); - std::optional<StringPiece> flag = - xml::FindAttribute(parser, "http://schemas.android.com/apk/res/android", "featureFlag"); - out_resource->flag_status = FlagStatus::NoFlag; - if (flag) { - auto flag_it = options_.feature_flag_values.find(flag.value()); - if (flag_it == options_.feature_flag_values.end()) { - diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number())) - << "Resource flag value undefined"); - return false; - } - const auto& flag_properties = flag_it->second; - if (!flag_properties.read_only) { - diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number())) - << "Only read only flags may be used with resources"); - return false; - } - if (!flag_properties.enabled.has_value()) { - diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number())) - << "Only flags with a value may be used with resources"); - return false; - } - out_resource->flag_status = - flag_properties.enabled.value() ? FlagStatus::Enabled : FlagStatus::Disabled; + auto flag_status = GetFlagStatus(parser); + if (!flag_status) { + return false; } + out_resource->flag_status = flag_status.value(); // The value format accepted for this resource. uint32_t resource_format = 0u; @@ -751,6 +733,33 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser, return false; } +std::optional<FlagStatus> ResourceParser::GetFlagStatus(xml::XmlPullParser* parser) { + auto flag_status = FlagStatus::NoFlag; + + std::optional<StringPiece> flag = xml::FindAttribute(parser, xml::kSchemaAndroid, "featureFlag"); + if (flag) { + auto flag_it = options_.feature_flag_values.find(flag.value()); + if (flag_it == options_.feature_flag_values.end()) { + diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number())) + << "Resource flag value undefined"); + return {}; + } + const auto& flag_properties = flag_it->second; + if (!flag_properties.read_only) { + diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number())) + << "Only read only flags may be used with resources"); + return {}; + } + if (!flag_properties.enabled.has_value()) { + diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number())) + << "Only flags with a value may be used with resources"); + return {}; + } + flag_status = flag_properties.enabled.value() ? FlagStatus::Enabled : FlagStatus::Disabled; + } + return flag_status; +} + bool ResourceParser::ParseItem(xml::XmlPullParser* parser, ParsedResource* out_resource, const uint32_t format) { @@ -1657,12 +1666,18 @@ bool ResourceParser::ParseArrayImpl(xml::XmlPullParser* parser, const std::string& element_namespace = parser->element_namespace(); const std::string& element_name = parser->element_name(); if (element_namespace.empty() && element_name == "item") { + auto flag_status = GetFlagStatus(parser); + if (!flag_status) { + error = true; + continue; + } std::unique_ptr<Item> item = ParseXml(parser, typeMask, kNoRawString); if (!item) { diag_->Error(android::DiagMessage(item_source) << "could not parse array item"); error = true; continue; } + item->SetFlagStatus(flag_status.value()); item->SetSource(item_source); array->elements.emplace_back(std::move(item)); diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h index 45d41c193cb4..442dea89ef40 100644 --- a/tools/aapt2/ResourceParser.h +++ b/tools/aapt2/ResourceParser.h @@ -85,6 +85,8 @@ class ResourceParser { private: DISALLOW_COPY_AND_ASSIGN(ResourceParser); + std::optional<FlagStatus> GetFlagStatus(xml::XmlPullParser* parser); + std::optional<FlattenedXmlSubTree> CreateFlattenSubTree(xml::XmlPullParser* parser); // Parses the XML subtree as a StyleString (flattened XML representation for strings with diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp index 1cdb71551d5d..7a4f40e471d2 100644 --- a/tools/aapt2/ResourceTable.cpp +++ b/tools/aapt2/ResourceTable.cpp @@ -605,12 +605,12 @@ bool ResourceTable::AddResource(NewResource&& res, android::IDiagnostics* diag) if (!config_value->value) { // Resource does not exist, add it now. config_value->value = std::move(res.value); - config_value->flag_status = res.flag_status; } else { // When validation is enabled, ensure that a resource cannot have multiple values defined for // the same configuration unless protected by flags. - auto result = validate ? ResolveFlagCollision(config_value->flag_status, res.flag_status) - : CollisionResult::kKeepBoth; + auto result = + validate ? ResolveFlagCollision(config_value->value->GetFlagStatus(), res.flag_status) + : CollisionResult::kKeepBoth; if (result == CollisionResult::kConflict) { result = ResolveValueCollision(config_value->value.get(), res.value.get()); } @@ -619,7 +619,6 @@ bool ResourceTable::AddResource(NewResource&& res, android::IDiagnostics* diag) // Insert the value ignoring for duplicate configurations entry->values.push_back(util::make_unique<ResourceConfigValue>(res.config, res.product)); entry->values.back()->value = std::move(res.value); - entry->values.back()->flag_status = res.flag_status; break; case CollisionResult::kTakeNew: diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h index 4f76e7d3a2b7..cba6b70cfbd6 100644 --- a/tools/aapt2/ResourceTable.h +++ b/tools/aapt2/ResourceTable.h @@ -104,8 +104,6 @@ class ResourceConfigValue { // The actual Value. std::unique_ptr<Value> value; - FlagStatus flag_status = FlagStatus::NoFlag; - ResourceConfigValue(const android::ConfigDescription& config, android::StringPiece product) : config(config), product(product) { } diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp index 166b01bd9154..b75e87c90128 100644 --- a/tools/aapt2/ResourceValues.cpp +++ b/tools/aapt2/ResourceValues.cpp @@ -971,6 +971,16 @@ void Array::Print(std::ostream* out) const { *out << "(array) [" << util::Joiner(elements, ", ") << "]"; } +void Array::RemoveFlagDisabledElements() { + const auto end_iter = elements.end(); + const auto remove_iter = std::stable_partition( + elements.begin(), end_iter, [](const std::unique_ptr<Item>& item) -> bool { + return item->GetFlagStatus() != FlagStatus::Disabled; + }); + + elements.erase(remove_iter, end_iter); +} + bool Plural::Equals(const Value* value) const { const Plural* other = ValueCast<Plural>(value); if (!other) { @@ -1092,6 +1102,7 @@ template <typename T> std::unique_ptr<T> CopyValueFields(std::unique_ptr<T> new_value, const T* value) { new_value->SetSource(value->GetSource()); new_value->SetComment(value->GetComment()); + new_value->SetFlagStatus(value->GetFlagStatus()); return new_value; } diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h index 5192c2be1f98..a1b1839b19ef 100644 --- a/tools/aapt2/ResourceValues.h +++ b/tools/aapt2/ResourceValues.h @@ -65,6 +65,14 @@ class Value { return translatable_; } + void SetFlagStatus(FlagStatus val) { + flag_status_ = val; + } + + FlagStatus GetFlagStatus() const { + return flag_status_; + } + // Returns the source where this value was defined. const android::Source& GetSource() const { return source_; @@ -109,6 +117,10 @@ class Value { // of brevity and readability. Default implementation just calls Print(). virtual void PrettyPrint(text::Printer* printer) const; + // Removes any part of the value that is beind a disabled flag. + virtual void RemoveFlagDisabledElements() { + } + friend std::ostream& operator<<(std::ostream& out, const Value& value); protected: @@ -116,6 +128,7 @@ class Value { std::string comment_; bool weak_ = false; bool translatable_ = true; + FlagStatus flag_status_ = FlagStatus::NoFlag; private: virtual Value* TransformValueImpl(ValueTransformer& transformer) const = 0; @@ -346,6 +359,7 @@ struct Array : public TransformableValue<Array, BaseValue<Array>> { bool Equals(const Value* value) const override; void Print(std::ostream* out) const override; + void RemoveFlagDisabledElements() override; }; struct Plural : public TransformableValue<Plural, BaseValue<Plural>> { diff --git a/tools/aapt2/Resources.proto b/tools/aapt2/Resources.proto index 2ecc82ae4792..5c6408940b34 100644 --- a/tools/aapt2/Resources.proto +++ b/tools/aapt2/Resources.proto @@ -246,7 +246,7 @@ message Entry { message ConfigValue { Configuration config = 1; Value value = 2; - uint32 flag_status = 3; + reserved 3; } // The generic meta-data for every value in a resource table. @@ -280,6 +280,9 @@ message Item { Id id = 6; Primitive prim = 7; } + + // The status of the flag the value is behind if any + uint32 flag_status = 8; } // A CompoundValue is an abstract type. It represents a value that is a made of other values. diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp index 56f52885b36d..be63f82b30cf 100644 --- a/tools/aapt2/cmd/Link.cpp +++ b/tools/aapt2/cmd/Link.cpp @@ -1878,7 +1878,7 @@ class Linker { for (auto& type : package->types) { for (auto& entry : type->entries) { for (auto& config_value : entry->values) { - if (config_value->flag_status == FlagStatus::Disabled) { + if (config_value->value->GetFlagStatus() == FlagStatus::Disabled) { config_value->value->Accept(&visitor); } } diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp index aaab3158f61e..55f5e5668a16 100644 --- a/tools/aapt2/format/proto/ProtoDeserialize.cpp +++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp @@ -534,8 +534,6 @@ static bool DeserializePackageFromPb(const pb::Package& pb_package, const ResStr return false; } - config_value->flag_status = (FlagStatus)pb_config_value.flag_status(); - config_value->value = DeserializeValueFromPb(pb_config_value.value(), src_pool, config, &out_table->string_pool, files, out_error); if (config_value->value == nullptr) { @@ -877,11 +875,12 @@ std::unique_ptr<Value> DeserializeValueFromPb(const pb::Value& pb_value, return value; } -std::unique_ptr<Item> DeserializeItemFromPb(const pb::Item& pb_item, - const android::ResStringPool& src_pool, - const ConfigDescription& config, - android::StringPool* value_pool, - io::IFileCollection* files, std::string* out_error) { +std::unique_ptr<Item> DeserializeItemFromPbInternal(const pb::Item& pb_item, + const android::ResStringPool& src_pool, + const ConfigDescription& config, + android::StringPool* value_pool, + io::IFileCollection* files, + std::string* out_error) { switch (pb_item.value_case()) { case pb::Item::kRef: { const pb::Reference& pb_ref = pb_item.ref(); @@ -1010,6 +1009,19 @@ std::unique_ptr<Item> DeserializeItemFromPb(const pb::Item& pb_item, return {}; } +std::unique_ptr<Item> DeserializeItemFromPb(const pb::Item& pb_item, + const android::ResStringPool& src_pool, + const ConfigDescription& config, + android::StringPool* value_pool, + io::IFileCollection* files, std::string* out_error) { + auto item = + DeserializeItemFromPbInternal(pb_item, src_pool, config, value_pool, files, out_error); + if (item) { + item->SetFlagStatus((FlagStatus)pb_item.flag_status()); + } + return item; +} + std::unique_ptr<xml::XmlResource> DeserializeXmlResourceFromPb(const pb::XmlNode& pb_node, std::string* out_error) { if (!pb_node.has_element()) { diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp index c1e15bcf9f70..5772b3b0b3e6 100644 --- a/tools/aapt2/format/proto/ProtoSerialize.cpp +++ b/tools/aapt2/format/proto/ProtoSerialize.cpp @@ -426,7 +426,6 @@ void SerializeTableToPb(const ResourceTable& table, pb::ResourceTable* out_table pb_config_value->mutable_config()->set_product(config_value->product); SerializeValueToPb(*config_value->value, pb_config_value->mutable_value(), source_pool.get()); - pb_config_value->set_flag_status((uint32_t)config_value->flag_status); } } } @@ -720,6 +719,9 @@ void SerializeValueToPb(const Value& value, pb::Value* out_value, android::Strin if (src_pool != nullptr) { SerializeSourceToPb(value.GetSource(), src_pool, out_value->mutable_source()); } + if (out_value->has_item()) { + out_value->mutable_item()->set_flag_status((uint32_t)value.GetFlagStatus()); + } } void SerializeItemToPb(const Item& item, pb::Item* out_item) { @@ -727,6 +729,7 @@ void SerializeItemToPb(const Item& item, pb::Item* out_item) { ValueSerializer serializer(&value, nullptr); item.Accept(&serializer); out_item->MergeFrom(value.item()); + out_item->set_flag_status((uint32_t)item.GetFlagStatus()); } void SerializeCompiledFileToPb(const ResourceFile& file, pb::internal::CompiledFile* out_file) { diff --git a/tools/aapt2/integration-tests/FlaggedResourcesTest/Android.bp b/tools/aapt2/integration-tests/FlaggedResourcesTest/Android.bp index 5932271d4d28..4866d2c83c71 100644 --- a/tools/aapt2/integration-tests/FlaggedResourcesTest/Android.bp +++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/Android.bp @@ -28,11 +28,13 @@ genrule { srcs: [ "res/values/bools.xml", "res/values/bools2.xml", + "res/values/ints.xml", "res/values/strings.xml", ], out: [ "values_bools.arsc.flat", "values_bools2.arsc.flat", + "values_ints.arsc.flat", "values_strings.arsc.flat", ], cmd: "$(location aapt2) compile $(in) -o $(genDir) " + diff --git a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/ints.xml b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/ints.xml new file mode 100644 index 000000000000..26a5c40bb338 --- /dev/null +++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/ints.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android"> + <integer-array name="intarr1"> + <item>1</item> + <item>2</item> + <item android:featureFlag="test.package.falseFlag">666</item> + <item>3</item> + </integer-array> +</resources>
\ No newline at end of file diff --git a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/strings.xml b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/strings.xml index 5c0fca16fe39..3cbb928a64cc 100644 --- a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/strings.xml +++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/strings.xml @@ -3,4 +3,11 @@ <string name="str">plain string</string> <string name="str1" android:featureFlag="test.package.falseFlag">DONTFIND</string> + + <string-array name="strarr1"> + <item>one</item> + <item>two</item> + <item android:featureFlag="test.package.falseFlag">remove</item> + <item android:featureFlag="test.package.trueFlag">three</item> + </string-array> </resources>
\ No newline at end of file diff --git a/tools/aapt2/link/FlagDisabledResourceRemover.cpp b/tools/aapt2/link/FlagDisabledResourceRemover.cpp index e3289e2a173a..3ac17625023e 100644 --- a/tools/aapt2/link/FlagDisabledResourceRemover.cpp +++ b/tools/aapt2/link/FlagDisabledResourceRemover.cpp @@ -32,12 +32,17 @@ static bool KeepResourceEntry(const std::unique_ptr<ResourceEntry>& entry) { const auto remove_iter = std::stable_partition(entry->values.begin(), end_iter, [](const std::unique_ptr<ResourceConfigValue>& value) -> bool { - return value->flag_status != FlagStatus::Disabled; + return value->value->GetFlagStatus() != FlagStatus::Disabled; }); bool keep = remove_iter != entry->values.begin(); entry->values.erase(remove_iter, end_iter); + + for (auto& value : entry->values) { + value->value->RemoveFlagDisabledElements(); + } + return keep; } diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp index 1942fc11c32e..37a039e9528f 100644 --- a/tools/aapt2/link/TableMerger.cpp +++ b/tools/aapt2/link/TableMerger.cpp @@ -212,8 +212,8 @@ static ResourceTable::CollisionResult MergeConfigValue( collision_result = ResolveMergeCollision(override_styles_instead_of_overlaying, dst_value, src_value, pool); } else { - collision_result = ResourceTable::ResolveFlagCollision(dst_config_value->flag_status, - src_config_value->flag_status); + collision_result = + ResourceTable::ResolveFlagCollision(dst_value->GetFlagStatus(), src_value->GetFlagStatus()); if (collision_result == CollisionResult::kConflict) { collision_result = ResourceTable::ResolveValueCollision(dst_value, src_value); } @@ -295,7 +295,6 @@ bool TableMerger::DoMerge(const android::Source& src, ResourceTablePackage* src_ } else { dst_config_value = dst_entry->FindOrCreateValue(src_config_value->config, src_config_value->product); - dst_config_value->flag_status = src_config_value->flag_status; } // Continue if we're taking the new resource. diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt index 36bfbefdb086..cb4db57bae0c 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt @@ -37,6 +37,7 @@ import org.objectweb.asm.commons.ClassRemapper import org.objectweb.asm.commons.Remapper import org.objectweb.asm.util.CheckClassAdapter import java.io.BufferedInputStream +import java.io.BufferedOutputStream import java.io.FileOutputStream import java.io.InputStream import java.io.OutputStream @@ -273,7 +274,7 @@ class HostStubGen(val options: HostStubGenOptions) { if (filename == null) { return block(null) } - return ZipOutputStream(FileOutputStream(filename)).use(block) + return ZipOutputStream(BufferedOutputStream(FileOutputStream(filename))).use(block) } /** @@ -334,13 +335,14 @@ class HostStubGen(val options: HostStubGenOptions) { entry: ZipEntry, out: ZipOutputStream, ) { - BufferedInputStream(inZip.getInputStream(entry)).use { bis -> + // TODO: It seems like copying entries this way is _very_ slow, + // even with out.setLevel(0). Look for other ways to do it. + + inZip.getInputStream(entry).use { ins -> // Copy unknown entries as is to the impl out. (but not to the stub out.) val outEntry = ZipEntry(entry.name) out.putNextEntry(outEntry) - while (bis.available() > 0) { - out.write(bis.read()) - } + ins.transferTo(out) out.closeEntry() } } diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt index ee4a06fb983d..fcdf8247e7d0 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt @@ -121,7 +121,7 @@ class HostStubGenLogger { return level.ordinal <= maxLogLevel.ordinal } - private fun println(level: LogLevel, message: String) { + fun println(level: LogLevel, message: String) { printers.forEach { if (it.logLevel.ordinal >= level.ordinal) { it.println(level, indent, message) @@ -129,7 +129,7 @@ class HostStubGenLogger { } } - private fun println(level: LogLevel, format: String, vararg args: Any?) { + fun println(level: LogLevel, format: String, vararg args: Any?) { if (isEnabled(level)) { println(level, String.format(format, *args)) } @@ -185,14 +185,29 @@ class HostStubGenLogger { println(LogLevel.Debug, format, *args) } - inline fun <T> iTime(message: String, block: () -> T): T { + inline fun <T> logTime(level: LogLevel, message: String, block: () -> T): T { val start = System.currentTimeMillis() - val ret = block() - val end = System.currentTimeMillis() + try { + return block() + } finally { + val end = System.currentTimeMillis() + if (isEnabled(level)) { + println(level, + String.format("%s: took %.1f second(s).", message, (end - start) / 1000.0)) + } + } + } - log.i("%s: took %.1f second(s).", message, (end - start) / 1000.0) + inline fun <T> iTime(message: String, block: () -> T): T { + return logTime(LogLevel.Info, message, block) + } + + inline fun <T> vTime(message: String, block: () -> T): T { + return logTime(LogLevel.Verbose, message, block) + } - return ret + inline fun <T> dTime(message: String, block: () -> T): T { + return logTime(LogLevel.Debug, message, block) } inline fun forVerbose(block: () -> Unit) { diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt index aa530050741b..22858803b597 100644 --- a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt +++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt @@ -26,7 +26,6 @@ import com.github.javaparser.ast.CompilationUnit import com.github.javaparser.ast.Modifier import com.github.javaparser.ast.NodeList import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration -import com.github.javaparser.ast.body.InitializerDeclaration import com.github.javaparser.ast.expr.ArrayAccessExpr import com.github.javaparser.ast.expr.ArrayCreationExpr import com.github.javaparser.ast.expr.ArrayInitializerExpr @@ -42,7 +41,10 @@ import com.github.javaparser.ast.expr.NullLiteralExpr import com.github.javaparser.ast.expr.ObjectCreationExpr import com.github.javaparser.ast.expr.SimpleName import com.github.javaparser.ast.expr.StringLiteralExpr +import com.github.javaparser.ast.expr.VariableDeclarationExpr import com.github.javaparser.ast.stmt.BlockStmt +import com.github.javaparser.ast.stmt.ReturnStmt +import com.github.javaparser.ast.type.ClassOrInterfaceType import java.io.File import java.io.FileInputStream import java.io.FileNotFoundException @@ -195,6 +197,7 @@ object ProtoLogTool { groups: Map<String, LogGroup>, protoLogGroupsClassName: String ) { + var needsCreateLogGroupsMap = false classDeclaration.fields.forEach { field -> field.getAnnotationByClass(ProtoLogToolInjected::class.java) .ifPresent { annotationExpr -> @@ -222,33 +225,10 @@ object ProtoLogTool { } ?: NullLiteralExpr()) } ProtoLogToolInjected.Value.LOG_GROUPS.name -> { - val initializerBlockStmt = BlockStmt() - for (group in groups) { - initializerBlockStmt.addStatement( - MethodCallExpr() - .setName("put") - .setArguments( - NodeList(StringLiteralExpr(group.key), - FieldAccessExpr() - .setScope( - NameExpr( - protoLogGroupsClassName - )) - .setName(group.value.name))) - ) - group.key - } - - val treeMapCreation = ObjectCreationExpr() - .setType("TreeMap<String, IProtoLogGroup>") - .setAnonymousClassBody(NodeList( - InitializerDeclaration().setBody( - initializerBlockStmt - ) - )) - + needsCreateLogGroupsMap = true field.setFinal(true) - field.variables.first().setInitializer(treeMapCreation) + field.variables.first().setInitializer( + MethodCallExpr().setName("createLogGroupsMap")) } ProtoLogToolInjected.Value.CACHE_UPDATER.name -> { field.setFinal(true) @@ -261,6 +241,45 @@ object ProtoLogTool { } } } + + if (needsCreateLogGroupsMap) { + val body = BlockStmt() + body.addStatement(AssignExpr( + VariableDeclarationExpr( + ClassOrInterfaceType("TreeMap<String, IProtoLogGroup>"), + "result" + ), + ObjectCreationExpr().setType("TreeMap<String, IProtoLogGroup>"), + AssignExpr.Operator.ASSIGN + )) + for (group in groups) { + body.addStatement( + MethodCallExpr( + NameExpr("result"), + "put", + NodeList( + StringLiteralExpr(group.key), + FieldAccessExpr() + .setScope( + NameExpr( + protoLogGroupsClassName + )) + .setName(group.value.name) + ) + ) + ) + } + body.addStatement(ReturnStmt(NameExpr("result"))) + + val method = classDeclaration.addMethod( + "createLogGroupsMap", + Modifier.Keyword.PRIVATE, + Modifier.Keyword.STATIC, + Modifier.Keyword.FINAL + ) + method.setType("TreeMap<String, IProtoLogGroup>") + method.setBody(body) + } } private fun injectCacheClass( |