diff options
274 files changed, 4850 insertions, 2645 deletions
diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp index b3a674fbd70e..d1aa23c8ea5f 100644 --- a/api/StubLibraries.bp +++ b/api/StubLibraries.bp @@ -466,6 +466,32 @@ java_library { } java_library { + name: "android-non-updatable.stubs.system_server", + defaults: ["android-non-updatable_defaults"], + static_libs: [ + "android-non-updatable.stubs.system_server.from-source", + ], + product_variables: { + build_from_text_stub: { + static_libs: [ + "android-non-updatable.stubs.system_server.from-text", + ], + exclude_static_libs: [ + "android-non-updatable.stubs.system_server.from-source", + ], + }, + }, +} + +java_library { + name: "android-non-updatable.stubs.exportable.system_server", + defaults: ["android-non-updatable_defaults"], + static_libs: [ + "android-non-updatable.stubs.exportable.system_server.from-source", + ], +} + +java_library { name: "android-non-updatable.stubs.from-source", defaults: [ "android-non-updatable_defaults", @@ -561,6 +587,30 @@ java_library { }, } +java_library { + name: "android-non-updatable.stubs.system_server.from-source", + defaults: [ + "android-non-updatable_defaults", + "android-non-updatable_from_source_defaults", + ], + srcs: [":services-non-updatable-stubs"], + libs: non_updatable_api_deps_on_modules, +} + +java_library { + name: "android-non-updatable.stubs.exportable.system_server.from-source", + defaults: [ + "android-non-updatable_defaults", + "android-non-updatable_from_source_defaults", + "android-non-updatable_exportable_from_source_defaults", + ], + srcs: [":services-non-updatable-stubs{.exportable}"], + libs: non_updatable_api_deps_on_modules, + dist: { + dir: "apistubs/android/system-server", + }, +} + java_defaults { name: "android-non-updatable_from_text_defaults", defaults: ["android-non-updatable-stubs-libs-defaults"], @@ -662,6 +712,25 @@ java_api_library { libs: ["all-modules-system-stubs"], } +java_api_library { + name: "android-non-updatable.stubs.system_server.from-text", + api_surface: "system_server", + api_contributions: [ + "api-stubs-docs-non-updatable.api.contribution", + "system-api-stubs-docs-non-updatable.api.contribution", + "module-lib-api-stubs-docs-non-updatable.api.contribution", + "services-non-updatable-stubs.api.contribution", + ], + defaults: [ + "module-classpath-java-defaults", + "android-non-updatable_everything_from_text_defaults", + ], + + // Use full Android API not just the non-updatable API as the latter is incomplete + // and can result in incorrect behavior. + previous_api: ":android.api.combined.system-server.latest", +} + java_defaults { name: "android_stubs_dists_default", dist: { @@ -813,9 +882,9 @@ java_library { defaults: [ "android.jar_defaults", ], - srcs: [":services-non-updatable-stubs"], installable: false, static_libs: [ + "android-non-updatable.stubs.system_server", "android_module_lib_stubs_current", ], visibility: ["//frameworks/base/services"], @@ -827,9 +896,9 @@ java_library { "android.jar_defaults", "android_stubs_dists_default", ], - srcs: [":services-non-updatable-stubs{.exportable}"], installable: false, static_libs: [ + "android-non-updatable.stubs.exportable.system_server", "android_module_lib_stubs_current_exportable", ], dist: { diff --git a/cmds/uiautomator/library/Android.bp b/cmds/uiautomator/library/Android.bp index 966bf13adfe4..5c5b220d20e9 100644 --- a/cmds/uiautomator/library/Android.bp +++ b/cmds/uiautomator/library/Android.bp @@ -28,9 +28,9 @@ droidstubs { "testrunner-src/**/*.java", ], libs: [ - "android.test.runner", + "android.test.runner.stubs.system", "junit", - "android.test.base", + "android.test.base.stubs.system", "unsupportedappusage", ], installable: false, @@ -56,9 +56,9 @@ droiddoc { ":uiautomator-stubs", ], libs: [ - "android.test.runner", + "android.test.runner.stubs", "junit", - "android.test.base", + "android.test.base.stubs", ], sdk_version: "current", installable: false, diff --git a/core/api/current.txt b/core/api/current.txt index 1667f2e4457a..542543d11318 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -32613,6 +32613,13 @@ package android.os { method public boolean isCharging(); field public static final String ACTION_CHARGING = "android.os.action.CHARGING"; field public static final String ACTION_DISCHARGING = "android.os.action.DISCHARGING"; + field @FlaggedApi("android.os.battery_part_status_api") public static final int BATTERY_CAPACITY_LEVEL_CRITICAL = 1; // 0x1 + field @FlaggedApi("android.os.battery_part_status_api") public static final int BATTERY_CAPACITY_LEVEL_FULL = 5; // 0x5 + field @FlaggedApi("android.os.battery_part_status_api") public static final int BATTERY_CAPACITY_LEVEL_HIGH = 4; // 0x4 + field @FlaggedApi("android.os.battery_part_status_api") public static final int BATTERY_CAPACITY_LEVEL_LOW = 2; // 0x2 + field @FlaggedApi("android.os.battery_part_status_api") public static final int BATTERY_CAPACITY_LEVEL_NORMAL = 3; // 0x3 + field @FlaggedApi("android.os.battery_part_status_api") public static final int BATTERY_CAPACITY_LEVEL_UNKNOWN = 0; // 0x0 + field @FlaggedApi("android.os.battery_part_status_api") public static final int BATTERY_CAPACITY_LEVEL_UNSUPPORTED = -1; // 0xffffffff field public static final int BATTERY_HEALTH_COLD = 7; // 0x7 field public static final int BATTERY_HEALTH_DEAD = 4; // 0x4 field public static final int BATTERY_HEALTH_GOOD = 2; // 0x2 @@ -32637,6 +32644,7 @@ package android.os { field public static final int BATTERY_STATUS_NOT_CHARGING = 4; // 0x4 field public static final int BATTERY_STATUS_UNKNOWN = 1; // 0x1 field public static final String EXTRA_BATTERY_LOW = "battery_low"; + field @FlaggedApi("android.os.battery_part_status_api") public static final String EXTRA_CAPACITY_LEVEL = "android.os.extra.CAPACITY_LEVEL"; field public static final String EXTRA_CHARGING_STATUS = "android.os.extra.CHARGING_STATUS"; field public static final String EXTRA_CYCLE_COUNT = "android.os.extra.CYCLE_COUNT"; field public static final String EXTRA_HEALTH = "health"; @@ -33228,6 +33236,7 @@ package android.os { } public interface IBinder { + method @FlaggedApi("android.os.binder_frozen_state_change_callback") public default void addFrozenStateChangeCallback(@NonNull android.os.IBinder.FrozenStateChangeCallback) throws android.os.RemoteException; method public void dump(@NonNull java.io.FileDescriptor, @Nullable String[]) throws android.os.RemoteException; method public void dumpAsync(@NonNull java.io.FileDescriptor, @Nullable String[]) throws android.os.RemoteException; method @Nullable public String getInterfaceDescriptor() throws android.os.RemoteException; @@ -33236,6 +33245,7 @@ package android.os { method public void linkToDeath(@NonNull android.os.IBinder.DeathRecipient, int) throws android.os.RemoteException; method public boolean pingBinder(); method @Nullable public android.os.IInterface queryLocalInterface(@NonNull String); + method @FlaggedApi("android.os.binder_frozen_state_change_callback") public default boolean removeFrozenStateChangeCallback(@NonNull android.os.IBinder.FrozenStateChangeCallback); method public boolean transact(int, @NonNull android.os.Parcel, @Nullable android.os.Parcel, int) throws android.os.RemoteException; method public boolean unlinkToDeath(@NonNull android.os.IBinder.DeathRecipient, int); field public static final int DUMP_TRANSACTION = 1598311760; // 0x5f444d50 @@ -33253,6 +33263,12 @@ package android.os { method public default void binderDied(@NonNull android.os.IBinder); } + @FlaggedApi("android.os.binder_frozen_state_change_callback") public static interface IBinder.FrozenStateChangeCallback { + method public void onFrozenStateChanged(@NonNull android.os.IBinder, int); + field public static final int STATE_FROZEN = 0; // 0x0 + field public static final int STATE_UNFROZEN = 1; // 0x1 + } + public interface IInterface { method public android.os.IBinder asBinder(); } @@ -44070,7 +44086,7 @@ package android.telephony { } public static final class CarrierConfigManager.Gps { - field @FlaggedApi("android.location.flags.enable_ni_supl_message_injection_by_carrier_config") public static final String KEY_ENABLE_NI_SUPL_MESSAGE_INJECTION_BOOL = "gps.enable_ni_supl_message_injection_bool"; + field @FlaggedApi("android.location.flags.enable_ni_supl_message_injection_by_carrier_config_bugfix") public static final String KEY_ENABLE_NI_SUPL_MESSAGE_INJECTION_BOOL = "gps.enable_ni_supl_message_injection_bool"; field public static final String KEY_PERSIST_LPP_MODE_BOOL = "gps.persist_lpp_mode_bool"; field public static final String KEY_PREFIX = "gps."; } diff --git a/core/java/android/app/appfunctions/AppFunctionService.java b/core/java/android/app/appfunctions/AppFunctionService.java index c27141a1acbf..0d981ea5a679 100644 --- a/core/java/android/app/appfunctions/AppFunctionService.java +++ b/core/java/android/app/appfunctions/AppFunctionService.java @@ -26,6 +26,7 @@ import android.annotation.MainThread; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Service; +import android.content.Context; import android.content.Intent; import android.os.Binder; import android.os.IBinder; @@ -60,29 +61,53 @@ public abstract class AppFunctionService extends Service { @NonNull public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService"; - private final Binder mBinder = - new IAppFunctionService.Stub() { - @Override - public void executeAppFunction( - @NonNull ExecuteAppFunctionRequest request, - @NonNull IExecuteAppFunctionCallback callback) { - if (AppFunctionService.this.checkCallingPermission(BIND_APP_FUNCTION_SERVICE) - == PERMISSION_DENIED) { - throw new SecurityException("Can only be called by the system server."); - } - SafeOneTimeExecuteAppFunctionCallback safeCallback = - new SafeOneTimeExecuteAppFunctionCallback(callback); - try { - AppFunctionService.this.onExecuteFunction(request, safeCallback::onResult); - } catch (Exception ex) { - // Apps should handle exceptions. But if they don't, report the error on - // behalf of them. - safeCallback.onResult( - ExecuteAppFunctionResponse.newFailure( - getResultCode(ex), ex.getMessage(), /* extras= */ null)); - } + /** + * Functional interface to represent the execution logic of an app function. + * + * @hide + */ + @FunctionalInterface + public interface OnExecuteFunction { + /** + * Performs the semantic of executing the function specified by the provided request and + * return the response through the provided callback. + */ + void perform( + @NonNull ExecuteAppFunctionRequest request, + @NonNull Consumer<ExecuteAppFunctionResponse> callback); + } + + /** @hide */ + @NonNull + public static Binder createBinder( + @NonNull Context context, @NonNull OnExecuteFunction onExecuteFunction) { + return new IAppFunctionService.Stub() { + @Override + public void executeAppFunction( + @NonNull ExecuteAppFunctionRequest request, + @NonNull IExecuteAppFunctionCallback callback) { + if (context.checkCallingPermission(BIND_APP_FUNCTION_SERVICE) + == PERMISSION_DENIED) { + throw new SecurityException("Can only be called by the system server."); + } + SafeOneTimeExecuteAppFunctionCallback safeCallback = + new SafeOneTimeExecuteAppFunctionCallback(callback); + try { + onExecuteFunction.perform(request, safeCallback::onResult); + } catch (Exception ex) { + // Apps should handle exceptions. But if they don't, report the error on + // behalf of them. + safeCallback.onResult( + ExecuteAppFunctionResponse.newFailure( + getResultCode(ex), ex.getMessage(), /* extras= */ null)); } - }; + } + }; + } + + private final Binder mBinder = createBinder( + AppFunctionService.this, + AppFunctionService.this::onExecuteFunction); @NonNull @Override diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig index 9891e8930936..9b06adf4e894 100644 --- a/core/java/android/app/notification.aconfig +++ b/core/java/android/app/notification.aconfig @@ -236,4 +236,12 @@ flag { namespace: "systemui" description: "Guards new android.app.richongoingnotification api" bug: "337261753" +} + +flag { + name: "ui_rich_ongoing" + is_exported: true + namespace: "systemui" + description: "Guards new android.app.richongoingnotification promotion and new uis" + bug: "337261753" }
\ No newline at end of file diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING index ffadd1e4083c..2cdae21bde92 100644 --- a/core/java/android/content/pm/TEST_MAPPING +++ b/core/java/android/content/pm/TEST_MAPPING @@ -206,6 +206,17 @@ ] }, { + "name": "CtsPackageInstallerCUJDeviceAdminTestCases", + "options":[ + { + "exclude-annotation":"androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation":"org.junit.Ignore" + } + ] + }, + { "name": "CtsPackageInstallerCUJInstallationTestCases", "options":[ { @@ -217,6 +228,17 @@ ] }, { + "name": "CtsPackageInstallerCUJMultiUsersTestCases", + "options":[ + { + "exclude-annotation":"androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation":"org.junit.Ignore" + } + ] + }, + { "name": "CtsPackageInstallerCUJUninstallationTestCases", "options":[ { diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java index 6f11d3ae661c..af93c964a8ba 100644 --- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java +++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java @@ -35,7 +35,6 @@ import android.os.PersistableBundle; import android.util.ArraySet; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.ArrayUtils; import com.android.internal.util.Preconditions; import com.android.server.vcn.util.PersistableBundleUtils; @@ -434,7 +433,14 @@ public final class VcnGatewayConnectionConfig { @NonNull public int[] getExposedCapabilities() { // Sorted set guarantees ordering - return ArrayUtils.convertToIntArray(new ArrayList<>(mExposedCapabilities)); + final int[] caps = new int[mExposedCapabilities.size()]; + + int i = 0; + for (int c : mExposedCapabilities) { + caps[i++] = c; + } + + return caps; } /** diff --git a/core/java/android/net/vcn/VcnUnderlyingNetworkSpecifier.java b/core/java/android/net/vcn/VcnUnderlyingNetworkSpecifier.java index a97563724e50..e1d1b3c65c99 100644 --- a/core/java/android/net/vcn/VcnUnderlyingNetworkSpecifier.java +++ b/core/java/android/net/vcn/VcnUnderlyingNetworkSpecifier.java @@ -24,7 +24,6 @@ import android.os.Parcelable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting.Visibility; -import com.android.internal.util.ArrayUtils; import java.util.Arrays; import java.util.Objects; @@ -114,8 +113,13 @@ public final class VcnUnderlyingNetworkSpecifier extends NetworkSpecifier implem @Override public boolean canBeSatisfiedBy(NetworkSpecifier other) { if (other instanceof TelephonyNetworkSpecifier) { - return ArrayUtils.contains( - mSubIds, ((TelephonyNetworkSpecifier) other).getSubscriptionId()); + final int targetSubId = ((TelephonyNetworkSpecifier) other).getSubscriptionId(); + for (int subId : mSubIds) { + if (targetSubId == subId) { + return true; + } + } + return false; } // TODO(b/180140053): Allow matching against WifiNetworkAgentSpecifier diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java index f3efd89d4bc3..8b267bf28c7e 100644 --- a/core/java/android/os/BatteryManager.java +++ b/core/java/android/os/BatteryManager.java @@ -141,6 +141,7 @@ public class BatteryManager { /** * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}: * integer containing the charge counter present in the battery. + * It shows the available battery power in µAh * {@hide} */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) @@ -166,6 +167,76 @@ public class BatteryManager { public static final String EXTRA_CHARGING_STATUS = "android.os.extra.CHARGING_STATUS"; /** + * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}: + * Int value representing the battery's capacity level. These constants are key indicators of + * battery status and system capabilities, guiding power management decisions for both the + * system and apps: + * {@link #BATTERY_CAPACITY_LEVEL_UNSUPPORTED}: Feature not supported on this device. + * {@link #BATTERY_CAPACITY_LEVEL_UNKNOWN}: Battery status is unavailable or uninitialized. + * {@link #BATTERY_CAPACITY_LEVEL_CRITICAL}: Battery is critically low and the Android + * framework has been notified to schedule a shutdown by this value + * {@link #BATTERY_CAPACITY_LEVEL_LOW}: Android framework must limit background jobs to + * avoid impacting charging speed + * {@link #BATTERY_CAPACITY_LEVEL_NORMAL}: Battery level and charging rates are normal, + * battery temperature is within normal range and adapter power is enough to charge the + * battery at an acceptable rate. Android framework can run light background tasks without + * affecting charging performance severely. + * {@link #BATTERY_CAPACITY_LEVEL_HIGH}: Battery level is high, battery temperature is + * within normal range and adapter power is enough to charge the battery at an acceptable + * rate while running background loads. Android framework can run background tasks without + * affecting charging or battery performance. + * {@link #BATTERY_CAPACITY_LEVEL_FULL}: The battery is full, battery temperature is + * within normal range and adapter power is enough to sustain running background loads. + * Android framework can run background tasks without affecting the battery level or + * battery performance. + */ + + @FlaggedApi(FLAG_BATTERY_PART_STATUS_API) + public static final String EXTRA_CAPACITY_LEVEL = "android.os.extra.CAPACITY_LEVEL"; + + /** + * Battery capacity level is unsupported. @see EXTRA_CAPACITY_LEVEL + */ + @FlaggedApi(FLAG_BATTERY_PART_STATUS_API) + public static final int BATTERY_CAPACITY_LEVEL_UNSUPPORTED = -1; + + /** + * Battery capacity level is unknown. @see EXTRA_CAPACITY_LEVEL + */ + @FlaggedApi(FLAG_BATTERY_PART_STATUS_API) + public static final int BATTERY_CAPACITY_LEVEL_UNKNOWN = 0; + + /** + * Battery capacity level is critical. @see EXTRA_CAPACITY_LEVEL + */ + @FlaggedApi(FLAG_BATTERY_PART_STATUS_API) + public static final int BATTERY_CAPACITY_LEVEL_CRITICAL = 1; + + /** + * Battery capacity level is low. @see EXTRA_CAPACITY_LEVEL + */ + @FlaggedApi(FLAG_BATTERY_PART_STATUS_API) + public static final int BATTERY_CAPACITY_LEVEL_LOW = 2; + + /** + * Battery capacity level is normal. @see EXTRA_CAPACITY_LEVEL + */ + @FlaggedApi(FLAG_BATTERY_PART_STATUS_API) + public static final int BATTERY_CAPACITY_LEVEL_NORMAL = 3; + + /** + * Battery capacity level is high. @see EXTRA_CAPACITY_LEVEL + */ + @FlaggedApi(FLAG_BATTERY_PART_STATUS_API) + public static final int BATTERY_CAPACITY_LEVEL_HIGH = 4; + + /** + * Battery capacity level is full. @see EXTRA_CAPACITY_LEVEL + */ + @FlaggedApi(FLAG_BATTERY_PART_STATUS_API) + public static final int BATTERY_CAPACITY_LEVEL_FULL = 5; + + /** * Extra for {@link android.content.Intent#ACTION_BATTERY_LEVEL_CHANGED}: * Contains list of Bundles representing battery events * @hide diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java index c22f46cdc2b5..80546cd6770f 100644 --- a/core/java/android/os/BinderProxy.java +++ b/core/java/android/os/BinderProxy.java @@ -650,13 +650,13 @@ public final class BinderProxy implements IBinder { * weakly referenced by JNI so the strong references here are needed to keep the callbacks * around until the proxy is GC'ed. */ - private List<IFrozenStateChangeCallback> mFrozenStateChangeCallbacks = + private List<FrozenStateChangeCallback> mFrozenStateChangeCallbacks = Collections.synchronizedList(new ArrayList<>()); /** - * See {@link IBinder#addFrozenStateChangeCallback(IFrozenStateChangeCallback)} + * See {@link IBinder#addFrozenStateChangeCallback(FrozenStateChangeCallback)} */ - public void addFrozenStateChangeCallback(IFrozenStateChangeCallback callback) + public void addFrozenStateChangeCallback(FrozenStateChangeCallback callback) throws RemoteException { addFrozenStateChangeCallbackNative(callback); mFrozenStateChangeCallbacks.add(callback); @@ -665,16 +665,16 @@ public final class BinderProxy implements IBinder { /** * See {@link IBinder#removeFrozenStateChangeCallback} */ - public boolean removeFrozenStateChangeCallback(IFrozenStateChangeCallback callback) { + public boolean removeFrozenStateChangeCallback(FrozenStateChangeCallback callback) { mFrozenStateChangeCallbacks.remove(callback); return removeFrozenStateChangeCallbackNative(callback); } - private native void addFrozenStateChangeCallbackNative(IFrozenStateChangeCallback callback) + private native void addFrozenStateChangeCallbackNative(FrozenStateChangeCallback callback) throws RemoteException; private native boolean removeFrozenStateChangeCallbackNative( - IFrozenStateChangeCallback callback); + FrozenStateChangeCallback callback); /** * Perform a dump on the remote object @@ -762,10 +762,9 @@ public final class BinderProxy implements IBinder { } private static void invokeFrozenStateChangeCallback( - IFrozenStateChangeCallback callback, IBinder binderProxy, int stateIndex) { + FrozenStateChangeCallback callback, IBinder binderProxy, int stateIndex) { try { - callback.onFrozenStateChanged(binderProxy, - IFrozenStateChangeCallback.State.values()[stateIndex]); + callback.onFrozenStateChanged(binderProxy, stateIndex); } catch (RuntimeException exc) { Log.w("BinderNative", "Uncaught exception from frozen state change callback", exc); diff --git a/core/java/android/os/IBinder.java b/core/java/android/os/IBinder.java index 8185e8e542e1..a997f4c86704 100644 --- a/core/java/android/os/IBinder.java +++ b/core/java/android/os/IBinder.java @@ -16,11 +16,15 @@ package android.os; +import android.annotation.FlaggedApi; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import java.io.FileDescriptor; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** * Base interface for a remotable object, the core part of a lightweight @@ -377,9 +381,24 @@ public interface IBinder { */ public boolean unlinkToDeath(@NonNull DeathRecipient recipient, int flags); - /** @hide */ - interface IFrozenStateChangeCallback { - enum State {FROZEN, UNFROZEN}; + /** + * A callback interface for receiving frozen state change events. + */ + @FlaggedApi(Flags.FLAG_BINDER_FROZEN_STATE_CHANGE_CALLBACK) + interface FrozenStateChangeCallback { + /** + * @hide + */ + @IntDef(prefix = {"STATE_"}, value = { + STATE_FROZEN, + STATE_UNFROZEN, + }) + @Retention(RetentionPolicy.SOURCE) + @interface State { + } + + int STATE_FROZEN = 0; + int STATE_UNFROZEN = 1; /** * Interface for receiving a callback when the process hosting an IBinder @@ -387,13 +406,13 @@ public interface IBinder { * @param who The IBinder whose hosting process has changed state. * @param state The latest state. */ - void onFrozenStateChanged(@NonNull IBinder who, State state); + void onFrozenStateChanged(@NonNull IBinder who, @State int state); } /** - * {@link addFrozenStateChangeCallback} provides a callback mechanism to notify about process - * frozen/unfrozen events. Upon registration and any subsequent state changes, the callback is - * invoked with the latest process frozen state. + * This method provides a callback mechanism to notify about process frozen/unfrozen events. + * Upon registration and any subsequent state changes, the callback is invoked with the latest + * process frozen state. * * <p>If the listener process (the one using this API) is itself frozen, state change events * might be combined into a single one with the latest frozen state. This single event would @@ -410,19 +429,19 @@ public interface IBinder { * * <p>@throws {@link UnsupportedOperationException} if the kernel binder driver does not support * this feature. - * @hide */ - default void addFrozenStateChangeCallback(@NonNull IFrozenStateChangeCallback callback) + @FlaggedApi(Flags.FLAG_BINDER_FROZEN_STATE_CHANGE_CALLBACK) + default void addFrozenStateChangeCallback(@NonNull FrozenStateChangeCallback callback) throws RemoteException { throw new UnsupportedOperationException(); } /** - * Unregister a {@link IFrozenStateChangeCallback}. The callback will no longer be invoked when + * Unregister a {@link FrozenStateChangeCallback}. The callback will no longer be invoked when * the hosting process changes its frozen state. - * @hide */ - default boolean removeFrozenStateChangeCallback(@NonNull IFrozenStateChangeCallback callback) { + @FlaggedApi(Flags.FLAG_BINDER_FROZEN_STATE_CHANGE_CALLBACK) + default boolean removeFrozenStateChangeCallback(@NonNull FrozenStateChangeCallback callback) { throw new UnsupportedOperationException(); } } diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig index 39bd15c968d7..738d12978aed 100644 --- a/core/java/android/os/flags.aconfig +++ b/core/java/android/os/flags.aconfig @@ -153,6 +153,14 @@ flag { } flag { + name: "binder_frozen_state_change_callback" + is_exported: true + namespace: "system_performance" + description: "Guards the frozen state change callback API." + bug: "361157077" +} + +flag { name: "message_queue_tail_tracking" namespace: "system_performance" description: "track tail of message queue." diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index e32625e1f7a8..0ada9934482c 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -111,6 +111,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Field; import java.net.URISyntaxException; +import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -3022,6 +3023,9 @@ public final class Settings { /** @hide - Private call() method to query the 'configuration' table */ public static final String CALL_METHOD_LIST_CONFIG = "LIST_config"; + /** @hide - Private call() method to query the 'configuration' tables' namespaces */ + public static final String CALL_METHOD_LIST_NAMESPACES_CONFIG = "LIST_namespaces_config"; + /** @hide - Private call() method to disable / re-enable syncs to the 'configuration' table */ public static final String CALL_METHOD_SET_SYNC_DISABLED_MODE_CONFIG = "SET_SYNC_DISABLED_MODE_config"; @@ -20458,6 +20462,10 @@ public final class Settings { * * The keys take the form {@code namespace/flag}, and the values are the flag values. * + * Note: this API is _not_ performant, and may make a large number of + * Binder calls. It is intended for use in testing and debugging, and + * should not be used in performance-sensitive code. + * * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @@ -20469,13 +20477,33 @@ public final class Settings { Bundle arg = new Bundle(); arg.putInt(Settings.CALL_METHOD_USER_KEY, resolver.getUserId()); IContentProvider cp = sProviderHolder.getProvider(resolver); - Bundle b = cp.call(resolver.getAttributionSource(), - sProviderHolder.mUri.getAuthority(), CALL_METHOD_LIST_CONFIG, null, arg); - if (b != null) { - Map<String, String> flagsToValues = - (HashMap) b.getSerializable(Settings.NameValueTable.VALUE, - java.util.HashMap.class); - allFlags.putAll(flagsToValues); + + if (Flags.reduceBinderTransactionSizeForGetAllProperties()) { + Bundle b = cp.call(resolver.getAttributionSource(), + sProviderHolder.mUri.getAuthority(), + CALL_METHOD_LIST_NAMESPACES_CONFIG, null, arg); + if (b != null) { + HashSet<String> namespaces = + (HashSet) b.getSerializable(Settings.NameValueTable.VALUE, + java.util.HashSet.class); + for (String namespace : namespaces) { + Map<String, String> keyValues = + getStrings(namespace, new ArrayList()); + for (String key : keyValues.keySet()) { + allFlags.put(namespace + "/" + key, keyValues.get(key)); + } + } + } + } else { + Bundle b = cp.call(resolver.getAttributionSource(), + sProviderHolder.mUri.getAuthority(), + CALL_METHOD_LIST_CONFIG, null, arg); + if (b != null) { + Map<String, String> flagsToValues = + (HashMap) b.getSerializable(Settings.NameValueTable.VALUE, + java.util.HashMap.class); + allFlags.putAll(flagsToValues); + } } } catch (RemoteException e) { Log.w(TAG, "Can't query configuration table for " + CONTENT_URI, e); diff --git a/core/java/android/provider/flags.aconfig b/core/java/android/provider/flags.aconfig index 5c0f8737ca27..4c636735b5ce 100644 --- a/core/java/android/provider/flags.aconfig +++ b/core/java/android/provider/flags.aconfig @@ -52,3 +52,14 @@ flag { description: "Enable the new ContactsContract Default Account APIs." bug: "359957527" } + +flag { + name: "reduce_binder_transaction_size_for_get_all_properties" + namespace: "core_experiments_team_internal" + description: "Reduce Binder transaction size in getAllProperties calls" + bug: "362652574" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/text/ClientFlags.java b/core/java/android/text/ClientFlags.java index c2ad508c2b44..ca887646b3aa 100644 --- a/core/java/android/text/ClientFlags.java +++ b/core/java/android/text/ClientFlags.java @@ -16,21 +16,14 @@ package android.text; -import com.android.text.flags.Flags; - /** * An aconfig feature flags that can be accessible from application process without * ContentProvider IPCs. * * When you add new flags, you have to add flag string to {@link TextFlags#TEXT_ACONFIGS_FLAGS}. * + * TODO(nona): Remove this class. * @hide */ public class ClientFlags { - /** - * @see Flags#fixMisalignedContextMenu() - */ - public static boolean fixMisalignedContextMenu() { - return TextFlags.isFeatureEnabled(Flags.FLAG_FIX_MISALIGNED_CONTEXT_MENU); - } } diff --git a/core/java/android/text/TextFlags.java b/core/java/android/text/TextFlags.java index 076721f629ed..f69a333ff81f 100644 --- a/core/java/android/text/TextFlags.java +++ b/core/java/android/text/TextFlags.java @@ -19,11 +19,10 @@ package android.text; import android.annotation.NonNull; import android.app.AppGlobals; -import com.android.text.flags.Flags; - /** * Flags in the "text" namespace. * + * TODO(nona): Remove this class. * @hide */ public final class TextFlags { @@ -55,7 +54,6 @@ public final class TextFlags { * List of text flags to be transferred to the application process. */ public static final String[] TEXT_ACONFIGS_FLAGS = { - Flags.FLAG_FIX_MISALIGNED_CONTEXT_MENU, }; /** @@ -64,7 +62,6 @@ public final class TextFlags { * The order must be the same to the TEXT_ACONFIG_FLAGS. */ public static final boolean[] TEXT_ACONFIG_DEFAULT_VALUE = { - Flags.fixMisalignedContextMenu(), }; /** diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig index 3846972a12e8..c83285a5c889 100644 --- a/core/java/android/text/flags/flags.aconfig +++ b/core/java/android/text/flags/flags.aconfig @@ -84,16 +84,6 @@ flag { } flag { - name: "fix_misaligned_context_menu" - namespace: "text" - description: "Fix the context menu misalignment and incosistent icon size." - bug: "332542108" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { name: "missing_getter_apis" namespace: "text" description: "Fix the lint warning about missing getters." diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java index 6b4340a02edc..15a4715bd059 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -38,6 +38,7 @@ import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST; import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; +import static android.window.flags.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS; import android.annotation.NonNull; import android.annotation.Nullable; @@ -55,7 +56,6 @@ import android.view.WindowInsets.Type.InsetsType; import android.view.WindowManager.LayoutParams.SoftInputModeFlags; import com.android.internal.annotations.VisibleForTesting; -import com.android.window.flags.Flags; import java.io.PrintWriter; import java.util.Objects; @@ -146,7 +146,7 @@ public class InsetsState implements Parcelable { forceConsumingTypes |= type; } - if (Flags.enableCaptionCompatInsetForceConsumptionAlways() + if (ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS.isEnabled() && (flags & FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR) != 0) { forceConsumingOpaqueCaptionBar = true; } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 9ff503171a3f..33e79059c7e5 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -125,11 +125,11 @@ import static android.view.flags.Flags.toolkitMetricsForFrameRateDecision; import static android.view.flags.Flags.toolkitSetFrameRateReadOnly; import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER; import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER; +import static android.window.flags.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION; import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; import static com.android.text.flags.Flags.disableHandwritingInitiatorForIme; import static com.android.window.flags.Flags.enableBufferTransformHintFromDisplay; -import static com.android.window.flags.Flags.enableCaptionCompatInsetForceConsumption; import static com.android.window.flags.Flags.insetsControlChangedItem; import static com.android.window.flags.Flags.insetsControlSeq; import static com.android.window.flags.Flags.setScPropertiesInClient; @@ -3214,10 +3214,10 @@ public final class ViewRootImpl implements ViewParent, typesToShow |= Type.navigationBars(); } if (captionIsHiddenByFlags && !captionWasHiddenByFlags - && enableCaptionCompatInsetForceConsumption()) { + && ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION.isEnabled()) { typesToHide |= Type.captionBar(); } else if (!captionIsHiddenByFlags && captionWasHiddenByFlags - && enableCaptionCompatInsetForceConsumption()) { + && ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION.isEnabled()) { typesToShow |= Type.captionBar(); } if (typesToHide != 0) { diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index e1154ca0701c..06820cd4c2ce 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -1003,6 +1003,55 @@ public class RemoteViews implements Parcelable, Filter { public int getActionTag() { return SET_EMPTY_VIEW_ACTION_TAG; } + + @Override + public boolean canWriteToProto() { + return true; + } + + @Override + public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) { + final long token = out.start(RemoteViewsProto.Action.SET_EMPTY_VIEW_ACTION); + out.write(RemoteViewsProto.SetEmptyViewAction.VIEW_ID, + appResources.getResourceName(mViewId)); + out.write(RemoteViewsProto.SetEmptyViewAction.EMPTY_VIEW_ID, + appResources.getResourceName(mViewId)); + out.end(token); + } + + public static PendingResources<Action> createFromProto(ProtoInputStream in) + throws Exception { + final LongSparseArray<Object> values = new LongSparseArray<>(); + + final long token = in.start(RemoteViewsProto.Action.SET_EMPTY_VIEW_ACTION); + while (in.nextField() != NO_MORE_FIELDS) { + switch (in.getFieldNumber()) { + case (int) RemoteViewsProto.SetEmptyViewAction.VIEW_ID: + values.put(RemoteViewsProto.SetEmptyViewAction.VIEW_ID, + in.readString(RemoteViewsProto.SetEmptyViewAction.VIEW_ID)); + break; + case (int) RemoteViewsProto.SetEmptyViewAction.EMPTY_VIEW_ID: + values.put(RemoteViewsProto.SetEmptyViewAction.EMPTY_VIEW_ID, + in.readString(RemoteViewsProto.SetEmptyViewAction.EMPTY_VIEW_ID)); + break; + default: + Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" + + ProtoUtils.currentFieldToString(in)); + } + } + in.end(token); + + checkContainsKeys(values, new long[]{RemoteViewsProto.SetEmptyViewAction.VIEW_ID, + RemoteViewsProto.SetEmptyViewAction.EMPTY_VIEW_ID}); + + return (context, resources, rootData, depth) -> { + int viewId = getAsIdentifier(resources, values, + RemoteViewsProto.SetEmptyViewAction.VIEW_ID); + int emptyViewId = getAsIdentifier(resources, values, + RemoteViewsProto.SetEmptyViewAction.EMPTY_VIEW_ID); + return new SetEmptyView(viewId, emptyViewId); + }; + } } private static class SetPendingIntentTemplate extends Action { @@ -1243,6 +1292,68 @@ public class RemoteViews implements Parcelable, Filter { mItems.visitUris(visitor); } + + @Override + public boolean canWriteToProto() { + // Skip actions that do not contain items (intent only actions) + return mItems != null; + } + + @Override + public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) { + if (mItems == null) return; + final long token = out.start( + RemoteViewsProto.Action.SET_REMOTE_COLLECTION_ITEM_LIST_ADAPTER_ACTION); + out.write(RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.VIEW_ID, + appResources.getResourceName(mViewId)); + final long itemsToken = out.start( + RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.ITEMS); + mItems.writeToProto(context, out, /* attached= */ true); + out.end(itemsToken); + out.end(token); + } + } + + private PendingResources<Action> createSetRemoteCollectionItemListAdapterActionFromProto( + ProtoInputStream in) throws Exception { + final LongSparseArray<Object> values = new LongSparseArray<>(); + + final long token = in.start( + RemoteViewsProto.Action.SET_REMOTE_COLLECTION_ITEM_LIST_ADAPTER_ACTION); + while (in.nextField() != NO_MORE_FIELDS) { + switch (in.getFieldNumber()) { + case (int) RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.VIEW_ID: + values.put(RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.VIEW_ID, + in.readString( + RemoteViewsProto + .SetRemoteCollectionItemListAdapterAction.VIEW_ID)); + break; + case (int) RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.ITEMS: + final long itemsToken = in.start( + RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.ITEMS); + values.put(RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.ITEMS, + RemoteCollectionItems.createFromProto(in)); + in.end(itemsToken); + break; + default: + Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" + + ProtoUtils.currentFieldToString(in)); + } + } + in.end(token); + + checkContainsKeys(values, + new long[]{RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.VIEW_ID, + RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.ITEMS}); + + return (context, resources, rootData, depth) -> { + int viewId = getAsIdentifier(resources, values, + RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.VIEW_ID); + return new SetRemoteCollectionItemListAdapterAction(viewId, + ((PendingResources<RemoteCollectionItems>) values.get( + RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.ITEMS)) + .create(context, resources, rootData, depth)); + }; } /** @@ -2036,6 +2147,68 @@ public class RemoteViews implements Parcelable, Filter { public int getActionTag() { return SET_DRAWABLE_TINT_TAG; } + + @Override + public boolean canWriteToProto() { + return true; + } + + @Override + public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) { + final long token = out.start(RemoteViewsProto.Action.SET_DRAWABLE_TINT_ACTION); + out.write(RemoteViewsProto.SetDrawableTintAction.VIEW_ID, + appResources.getResourceName(mViewId)); + out.write(RemoteViewsProto.SetDrawableTintAction.COLOR_FILTER, mColorFilter); + out.write(RemoteViewsProto.SetDrawableTintAction.FILTER_MODE, + PorterDuff.modeToInt(mFilterMode)); + out.write(RemoteViewsProto.SetDrawableTintAction.TARGET_BACKGROUND, mTargetBackground); + out.end(token); + } + + public static PendingResources<Action> createFromProto(ProtoInputStream in) + throws Exception { + final LongSparseArray<Object> values = new LongSparseArray<>(); + + final long token = in.start(RemoteViewsProto.Action.SET_DRAWABLE_TINT_ACTION); + while (in.nextField() != NO_MORE_FIELDS) { + switch (in.getFieldNumber()) { + case (int) RemoteViewsProto.SetDrawableTintAction.VIEW_ID: + values.put(RemoteViewsProto.SetDrawableTintAction.VIEW_ID, + in.readString(RemoteViewsProto.SetDrawableTintAction.VIEW_ID)); + break; + case (int) RemoteViewsProto.SetDrawableTintAction.TARGET_BACKGROUND: + values.put(RemoteViewsProto.SetDrawableTintAction.TARGET_BACKGROUND, + in.readBoolean( + RemoteViewsProto.SetDrawableTintAction.TARGET_BACKGROUND)); + break; + case (int) RemoteViewsProto.SetDrawableTintAction.COLOR_FILTER: + values.put(RemoteViewsProto.SetDrawableTintAction.COLOR_FILTER, + in.readInt(RemoteViewsProto.SetDrawableTintAction.COLOR_FILTER)); + break; + case (int) RemoteViewsProto.SetDrawableTintAction.FILTER_MODE: + values.put(RemoteViewsProto.SetDrawableTintAction.FILTER_MODE, + PorterDuff.intToMode(in.readInt( + RemoteViewsProto.SetDrawableTintAction.FILTER_MODE))); + break; + default: + Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" + + ProtoUtils.currentFieldToString(in)); + } + } + in.end(token); + + checkContainsKeys(values, new long[]{RemoteViewsProto.SetDrawableTintAction.VIEW_ID}); + + return (context, resources, rootData, depth) -> { + int viewId = getAsIdentifier(resources, values, + RemoteViewsProto.SetDrawableTintAction.VIEW_ID); + return new SetDrawableTint(viewId, (boolean) values.get( + RemoteViewsProto.SetDrawableTintAction.TARGET_BACKGROUND, false), + (int) values.get(RemoteViewsProto.SetDrawableTintAction.COLOR_FILTER, 0), + (PorterDuff.Mode) values.get( + RemoteViewsProto.SetDrawableTintAction.FILTER_MODE)); + }; + } } /** @@ -2047,7 +2220,7 @@ public class RemoteViews implements Parcelable, Filter { * target {@link View#getBackground()}. * <p> */ - private class SetRippleDrawableColor extends Action { + private static class SetRippleDrawableColor extends Action { ColorStateList mColorStateList; SetRippleDrawableColor(@IdRes int id, ColorStateList colorStateList) { @@ -2082,6 +2255,58 @@ public class RemoteViews implements Parcelable, Filter { public int getActionTag() { return SET_RIPPLE_DRAWABLE_COLOR_TAG; } + + @Override + public boolean canWriteToProto() { + return true; + } + + @Override + public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) { + final long token = out.start(RemoteViewsProto.Action.SET_RIPPLE_DRAWABLE_COLOR_ACTION); + out.write(RemoteViewsProto.SetRippleDrawableColorAction.VIEW_ID, + appResources.getResourceName(mViewId)); + writeColorStateListToProto(out, mColorStateList, + RemoteViewsProto.SetRippleDrawableColorAction.COLOR_STATE_LIST); + out.end(token); + } + + public static PendingResources<Action> createFromProto(ProtoInputStream in) + throws Exception { + final LongSparseArray<Object> values = new LongSparseArray<>(); + + final long token = in.start(RemoteViewsProto.Action.SET_RIPPLE_DRAWABLE_COLOR_ACTION); + while (in.nextField() != NO_MORE_FIELDS) { + switch (in.getFieldNumber()) { + case (int) RemoteViewsProto.SetRippleDrawableColorAction.VIEW_ID: + values.put(RemoteViewsProto.SetRippleDrawableColorAction.VIEW_ID, + in.readString( + RemoteViewsProto.SetRippleDrawableColorAction.VIEW_ID)); + break; + case (int) RemoteViewsProto.SetRippleDrawableColorAction.COLOR_STATE_LIST: + values.put(RemoteViewsProto.SetRippleDrawableColorAction.COLOR_STATE_LIST, + createColorStateListFromProto(in, + RemoteViewsProto + .SetRippleDrawableColorAction.COLOR_STATE_LIST)); + break; + default: + Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" + + ProtoUtils.currentFieldToString(in)); + } + } + in.end(token); + + checkContainsKeys(values, + new long[]{RemoteViewsProto.SetRippleDrawableColorAction.VIEW_ID, + RemoteViewsProto.SetRippleDrawableColorAction.COLOR_STATE_LIST}); + + return (context, resources, rootData, depth) -> { + int viewId = getAsIdentifier(resources, values, + RemoteViewsProto.SetRippleDrawableColorAction.VIEW_ID); + return new SetRippleDrawableColor(viewId, (ColorStateList) values.get( + RemoteViewsProto.SetRippleDrawableColorAction.COLOR_STATE_LIST)); + }; + } } /** @@ -2987,6 +3212,82 @@ public class RemoteViews implements Parcelable, Filter { public int getActionTag() { return RESOURCE_REFLECTION_ACTION_TAG; } + + @Override + public boolean canWriteToProto() { + return true; + } + + @Override + public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) { + final long token = out.start(RemoteViewsProto.Action.RESOURCE_REFLECTION_ACTION); + out.write(RemoteViewsProto.ResourceReflectionAction.VIEW_ID, + appResources.getResourceName(mViewId)); + out.write(RemoteViewsProto.ResourceReflectionAction.METHOD_NAME, mMethodName); + out.write(RemoteViewsProto.ResourceReflectionAction.PARAMETER_TYPE, mType); + out.write(RemoteViewsProto.ResourceReflectionAction.RESOURCE_TYPE, mResourceType); + if (mResId != 0) { + out.write(RemoteViewsProto.ResourceReflectionAction.RES_ID, + appResources.getResourceName(mResId)); + } + out.end(token); + } + + public static PendingResources<Action> createFromProto(ProtoInputStream in) + throws Exception { + final LongSparseArray<Object> values = new LongSparseArray<>(); + + final long token = in.start(RemoteViewsProto.Action.RESOURCE_REFLECTION_ACTION); + while (in.nextField() != NO_MORE_FIELDS) { + switch (in.getFieldNumber()) { + case (int) RemoteViewsProto.ResourceReflectionAction.VIEW_ID: + values.put(RemoteViewsProto.ResourceReflectionAction.VIEW_ID, + in.readString(RemoteViewsProto.ResourceReflectionAction.VIEW_ID)); + break; + case (int) RemoteViewsProto.ResourceReflectionAction.METHOD_NAME: + values.put(RemoteViewsProto.ResourceReflectionAction.METHOD_NAME, + in.readString( + RemoteViewsProto.ResourceReflectionAction.METHOD_NAME)); + break; + case (int) RemoteViewsProto.ResourceReflectionAction.RESOURCE_TYPE: + values.put(RemoteViewsProto.ResourceReflectionAction.RESOURCE_TYPE, + in.readInt( + RemoteViewsProto.ResourceReflectionAction.RESOURCE_TYPE)); + break; + case (int) RemoteViewsProto.ResourceReflectionAction.RES_ID: + values.put(RemoteViewsProto.ResourceReflectionAction.RES_ID, + in.readString(RemoteViewsProto.ResourceReflectionAction.RES_ID)); + break; + case (int) RemoteViewsProto.ResourceReflectionAction.PARAMETER_TYPE: + values.put(RemoteViewsProto.ResourceReflectionAction.PARAMETER_TYPE, + in.readInt( + RemoteViewsProto.ResourceReflectionAction.PARAMETER_TYPE)); + break; + default: + Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" + + ProtoUtils.currentFieldToString(in)); + } + } + in.end(token); + + checkContainsKeys(values, new long[]{RemoteViewsProto.ResourceReflectionAction.VIEW_ID, + RemoteViewsProto.ResourceReflectionAction.METHOD_NAME, + RemoteViewsProto.ResourceReflectionAction.PARAMETER_TYPE}); + + return (context, resources, rootData, depth) -> { + int viewId = getAsIdentifier(resources, values, + RemoteViewsProto.ResourceReflectionAction.VIEW_ID); + + int resId = (values.indexOfKey(RemoteViewsProto.ResourceReflectionAction.RES_ID) + >= 0) ? getAsIdentifier(resources, values, + RemoteViewsProto.ResourceReflectionAction.RES_ID) : 0; + return new ResourceReflectionAction(viewId, + (String) values.get(RemoteViewsProto.ResourceReflectionAction.METHOD_NAME), + (int) values.get(RemoteViewsProto.ResourceReflectionAction.PARAMETER_TYPE), + (int) values.get(RemoteViewsProto.ResourceReflectionAction.RESOURCE_TYPE, + 0), resId); + }; + } } private static final class AttributeReflectionAction extends BaseReflectionAction { @@ -4593,6 +4894,61 @@ public class RemoteViews implements Parcelable, Filter { public int getActionTag() { return SET_INT_TAG_TAG; } + + @Override + public boolean canWriteToProto() { + return true; + } + + @Override + public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) { + final long token = out.start(RemoteViewsProto.Action.SET_INT_TAG_ACTION); + out.write(RemoteViewsProto.SetIntTagAction.VIEW_ID, + appResources.getResourceName(mViewId)); + out.write(RemoteViewsProto.SetIntTagAction.KEY, + appResources.getResourceName(mKey)); // rebase + out.write(RemoteViewsProto.SetIntTagAction.TAG, mTag); + out.end(token); + } + + public static PendingResources<Action> createFromProto(ProtoInputStream in) + throws Exception { + final LongSparseArray<Object> values = new LongSparseArray<>(); + + final long token = in.start(RemoteViewsProto.Action.SET_INT_TAG_ACTION); + while (in.nextField() != NO_MORE_FIELDS) { + switch (in.getFieldNumber()) { + case (int) RemoteViewsProto.SetIntTagAction.VIEW_ID: + values.put(RemoteViewsProto.SetIntTagAction.VIEW_ID, + in.readString(RemoteViewsProto.SetIntTagAction.VIEW_ID)); + break; + case (int) RemoteViewsProto.SetIntTagAction.KEY: + values.put(RemoteViewsProto.SetIntTagAction.KEY, + in.readString(RemoteViewsProto.SetIntTagAction.KEY)); + break; + case (int) RemoteViewsProto.SetIntTagAction.TAG: + values.put(RemoteViewsProto.SetIntTagAction.TAG, + in.readInt(RemoteViewsProto.SetIntTagAction.TAG)); + break; + default: + Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" + + ProtoUtils.currentFieldToString(in)); + } + } + in.end(token); + + checkContainsKeys(values, new long[]{RemoteViewsProto.SetIntTagAction.VIEW_ID, + RemoteViewsProto.SetIntTagAction.KEY}); + + return (context, resources, rootData, depth) -> { + int viewId = getAsIdentifier(resources, values, + RemoteViewsProto.SetIntTagAction.VIEW_ID); + int keyId = getAsIdentifier(resources, values, + RemoteViewsProto.SetIntTagAction.KEY); + return new SetIntTagAction(viewId, keyId, + (int) values.get(RemoteViewsProto.SetIntTagAction.TAG, 0)); + }; + } } private static class SetCompoundButtonCheckedAction extends Action { @@ -4643,6 +4999,56 @@ public class RemoteViews implements Parcelable, Filter { public int getActionTag() { return SET_COMPOUND_BUTTON_CHECKED_TAG; } + + @Override + public boolean canWriteToProto() { + return true; + } + + @Override + public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) { + final long token = out.start( + RemoteViewsProto.Action.SET_COMPOUND_BUTTON_CHECKED_ACTION); + out.write(RemoteViewsProto.SetCompoundButtonCheckedAction.VIEW_ID, + appResources.getResourceName(mViewId)); + out.write(RemoteViewsProto.SetCompoundButtonCheckedAction.CHECKED, mChecked); + out.end(token); + } + + public static PendingResources<Action> createFromProto(ProtoInputStream in) + throws Exception { + final LongSparseArray<Object> values = new LongSparseArray<>(); + + final long token = in.start(RemoteViewsProto.Action.SET_COMPOUND_BUTTON_CHECKED_ACTION); + while (in.nextField() != NO_MORE_FIELDS) { + switch (in.getFieldNumber()) { + case (int) RemoteViewsProto.SetCompoundButtonCheckedAction.VIEW_ID: + values.put(RemoteViewsProto.SetCompoundButtonCheckedAction.VIEW_ID, + in.readString( + RemoteViewsProto.SetCompoundButtonCheckedAction.VIEW_ID)); + break; + case (int) RemoteViewsProto.SetCompoundButtonCheckedAction.CHECKED: + values.put(RemoteViewsProto.SetCompoundButtonCheckedAction.CHECKED, + in.readBoolean( + RemoteViewsProto.SetCompoundButtonCheckedAction.CHECKED)); + break; + default: + Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" + + ProtoUtils.currentFieldToString(in)); + } + } + in.end(token); + + checkContainsKeys(values, + new long[]{RemoteViewsProto.SetCompoundButtonCheckedAction.VIEW_ID}); + + return (context, resources, rootData, depth) -> { + int viewId = getAsIdentifier(resources, values, + RemoteViewsProto.SetCompoundButtonCheckedAction.VIEW_ID); + return new SetCompoundButtonCheckedAction(viewId, (boolean) values.get( + RemoteViewsProto.SetCompoundButtonCheckedAction.CHECKED, false)); + }; + } } private static class SetRadioGroupCheckedAction extends Action { @@ -4707,6 +5113,61 @@ public class RemoteViews implements Parcelable, Filter { public int getActionTag() { return SET_RADIO_GROUP_CHECKED; } + + @Override + public boolean canWriteToProto() { + return true; + } + + @Override + public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) { + final long token = out.start(RemoteViewsProto.Action.SET_RADIO_GROUP_CHECKED_ACTION); + out.write(RemoteViewsProto.SetRadioGroupCheckedAction.VIEW_ID, + appResources.getResourceName(mViewId)); + if (mCheckedId != -1) { + out.write(RemoteViewsProto.SetRadioGroupCheckedAction.CHECKED_ID, + appResources.getResourceName(mCheckedId)); + } + out.end(token); + } + + public static PendingResources<Action> createFromProto(ProtoInputStream in) + throws Exception { + final LongSparseArray<Object> values = new LongSparseArray<>(); + + final long token = in.start(RemoteViewsProto.Action.SET_RADIO_GROUP_CHECKED_ACTION); + while (in.nextField() != NO_MORE_FIELDS) { + switch (in.getFieldNumber()) { + case (int) RemoteViewsProto.SetRadioGroupCheckedAction.VIEW_ID: + values.put(RemoteViewsProto.SetRadioGroupCheckedAction.VIEW_ID, + in.readString(RemoteViewsProto.SetRadioGroupCheckedAction.VIEW_ID)); + break; + case (int) RemoteViewsProto.SetRadioGroupCheckedAction.CHECKED_ID: + values.put(RemoteViewsProto.SetRadioGroupCheckedAction.CHECKED_ID, + in.readString( + RemoteViewsProto.SetRadioGroupCheckedAction.CHECKED_ID)); + break; + default: + Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" + + ProtoUtils.currentFieldToString(in)); + } + } + in.end(token); + + checkContainsKeys(values, + new long[]{RemoteViewsProto.SetRadioGroupCheckedAction.VIEW_ID}); + + return (context, resources, rootData, depth) -> { + int viewId = getAsIdentifier(resources, values, + RemoteViewsProto.SetRadioGroupCheckedAction.VIEW_ID); + + int checkedId = (values.indexOfKey( + RemoteViewsProto.SetRadioGroupCheckedAction.CHECKED_ID) >= 0) + ? getAsIdentifier(resources, values, + RemoteViewsProto.SetRadioGroupCheckedAction.CHECKED_ID) : -1; + return new SetRadioGroupCheckedAction(viewId, checkedId); + }; + } } private static class SetViewOutlinePreferredRadiusAction extends Action { @@ -8450,6 +8911,7 @@ public class RemoteViews implements Parcelable, Filter { public static PendingResources<RemoteCollectionItems> createFromProto(ProtoInputStream in) throws Exception { final LongSparseArray<Object> values = new LongSparseArray<>(); + values.put(RemoteViewsProto.RemoteCollectionItems.IDS, new ArrayList<Long>()); values.put(RemoteViewsProto.RemoteCollectionItems.VIEWS, new ArrayList<PendingResources<RemoteViews>>()); @@ -9207,6 +9669,22 @@ public class RemoteViews implements Parcelable, Filter { return ReflectionAction.createFromProto(in); case (int) RemoteViewsProto.Action.REMOVE_FROM_PARENT_ACTION: return RemoveFromParentAction.createFromProto(in); + case (int) RemoteViewsProto.Action.RESOURCE_REFLECTION_ACTION: + return ResourceReflectionAction.createFromProto(in); + case (int) RemoteViewsProto.Action.SET_COMPOUND_BUTTON_CHECKED_ACTION: + return SetCompoundButtonCheckedAction.createFromProto(in); + case (int) RemoteViewsProto.Action.SET_DRAWABLE_TINT_ACTION: + return SetDrawableTint.createFromProto(in); + case (int) RemoteViewsProto.Action.SET_EMPTY_VIEW_ACTION: + return SetEmptyView.createFromProto(in); + case (int) RemoteViewsProto.Action.SET_INT_TAG_ACTION: + return SetIntTagAction.createFromProto(in); + case (int) RemoteViewsProto.Action.SET_RADIO_GROUP_CHECKED_ACTION: + return SetRadioGroupCheckedAction.createFromProto(in); + case (int) RemoteViewsProto.Action.SET_REMOTE_COLLECTION_ITEM_LIST_ADAPTER_ACTION: + return rv.createSetRemoteCollectionItemListAdapterActionFromProto(in); + case (int) RemoteViewsProto.Action.SET_RIPPLE_DRAWABLE_COLOR_ACTION: + return SetRippleDrawableColor.createFromProto(in); default: throw new RuntimeException("Unhandled field while reading Action proto!\n" + ProtoUtils.currentFieldToString(in)); diff --git a/core/java/android/window/flags/DesktopModeFlags.java b/core/java/android/window/flags/DesktopModeFlags.java index 5c53d66e49fe..701b6be06e72 100644 --- a/core/java/android/window/flags/DesktopModeFlags.java +++ b/core/java/android/window/flags/DesktopModeFlags.java @@ -17,7 +17,9 @@ package android.window.flags; import android.annotation.Nullable; -import android.content.Context; +import android.app.ActivityThread; +import android.app.Application; +import android.content.ContentResolver; import android.provider.Settings; import android.util.Log; @@ -39,9 +41,13 @@ import java.util.function.Supplier; */ public enum DesktopModeFlags { // All desktop mode related flags to be overridden by developer option toggle will be added here - DESKTOP_WINDOWING_MODE( + ENABLE_DESKTOP_WINDOWING_MODE( Flags::enableDesktopWindowingMode, /* shouldOverrideByDevOption= */ true), - DYNAMIC_INITIAL_BOUNDS(Flags::enableWindowingDynamicInitialBounds, false); + ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS(Flags::enableWindowingDynamicInitialBounds, false), + ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION( + Flags::enableCaptionCompatInsetForceConsumption, true), + ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS( + Flags::enableCaptionCompatInsetForceConsumptionAlways, true); private static final String TAG = "DesktopModeFlagsUtil"; // Function called to obtain aconfig flag value. @@ -62,14 +68,15 @@ public enum DesktopModeFlags { * Determines state of flag based on the actual flag and desktop mode developer option * overrides. */ - public boolean isEnabled(Context context) { + public boolean isEnabled() { + Application application = ActivityThread.currentApplication(); if (!Flags.showDesktopWindowingDevOption() || !mShouldOverrideByDevOption - || context.getContentResolver() == null) { + || application == null) { return mFlagFunction.get(); } else { boolean shouldToggleBeEnabledByDefault = Flags.enableDesktopWindowingMode(); - return switch (getToggleOverride(context)) { + return switch (getToggleOverride(application.getContentResolver())) { case OVERRIDE_UNSET -> mFlagFunction.get(); // When toggle override matches its default state, don't override flags. This // helps users reset their feature overrides. @@ -79,14 +86,14 @@ public enum DesktopModeFlags { } } - private ToggleOverride getToggleOverride(Context context) { + private ToggleOverride getToggleOverride(ContentResolver contentResolver) { // If cached, return it if (sCachedToggleOverride != null) { return sCachedToggleOverride; } // Otherwise, fetch and cache it - ToggleOverride override = getToggleOverrideFromSystem(context); + ToggleOverride override = getToggleOverrideFromSystem(contentResolver); sCachedToggleOverride = override; Log.d(TAG, "Toggle override initialized to: " + override); return override; @@ -95,9 +102,9 @@ public enum DesktopModeFlags { /** * Returns {@link ToggleOverride} from Settings.Global set by toggle. */ - private ToggleOverride getToggleOverrideFromSystem(Context context) { + private ToggleOverride getToggleOverrideFromSystem(ContentResolver contentResolver) { int settingValue = Settings.Global.getInt( - context.getContentResolver(), + contentResolver, Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, ToggleOverride.OVERRIDE_UNSET.getSetting() ); diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig index 13648de5b28e..9ae3fc1fa3f0 100644 --- a/core/java/android/window/flags/windowing_sdk.aconfig +++ b/core/java/android/window/flags/windowing_sdk.aconfig @@ -122,3 +122,10 @@ flag { description: "Requires apps to opt-in to overlay pass through touches and provide APIs to opt-in" bug: "358129114" } + +flag { + namespace: "windowing_sdk" + name: "wlinfo_oncreate" + description: "Makes WindowLayoutInfo accessible without racing in the Activity#onCreate()" + bug: "337820752" +} diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java index 4708be8108c2..84dfc497dc84 100644 --- a/core/java/com/android/internal/policy/DecorView.java +++ b/core/java/com/android/internal/policy/DecorView.java @@ -38,6 +38,8 @@ import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATIO import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; import static android.view.flags.Flags.customizableWindowHeaders; +import static android.window.flags.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION; +import static android.window.flags.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS; import static com.android.internal.policy.PhoneWindow.FEATURE_OPTIONS_PANEL; @@ -114,7 +116,6 @@ import com.android.internal.view.menu.MenuHelper; import com.android.internal.widget.ActionBarContextView; import com.android.internal.widget.BackgroundFallback; import com.android.internal.widget.floatingtoolbar.FloatingToolbar; -import com.android.window.flags.Flags; import java.util.List; import java.util.concurrent.Executor; @@ -1217,14 +1218,15 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind final boolean hideCaptionBar = fullscreen || (requestedVisibleTypes & WindowInsets.Type.captionBar()) == 0; - final boolean consumingCaptionBar = Flags.enableCaptionCompatInsetForceConsumption() - && ((mLastForceConsumingTypes & WindowInsets.Type.captionBar()) != 0 + final boolean consumingCaptionBar = + ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION.isEnabled() + && ((mLastForceConsumingTypes & WindowInsets.Type.captionBar()) != 0 && hideCaptionBar); final boolean isOpaqueCaptionBar = customizableWindowHeaders() && (appearance & APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND) == 0; final boolean consumingOpaqueCaptionBar = - Flags.enableCaptionCompatInsetForceConsumptionAlways() + ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS.isEnabled() && mLastForceConsumingOpaqueCaptionBar && isOpaqueCaptionBar; diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java index e440dc9053fd..fbc058cc0330 100644 --- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java +++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java @@ -210,6 +210,8 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto DataSourceParams .PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP) .build(); + // NOTE: Registering that datasource is an async operation, so there may be no data traced + // for some messages logged right after the construction of this class. mDataSource.register(params); this.mViewerConfigInputStreamProvider = viewerConfigInputStreamProvider; this.mViewerConfigReader = viewerConfigReader; @@ -223,17 +225,17 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto "ServiceManager returned a null ProtoLog Configuration Service"); try { - var args = new ProtoLogConfigurationService.RegisterClientArgs(); + var args = new ProtoLogConfigurationServiceImpl.RegisterClientArgs(); if (viewerConfigFilePath != null) { args.setViewerConfigFile(viewerConfigFilePath); } final var groupArgs = Stream.of(groups) - .map(group -> new ProtoLogConfigurationService.RegisterClientArgs + .map(group -> new ProtoLogConfigurationServiceImpl.RegisterClientArgs .GroupConfig(group.name(), group.isLogToLogcat())) - .toArray( - ProtoLogConfigurationService.RegisterClientArgs.GroupConfig[]::new); + .toArray(ProtoLogConfigurationServiceImpl + .RegisterClientArgs.GroupConfig[]::new); args.setGroups(groupArgs); mProtoLogConfigurationService.registerClient(this, args); diff --git a/core/java/com/android/internal/protolog/ProtoLogConfigurationService.java b/core/java/com/android/internal/protolog/ProtoLogConfigurationService.java index 7031d694f09c..d65aaae7deaa 100644 --- a/core/java/com/android/internal/protolog/ProtoLogConfigurationService.java +++ b/core/java/com/android/internal/protolog/ProtoLogConfigurationService.java @@ -16,434 +16,32 @@ package com.android.internal.protolog; -import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.GROUPS; -import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.ID; -import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.NAME; -import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.TAG; -import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MESSAGES; -import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.GROUP_ID; -import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.LEVEL; -import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.LOCATION; -import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.MESSAGE; -import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.MESSAGE_ID; - import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.SystemService; -import android.content.Context; -import android.os.RemoteException; -import android.os.ResultReceiver; -import android.os.ShellCallback; -import android.tracing.perfetto.DataSourceParams; -import android.tracing.perfetto.InitArguments; -import android.tracing.perfetto.Producer; -import android.util.Log; -import android.util.proto.ProtoInputStream; -import android.util.proto.ProtoOutputStream; - -import com.android.internal.annotations.VisibleForTesting; - -import java.io.FileDescriptor; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.TreeMap; - -/** - * The ProtoLog service is responsible for orchestrating centralized actions of the protolog tracing - * system. Currently this service has the following roles: - * - Handle shell commands to toggle logging ProtoLog messages for specified groups to logcat. - * - Handle viewer config dumping (the mapping from message hash to message string) for all protolog - * clients. This is for two reasons: firstly, because client processes might be frozen so might - * not response to the request to dump their viewer config when the trace is stopped; secondly, - * multiple processes might be running the same code with the same viewer config, this centralized - * service ensures we don't dump the same viewer config multiple times across processes. - * <p> - * {@link com.android.internal.protolog.IProtoLogClient ProtoLog clients} register themselves to - * this service on initialization. - * <p> - * This service is intended to run on the system server, such that it never gets frozen. - */ -@SystemService(Context.PROTOLOG_CONFIGURATION_SERVICE) -public final class ProtoLogConfigurationService extends IProtoLogConfigurationService.Stub { - private static final String LOG_TAG = "ProtoLogConfigurationService"; - - private final ProtoLogDataSource mDataSource; - - /** - * Keeps track of how many of each viewer config file is currently registered. - * Use to keep track of which viewer config files are actively being used in tracing and might - * need to be dumped on flush. - */ - private final Map<String, Integer> mConfigFileCounts = new HashMap<>(); - /** - * Keeps track of the viewer config file of each client if available. - */ - private final Map<IProtoLogClient, String> mClientConfigFiles = new HashMap<>(); - - /** - * Keeps track of all the protolog groups that have been registered by clients and are still - * being actively traced. - */ - private final Set<String> mRegisteredGroups = new HashSet<>(); - /** - * Keeps track of all the clients that are actively tracing a given protolog group. - */ - private final Map<String, Set<IProtoLogClient>> mGroupToClients = new HashMap<>(); - - /** - * Keeps track of whether or not a given group should be logged to logcat. - * True when logging to logcat, false otherwise. - */ - private final Map<String, Boolean> mLogGroupToLogcatStatus = new TreeMap<>(); - - /** - * Keeps track of all the tracing instance ids that are actively running for ProtoLog. - */ - private final Set<Integer> mRunningInstances = new HashSet<>(); - - private final ViewerConfigFileTracer mViewerConfigFileTracer; - - public ProtoLogConfigurationService() { - this(ProtoLogDataSource::new, ProtoLogConfigurationService::dumpTransitionTraceConfig); - } - - @VisibleForTesting - public ProtoLogConfigurationService(@NonNull ProtoLogDataSourceBuilder dataSourceBuilder) { - this(dataSourceBuilder, ProtoLogConfigurationService::dumpTransitionTraceConfig); - } - - @VisibleForTesting - public ProtoLogConfigurationService(@NonNull ViewerConfigFileTracer tracer) { - this(ProtoLogDataSource::new, tracer); - } - - @VisibleForTesting - public ProtoLogConfigurationService( - @NonNull ProtoLogDataSourceBuilder dataSourceBuilder, - @NonNull ViewerConfigFileTracer tracer) { - mDataSource = dataSourceBuilder.build( - this::onTracingInstanceStart, - this::onTracingInstanceFlush, - this::onTracingInstanceStop - ); - - // Initialize the Perfetto producer and register the Perfetto ProtoLog datasource to be - // receive the lifecycle callbacks of the datasource and write the viewer configs if and - // when required to the datasource. - Producer.init(InitArguments.DEFAULTS); - final var params = new DataSourceParams.Builder() - .setBufferExhaustedPolicy(DataSourceParams.PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP) - .build(); - mDataSource.register(params); - - mViewerConfigFileTracer = tracer; - } - - public static class RegisterClientArgs extends IRegisterClientArgs.Stub { - /** - * The viewer config file to be registered for this client ProtoLog process. - */ - @Nullable - private String mViewerConfigFile = null; - /** - * The list of all groups that this client protolog process supports and might trace. - */ - @NonNull - private String[] mGroups = new String[0]; - /** - * The default logcat status of the ProtoLog client. True is logging to logcat, false - * otherwise. The indices should match the indices in {@link mGroups}. - */ - @NonNull - private boolean[] mLogcatStatus = new boolean[0]; - - public record GroupConfig(@NonNull String group, boolean logToLogcat) {} - - /** - * Specify groups to register with this client that will be used for protologging in this - * process. - * @param groups to register with this client. - * @return self - */ - public RegisterClientArgs setGroups(GroupConfig... groups) { - mGroups = new String[groups.length]; - mLogcatStatus = new boolean[groups.length]; - - for (int i = 0; i < groups.length; i++) { - mGroups[i] = groups[i].group; - mLogcatStatus[i] = groups[i].logToLogcat; - } - - return this; - } - - /** - * Set the viewer config file that the logs in this process are using. - * @param viewerConfigFile The file path of the viewer config. - * @return self - */ - public RegisterClientArgs setViewerConfigFile(@NonNull String viewerConfigFile) { - mViewerConfigFile = viewerConfigFile; - - return this; - } - - @Override - @NonNull - public String[] getGroups() { - return mGroups; - } - - @Override - @NonNull - public boolean[] getGroupsDefaultLogcatStatus() { - return mLogcatStatus; - } - - @Nullable - @Override - public String getViewerConfigFile() { - return mViewerConfigFile; - } - } - - @FunctionalInterface - public interface ViewerConfigFileTracer { - /** - * Write the viewer config data to the trace buffer. - * - * @param dataSource The target datasource to write the viewer config to. - * @param viewerConfigFilePath The path of the viewer config file which contains the data we - * want to write to the trace buffer. - * @throws FileNotFoundException if the viewerConfigFilePath is invalid. - */ - void trace(@NonNull ProtoLogDataSource dataSource, @NonNull String viewerConfigFilePath); - } - - @Override - public void registerClient(@NonNull IProtoLogClient client, @NonNull IRegisterClientArgs args) - throws RemoteException { - client.asBinder().linkToDeath(() -> onClientBinderDeath(client), /* flags */ 0); - - final String viewerConfigFile = args.getViewerConfigFile(); - if (viewerConfigFile != null) { - registerViewerConfigFile(client, viewerConfigFile); - } - - registerGroups(client, args.getGroups(), args.getGroupsDefaultLogcatStatus()); - } - - @Override - public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out, - @Nullable FileDescriptor err, @NonNull String[] args, @Nullable ShellCallback callback, - @NonNull ResultReceiver resultReceiver) throws RemoteException { - new ProtoLogCommandHandler(this) - .exec(this, in, out, err, args, callback, resultReceiver); - } +public interface ProtoLogConfigurationService extends IProtoLogConfigurationService { /** * Get the list of groups clients have registered to the protolog service. * @return The list of ProtoLog groups registered with this service. */ @NonNull - public String[] getGroups() { - return mRegisteredGroups.toArray(new String[0]); - } + String[] getGroups(); + + /** + * Check if a group is logging to logcat + * @param group The group we want to check for + * @return True iff we are logging this group to logcat. + */ + boolean isLoggingToLogcat(@NonNull String group); /** * Enable logging target groups to logcat. * @param groups we want to enable logging them to logcat for. */ - public void enableProtoLogToLogcat(String... groups) { - toggleProtoLogToLogcat(true, groups); - } + void enableProtoLogToLogcat(@NonNull String... groups); /** * Disable logging target groups to logcat. * @param groups we want to disable from being logged to logcat. */ - public void disableProtoLogToLogcat(String... groups) { - toggleProtoLogToLogcat(false, groups); - } - - /** - * Check if a group is logging to logcat - * @param group The group we want to check for - * @return True iff we are logging this group to logcat. - */ - public boolean isLoggingToLogcat(@NonNull String group) { - final Boolean isLoggingToLogcat = mLogGroupToLogcatStatus.get(group); - - if (isLoggingToLogcat == null) { - throw new RuntimeException( - "Trying to get logcat logging status of non-registered group " + group); - } - - return isLoggingToLogcat; - } - - private void registerViewerConfigFile( - @NonNull IProtoLogClient client, @NonNull String viewerConfigFile) { - final var count = mConfigFileCounts.getOrDefault(viewerConfigFile, 0); - mConfigFileCounts.put(viewerConfigFile, count + 1); - mClientConfigFiles.put(client, viewerConfigFile); - } - - private void registerGroups(@NonNull IProtoLogClient client, @NonNull String[] groups, - @NonNull boolean[] logcatStatuses) throws RemoteException { - if (groups.length != logcatStatuses.length) { - throw new RuntimeException( - "Expected groups and logcatStatuses to have the same length, " - + "but groups has length " + groups.length - + " and logcatStatuses has length " + logcatStatuses.length); - } - - for (int i = 0; i < groups.length; i++) { - String group = groups[i]; - boolean logcatStatus = logcatStatuses[i]; - - mRegisteredGroups.add(group); - - mGroupToClients.putIfAbsent(group, new HashSet<>()); - mGroupToClients.get(group).add(client); - - if (!mLogGroupToLogcatStatus.containsKey(group)) { - mLogGroupToLogcatStatus.put(group, logcatStatus); - } - - boolean requestedLogToLogcat = mLogGroupToLogcatStatus.get(group); - if (requestedLogToLogcat != logcatStatus) { - client.toggleLogcat(requestedLogToLogcat, new String[] { group }); - } - } - } - - private void toggleProtoLogToLogcat(boolean enabled, @NonNull String[] groups) { - final var clientToGroups = new HashMap<IProtoLogClient, Set<String>>(); - - for (String group : groups) { - final var clients = mGroupToClients.get(group); - - if (clients == null) { - // No clients associated to this group - Log.w(LOG_TAG, "Attempting to toggle log to logcat for group " + group - + " with no registered clients."); - continue; - } - - for (IProtoLogClient client : clients) { - clientToGroups.putIfAbsent(client, new HashSet<>()); - clientToGroups.get(client).add(group); - } - } - - for (IProtoLogClient client : clientToGroups.keySet()) { - try { - client.toggleLogcat(enabled, clientToGroups.get(client).toArray(new String[0])); - } catch (RemoteException e) { - throw new RuntimeException( - "Failed to toggle logcat status for groups on client", e); - } - } - - for (String group : groups) { - mLogGroupToLogcatStatus.put(group, enabled); - } - } - - private void onTracingInstanceStart(int instanceIdx, ProtoLogDataSource.ProtoLogConfig config) { - mRunningInstances.add(instanceIdx); - } - - private void onTracingInstanceFlush() { - for (String fileName : mConfigFileCounts.keySet()) { - mViewerConfigFileTracer.trace(mDataSource, fileName); - } - } - - private void onTracingInstanceStop(int instanceIdx, ProtoLogDataSource.ProtoLogConfig config) { - mRunningInstances.remove(instanceIdx); - } - - private static void dumpTransitionTraceConfig(@NonNull ProtoLogDataSource dataSource, - @NonNull String viewerConfigFilePath) { - Utils.dumpViewerConfig(dataSource, () -> { - try { - return new ProtoInputStream(new FileInputStream(viewerConfigFilePath)); - } catch (FileNotFoundException e) { - throw new RuntimeException( - "Failed to load viewer config file " + viewerConfigFilePath, e); - } - }); - } - - private void onClientBinderDeath(@NonNull IProtoLogClient client) { - // Dump the tracing config now if no other client is going to dump the same config file. - String configFile = mClientConfigFiles.get(client); - if (configFile != null) { - final var newCount = mConfigFileCounts.get(configFile) - 1; - mConfigFileCounts.put(configFile, newCount); - boolean lastProcessWithViewerConfig = newCount == 0; - if (lastProcessWithViewerConfig) { - mViewerConfigFileTracer.trace(mDataSource, configFile); - } - } - } - - private static void writeViewerConfigGroup( - @NonNull ProtoInputStream pis, @NonNull ProtoOutputStream os) throws IOException { - final long inGroupToken = pis.start(GROUPS); - final long outGroupToken = os.start(GROUPS); - - while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) { - switch (pis.getFieldNumber()) { - case (int) ID -> { - int id = pis.readInt(ID); - os.write(ID, id); - } - case (int) NAME -> { - String name = pis.readString(NAME); - os.write(NAME, name); - } - case (int) TAG -> { - String tag = pis.readString(TAG); - os.write(TAG, tag); - } - default -> - throw new RuntimeException( - "Unexpected field id " + pis.getFieldNumber()); - } - } - - pis.end(inGroupToken); - os.end(outGroupToken); - } - - private static void writeViewerConfigMessage( - @NonNull ProtoInputStream pis, @NonNull ProtoOutputStream os) throws IOException { - final long inMessageToken = pis.start(MESSAGES); - final long outMessagesToken = os.start(MESSAGES); - - while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) { - switch (pis.getFieldNumber()) { - case (int) MESSAGE_ID -> os.write(MESSAGE_ID, - pis.readLong(MESSAGE_ID)); - case (int) MESSAGE -> os.write(MESSAGE, pis.readString(MESSAGE)); - case (int) LEVEL -> os.write(LEVEL, pis.readInt(LEVEL)); - case (int) GROUP_ID -> os.write(GROUP_ID, pis.readInt(GROUP_ID)); - case (int) LOCATION -> os.write(LOCATION, pis.readString(LOCATION)); - default -> - throw new RuntimeException( - "Unexpected field id " + pis.getFieldNumber()); - } - } - - pis.end(inMessageToken); - os.end(outMessagesToken); - } + void disableProtoLogToLogcat(@NonNull String... groups); } diff --git a/core/java/com/android/internal/protolog/ProtoLogConfigurationServiceImpl.java b/core/java/com/android/internal/protolog/ProtoLogConfigurationServiceImpl.java new file mode 100644 index 000000000000..e382ac1513e0 --- /dev/null +++ b/core/java/com/android/internal/protolog/ProtoLogConfigurationServiceImpl.java @@ -0,0 +1,454 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.protolog; + +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.GROUPS; +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.ID; +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.NAME; +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.TAG; +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MESSAGES; +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.GROUP_ID; +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.LEVEL; +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.LOCATION; +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.MESSAGE; +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.MESSAGE_ID; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemService; +import android.content.Context; +import android.os.RemoteException; +import android.os.ResultReceiver; +import android.os.ShellCallback; +import android.tracing.perfetto.DataSourceParams; +import android.tracing.perfetto.InitArguments; +import android.tracing.perfetto.Producer; +import android.util.Log; +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoOutputStream; + +import com.android.internal.annotations.VisibleForTesting; + +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; + +/** + * The ProtoLog service is responsible for orchestrating centralized actions of the protolog tracing + * system. Currently this service has the following roles: + * - Handle shell commands to toggle logging ProtoLog messages for specified groups to logcat. + * - Handle viewer config dumping (the mapping from message hash to message string) for all protolog + * clients. This is for two reasons: firstly, because client processes might be frozen so might + * not response to the request to dump their viewer config when the trace is stopped; secondly, + * multiple processes might be running the same code with the same viewer config, this centralized + * service ensures we don't dump the same viewer config multiple times across processes. + * <p> + * {@link com.android.internal.protolog.IProtoLogClient ProtoLog clients} register themselves to + * this service on initialization. + * <p> + * This service is intended to run on the system server, such that it never gets frozen. + */ +@SystemService(Context.PROTOLOG_CONFIGURATION_SERVICE) +public class ProtoLogConfigurationServiceImpl extends IProtoLogConfigurationService.Stub + implements ProtoLogConfigurationService { + private static final String LOG_TAG = "ProtoLogConfigurationService"; + + private final ProtoLogDataSource mDataSource; + + /** + * Keeps track of how many of each viewer config file is currently registered. + * Use to keep track of which viewer config files are actively being used in tracing and might + * need to be dumped on flush. + */ + private final Map<String, Integer> mConfigFileCounts = new HashMap<>(); + /** + * Keeps track of the viewer config file of each client if available. + */ + private final Map<IProtoLogClient, String> mClientConfigFiles = new HashMap<>(); + + /** + * Keeps track of all the protolog groups that have been registered by clients and are still + * being actively traced. + */ + private final Set<String> mRegisteredGroups = new HashSet<>(); + /** + * Keeps track of all the clients that are actively tracing a given protolog group. + */ + private final Map<String, Set<IProtoLogClient>> mGroupToClients = new HashMap<>(); + + /** + * Keeps track of whether or not a given group should be logged to logcat. + * True when logging to logcat, false otherwise. + */ + private final Map<String, Boolean> mLogGroupToLogcatStatus = new TreeMap<>(); + + /** + * Keeps track of all the tracing instance ids that are actively running for ProtoLog. + */ + private final Set<Integer> mRunningInstances = new HashSet<>(); + + private final ViewerConfigFileTracer mViewerConfigFileTracer; + + public ProtoLogConfigurationServiceImpl() { + this(ProtoLogDataSource::new, ProtoLogConfigurationServiceImpl::dumpTransitionTraceConfig); + } + + @VisibleForTesting + public ProtoLogConfigurationServiceImpl(@NonNull ProtoLogDataSourceBuilder dataSourceBuilder) { + this(dataSourceBuilder, ProtoLogConfigurationServiceImpl::dumpTransitionTraceConfig); + } + + @VisibleForTesting + public ProtoLogConfigurationServiceImpl(@NonNull ViewerConfigFileTracer tracer) { + this(ProtoLogDataSource::new, tracer); + } + + @VisibleForTesting + public ProtoLogConfigurationServiceImpl( + @NonNull ProtoLogDataSourceBuilder dataSourceBuilder, + @NonNull ViewerConfigFileTracer tracer) { + mDataSource = dataSourceBuilder.build( + this::onTracingInstanceStart, + this::onTracingInstanceFlush, + this::onTracingInstanceStop + ); + + // Initialize the Perfetto producer and register the Perfetto ProtoLog datasource to be + // receive the lifecycle callbacks of the datasource and write the viewer configs if and + // when required to the datasource. + Producer.init(InitArguments.DEFAULTS); + final var params = new DataSourceParams.Builder() + .setBufferExhaustedPolicy(DataSourceParams.PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP) + .build(); + mDataSource.register(params); + + mViewerConfigFileTracer = tracer; + } + + public static class RegisterClientArgs extends IRegisterClientArgs.Stub { + /** + * The viewer config file to be registered for this client ProtoLog process. + */ + @Nullable + private String mViewerConfigFile = null; + /** + * The list of all groups that this client protolog process supports and might trace. + */ + @NonNull + private String[] mGroups = new String[0]; + /** + * The default logcat status of the ProtoLog client. True is logging to logcat, false + * otherwise. The indices should match the indices in {@link mGroups}. + */ + @NonNull + private boolean[] mLogcatStatus = new boolean[0]; + + public record GroupConfig(@NonNull String group, boolean logToLogcat) {} + + /** + * Specify groups to register with this client that will be used for protologging in this + * process. + * @param groups to register with this client. + * @return self + */ + public RegisterClientArgs setGroups(GroupConfig... groups) { + mGroups = new String[groups.length]; + mLogcatStatus = new boolean[groups.length]; + + for (int i = 0; i < groups.length; i++) { + mGroups[i] = groups[i].group; + mLogcatStatus[i] = groups[i].logToLogcat; + } + + return this; + } + + /** + * Set the viewer config file that the logs in this process are using. + * @param viewerConfigFile The file path of the viewer config. + * @return self + */ + public RegisterClientArgs setViewerConfigFile(@NonNull String viewerConfigFile) { + mViewerConfigFile = viewerConfigFile; + + return this; + } + + @Override + @NonNull + public String[] getGroups() { + return mGroups; + } + + @Override + @NonNull + public boolean[] getGroupsDefaultLogcatStatus() { + return mLogcatStatus; + } + + @Nullable + @Override + public String getViewerConfigFile() { + return mViewerConfigFile; + } + } + + @FunctionalInterface + public interface ViewerConfigFileTracer { + /** + * Write the viewer config data to the trace buffer. + * + * @param dataSource The target datasource to write the viewer config to. + * @param viewerConfigFilePath The path of the viewer config file which contains the data we + * want to write to the trace buffer. + * @throws FileNotFoundException if the viewerConfigFilePath is invalid. + */ + void trace(@NonNull ProtoLogDataSource dataSource, @NonNull String viewerConfigFilePath); + } + + @Override + public void registerClient(@NonNull IProtoLogClient client, @NonNull IRegisterClientArgs args) + throws RemoteException { + client.asBinder().linkToDeath(() -> onClientBinderDeath(client), /* flags */ 0); + + final String viewerConfigFile = args.getViewerConfigFile(); + if (viewerConfigFile != null) { + registerViewerConfigFile(client, viewerConfigFile); + } + + registerGroups(client, args.getGroups(), args.getGroupsDefaultLogcatStatus()); + } + + @Override + public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out, + @Nullable FileDescriptor err, @NonNull String[] args, @Nullable ShellCallback callback, + @NonNull ResultReceiver resultReceiver) throws RemoteException { + new ProtoLogCommandHandler(this) + .exec(this, in, out, err, args, callback, resultReceiver); + } + + /** + * Get the list of groups clients have registered to the protolog service. + * @return The list of ProtoLog groups registered with this service. + */ + @Override + @NonNull + public String[] getGroups() { + return mRegisteredGroups.toArray(new String[0]); + } + + /** + * Enable logging target groups to logcat. + * @param groups we want to enable logging them to logcat for. + */ + @Override + public void enableProtoLogToLogcat(@NonNull String... groups) { + toggleProtoLogToLogcat(true, groups); + } + + /** + * Disable logging target groups to logcat. + * @param groups we want to disable from being logged to logcat. + */ + @Override + public void disableProtoLogToLogcat(@NonNull String... groups) { + toggleProtoLogToLogcat(false, groups); + } + + /** + * Check if a group is logging to logcat + * @param group The group we want to check for + * @return True iff we are logging this group to logcat. + */ + @Override + public boolean isLoggingToLogcat(@NonNull String group) { + final Boolean isLoggingToLogcat = mLogGroupToLogcatStatus.get(group); + + if (isLoggingToLogcat == null) { + throw new RuntimeException( + "Trying to get logcat logging status of non-registered group " + group); + } + + return isLoggingToLogcat; + } + + private void registerViewerConfigFile( + @NonNull IProtoLogClient client, @NonNull String viewerConfigFile) { + final var count = mConfigFileCounts.getOrDefault(viewerConfigFile, 0); + mConfigFileCounts.put(viewerConfigFile, count + 1); + mClientConfigFiles.put(client, viewerConfigFile); + } + + private void registerGroups(@NonNull IProtoLogClient client, @NonNull String[] groups, + @NonNull boolean[] logcatStatuses) throws RemoteException { + if (groups.length != logcatStatuses.length) { + throw new RuntimeException( + "Expected groups and logcatStatuses to have the same length, " + + "but groups has length " + groups.length + + " and logcatStatuses has length " + logcatStatuses.length); + } + + for (int i = 0; i < groups.length; i++) { + String group = groups[i]; + boolean logcatStatus = logcatStatuses[i]; + + mRegisteredGroups.add(group); + + mGroupToClients.putIfAbsent(group, new HashSet<>()); + mGroupToClients.get(group).add(client); + + if (!mLogGroupToLogcatStatus.containsKey(group)) { + mLogGroupToLogcatStatus.put(group, logcatStatus); + } + + boolean requestedLogToLogcat = mLogGroupToLogcatStatus.get(group); + if (requestedLogToLogcat != logcatStatus) { + client.toggleLogcat(requestedLogToLogcat, new String[] { group }); + } + } + } + + private void toggleProtoLogToLogcat(boolean enabled, @NonNull String[] groups) { + final var clientToGroups = new HashMap<IProtoLogClient, Set<String>>(); + + for (String group : groups) { + final var clients = mGroupToClients.get(group); + + if (clients == null) { + // No clients associated to this group + Log.w(LOG_TAG, "Attempting to toggle log to logcat for group " + group + + " with no registered clients."); + continue; + } + + for (IProtoLogClient client : clients) { + clientToGroups.putIfAbsent(client, new HashSet<>()); + clientToGroups.get(client).add(group); + } + } + + for (IProtoLogClient client : clientToGroups.keySet()) { + try { + client.toggleLogcat(enabled, clientToGroups.get(client).toArray(new String[0])); + } catch (RemoteException e) { + throw new RuntimeException( + "Failed to toggle logcat status for groups on client", e); + } + } + + for (String group : groups) { + mLogGroupToLogcatStatus.put(group, enabled); + } + } + + private void onTracingInstanceStart(int instanceIdx, ProtoLogDataSource.ProtoLogConfig config) { + mRunningInstances.add(instanceIdx); + } + + private void onTracingInstanceFlush() { + for (String fileName : mConfigFileCounts.keySet()) { + mViewerConfigFileTracer.trace(mDataSource, fileName); + } + } + + private void onTracingInstanceStop(int instanceIdx, ProtoLogDataSource.ProtoLogConfig config) { + mRunningInstances.remove(instanceIdx); + } + + private static void dumpTransitionTraceConfig(@NonNull ProtoLogDataSource dataSource, + @NonNull String viewerConfigFilePath) { + Utils.dumpViewerConfig(dataSource, () -> { + try { + return new ProtoInputStream(new FileInputStream(viewerConfigFilePath)); + } catch (FileNotFoundException e) { + throw new RuntimeException( + "Failed to load viewer config file " + viewerConfigFilePath, e); + } + }); + } + + private void onClientBinderDeath(@NonNull IProtoLogClient client) { + // Dump the tracing config now if no other client is going to dump the same config file. + String configFile = mClientConfigFiles.get(client); + if (configFile != null) { + final var newCount = mConfigFileCounts.get(configFile) - 1; + mConfigFileCounts.put(configFile, newCount); + boolean lastProcessWithViewerConfig = newCount == 0; + if (lastProcessWithViewerConfig) { + mViewerConfigFileTracer.trace(mDataSource, configFile); + } + } + } + + private static void writeViewerConfigGroup( + @NonNull ProtoInputStream pis, @NonNull ProtoOutputStream os) throws IOException { + final long inGroupToken = pis.start(GROUPS); + final long outGroupToken = os.start(GROUPS); + + while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pis.getFieldNumber()) { + case (int) ID -> { + int id = pis.readInt(ID); + os.write(ID, id); + } + case (int) NAME -> { + String name = pis.readString(NAME); + os.write(NAME, name); + } + case (int) TAG -> { + String tag = pis.readString(TAG); + os.write(TAG, tag); + } + default -> + throw new RuntimeException( + "Unexpected field id " + pis.getFieldNumber()); + } + } + + pis.end(inGroupToken); + os.end(outGroupToken); + } + + private static void writeViewerConfigMessage( + @NonNull ProtoInputStream pis, @NonNull ProtoOutputStream os) throws IOException { + final long inMessageToken = pis.start(MESSAGES); + final long outMessagesToken = os.start(MESSAGES); + + while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pis.getFieldNumber()) { + case (int) MESSAGE_ID -> os.write(MESSAGE_ID, + pis.readLong(MESSAGE_ID)); + case (int) MESSAGE -> os.write(MESSAGE, pis.readString(MESSAGE)); + case (int) LEVEL -> os.write(LEVEL, pis.readInt(LEVEL)); + case (int) GROUP_ID -> os.write(GROUP_ID, pis.readInt(GROUP_ID)); + case (int) LOCATION -> os.write(LOCATION, pis.readString(LOCATION)); + default -> + throw new RuntimeException( + "Unexpected field id " + pis.getFieldNumber()); + } + } + + pis.end(inMessageToken); + os.end(outMessagesToken); + } +} diff --git a/core/java/com/android/internal/view/menu/ListMenuItemView.java b/core/java/com/android/internal/view/menu/ListMenuItemView.java index b73cacb77539..bdb33c4b151c 100644 --- a/core/java/com/android/internal/view/menu/ListMenuItemView.java +++ b/core/java/com/android/internal/view/menu/ListMenuItemView.java @@ -33,8 +33,6 @@ import android.widget.LinearLayout; import android.widget.RadioButton; import android.widget.TextView; -import com.android.text.flags.Flags; - /** * The item view for each item in the ListView-based MenuViews. */ @@ -283,10 +281,7 @@ public class ListMenuItemView extends LinearLayout private void insertIconView() { LayoutInflater inflater = getInflater(); - mIconView = (ImageView) inflater.inflate( - !Flags.fixMisalignedContextMenu() - ? com.android.internal.R.layout.list_menu_item_fixed_size_icon : - com.android.internal.R.layout.list_menu_item_icon, + mIconView = (ImageView) inflater.inflate(com.android.internal.R.layout.list_menu_item_icon, this, false); addContentView(mIconView, 0); } diff --git a/core/java/com/android/internal/view/menu/StandardMenuPopup.java b/core/java/com/android/internal/view/menu/StandardMenuPopup.java index c43a8c6ee7ee..8e2536a423b8 100644 --- a/core/java/com/android/internal/view/menu/StandardMenuPopup.java +++ b/core/java/com/android/internal/view/menu/StandardMenuPopup.java @@ -35,8 +35,6 @@ import android.widget.PopupWindow; import android.widget.PopupWindow.OnDismissListener; import android.widget.TextView; -import com.android.text.flags.Flags; - import java.util.Objects; /** @@ -122,8 +120,7 @@ final class StandardMenuPopup extends MenuPopup implements OnDismissListener, On mMenu = menu; mOverflowOnly = overflowOnly; final LayoutInflater inflater = LayoutInflater.from(context); - mAdapter = new MenuAdapter(menu, inflater, mOverflowOnly, - Flags.fixMisalignedContextMenu() ? ITEM_LAYOUT_MATERIAL : ITEM_LAYOUT); + mAdapter = new MenuAdapter(menu, inflater, mOverflowOnly, ITEM_LAYOUT_MATERIAL); mPopupStyleAttr = popupStyleAttr; mPopupStyleRes = popupStyleRes; diff --git a/core/jni/Android.bp b/core/jni/Android.bp index 90cb10aa62b2..9a4ff8fc264f 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -290,7 +290,6 @@ cc_library_shared_for_libandroid_runtime { "libasync_safe", "libbinderthreadstateutils", "libdmabufinfo", - "libgif", "libgui_window_info_static", "libkernelconfigs", "libnativehelper_lazy", diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp index f2c70b5f41d4..8003bb7d442b 100644 --- a/core/jni/android_util_Binder.cpp +++ b/core/jni/android_util_Binder.cpp @@ -1747,9 +1747,9 @@ static const JNINativeMethod gBinderProxyMethods[] = { {"linkToDeathNative", "(Landroid/os/IBinder$DeathRecipient;I)V", (void*)android_os_BinderProxy_linkToDeath}, {"unlinkToDeathNative", "(Landroid/os/IBinder$DeathRecipient;I)Z", (void*)android_os_BinderProxy_unlinkToDeath}, {"addFrozenStateChangeCallbackNative", - "(Landroid/os/IBinder$IFrozenStateChangeCallback;)V", (void*)android_os_BinderProxy_addFrozenStateChangeCallback}, + "(Landroid/os/IBinder$FrozenStateChangeCallback;)V", (void*)android_os_BinderProxy_addFrozenStateChangeCallback}, {"removeFrozenStateChangeCallbackNative", - "(Landroid/os/IBinder$IFrozenStateChangeCallback;)Z", (void*)android_os_BinderProxy_removeFrozenStateChangeCallback}, + "(Landroid/os/IBinder$FrozenStateChangeCallback;)Z", (void*)android_os_BinderProxy_removeFrozenStateChangeCallback}, {"getNativeFinalizer", "()J", (void*)android_os_BinderProxy_getNativeFinalizer}, {"getExtension", "()Landroid/os/IBinder;", (void*)android_os_BinderProxy_getExtension}, }; @@ -1774,7 +1774,7 @@ static int int_register_android_os_BinderProxy(JNIEnv* env) "(Landroid/os/IBinder$DeathRecipient;Landroid/os/IBinder;)V"); gBinderProxyOffsets.mInvokeFrozenStateChangeCallback = GetStaticMethodIDOrDie(env, clazz, "invokeFrozenStateChangeCallback", - "(Landroid/os/IBinder$IFrozenStateChangeCallback;Landroid/os/" + "(Landroid/os/IBinder$FrozenStateChangeCallback;Landroid/os/" "IBinder;I)V"); gBinderProxyOffsets.mNativeData = GetFieldIDOrDie(env, clazz, "mNativeData", "J"); diff --git a/core/jni/jni_wrappers.h b/core/jni/jni_wrappers.h index 3b29e305e410..21b5b1308fcf 100644 --- a/core/jni/jni_wrappers.h +++ b/core/jni/jni_wrappers.h @@ -69,9 +69,47 @@ static inline T MakeGlobalRefOrDie(JNIEnv* env, T in) { return static_cast<T>(res); } +// Inline variable that specifies the method binding format. +// The expected format is 'XX${method}XX', where ${method} represents the original method name. +// This variable is shared across all translation units. This is treated as a global variable as +// per C++ 17. +inline std::string jniMethodFormat; + +inline static void setJniMethodFormat(std::string value) { + jniMethodFormat = value; +} + +// Potentially translates the given JNINativeMethods if setJniMethodFormat has been set. +// Has no effect otherwise +inline const JNINativeMethod* maybeRenameJniMethods(const JNINativeMethod* gMethods, + int numMethods) { + if (jniMethodFormat.empty()) { + return gMethods; + } + // Make a copy of gMethods with reformatted method names. + JNINativeMethod* modifiedMethods = new JNINativeMethod[numMethods]; + LOG_ALWAYS_FATAL_IF(!modifiedMethods, "Failed to allocate a copy of the JNI methods"); + + size_t methodNamePos = jniMethodFormat.find("${method}"); + LOG_ALWAYS_FATAL_IF(methodNamePos == std::string::npos, + "Invalid jniMethodFormat: could not find '${method}' in pattern"); + + for (int i = 0; i < numMethods; i++) { + modifiedMethods[i] = gMethods[i]; + std::string modifiedName = jniMethodFormat; + modifiedName.replace(methodNamePos, 9, gMethods[i].name); + char* modifiedNameChars = new char[modifiedName.length() + 1]; + LOG_ALWAYS_FATAL_IF(!modifiedNameChars, "Failed to allocate the new method name"); + std::strcpy(modifiedNameChars, modifiedName.c_str()); + modifiedMethods[i].name = modifiedNameChars; + } + return modifiedMethods; +} + static inline int RegisterMethodsOrDie(JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods) { - int res = jniRegisterNativeMethods(env, className, gMethods, numMethods); + const JNINativeMethod* modifiedMethods = maybeRenameJniMethods(gMethods, numMethods); + int res = jniRegisterNativeMethods(env, className, modifiedMethods, numMethods); LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods."); return res; } diff --git a/core/proto/android/widget/remoteviews.proto b/core/proto/android/widget/remoteviews.proto index 47c97b08666b..f477d32cd915 100644 --- a/core/proto/android/widget/remoteviews.proto +++ b/core/proto/android/widget/remoteviews.proto @@ -299,6 +299,14 @@ message RemoteViewsProto { NightModeReflectionAction night_mode_reflection_action = 5; ReflectionAction reflection_action = 6; RemoveFromParentAction remove_from_parent_action = 7; + ResourceReflectionAction resource_reflection_action = 8; + SetCompoundButtonCheckedAction set_compound_button_checked_action = 9; + SetDrawableTintAction set_drawable_tint_action = 10; + SetEmptyViewAction set_empty_view_action = 11; + SetIntTagAction set_int_tag_action = 12; + SetRadioGroupCheckedAction set_radio_group_checked_action = 13; + SetRemoteCollectionItemListAdapterAction set_remote_collection_item_list_adapter_action = 14; + SetRippleDrawableColorAction set_ripple_drawable_color_action = 15; } } @@ -374,6 +382,52 @@ message RemoteViewsProto { message RemoveFromParentAction { optional string view_id = 1; } + + message ResourceReflectionAction { + optional string view_id = 1; + optional string method_name = 2; + optional int32 resource_type = 3; + optional string res_id = 4; + optional int32 parameter_type = 5; + } + + message SetCompoundButtonCheckedAction { + optional string view_id = 1; + optional bool checked = 2; + } + + message SetDrawableTintAction { + optional string view_id = 1; + optional bool target_background = 2; + optional int32 color_filter = 3; + optional int32 filter_mode = 4; + } + + message SetEmptyViewAction { + optional string view_id = 1; + optional string empty_view_id = 2; + } + + message SetIntTagAction { + optional string view_id = 1; + optional string key = 2; + optional int32 tag = 3; + } + + message SetRadioGroupCheckedAction { + optional string view_id = 1; + optional string checked_id = 2; + } + + message SetRemoteCollectionItemListAdapterAction { + optional string view_id = 1; + optional RemoteCollectionItems items = 2; + } + + message SetRippleDrawableColorAction { + optional string view_id = 1; + optional android.content.res.ColorStateListProto color_state_list = 2; + } } diff --git a/core/res/res/layout/miniresolver.xml b/core/res/res/layout/miniresolver.xml index e60e0b0079a9..db1c779d0087 100644 --- a/core/res/res/layout/miniresolver.xml +++ b/core/res/res/layout/miniresolver.xml @@ -122,7 +122,8 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentStart="true" - android:maxLines="2" + android:layout_toStartOf="@id/button_open" + android:layout_marginEnd="8dp" android:background="@drawable/resolver_outlined_button_bg" style="?android:attr/borderlessButtonStyle" android:paddingHorizontal="16dp" @@ -136,7 +137,6 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentEnd="true" - android:maxLines="2" android:paddingHorizontal="16dp" android:background="@drawable/resolver_button_bg" style="?android:attr/borderlessButtonStyle" diff --git a/core/tests/coretests/BinderFrozenStateChangeCallbackTestApp/src/com/android/frameworks/coretests/bfscctestapp/BfsccTestAppCmdService.java b/core/tests/coretests/BinderFrozenStateChangeCallbackTestApp/src/com/android/frameworks/coretests/bfscctestapp/BfsccTestAppCmdService.java index 77e8a404a0ff..fe54aa8d87f0 100644 --- a/core/tests/coretests/BinderFrozenStateChangeCallbackTestApp/src/com/android/frameworks/coretests/bfscctestapp/BfsccTestAppCmdService.java +++ b/core/tests/coretests/BinderFrozenStateChangeCallbackTestApp/src/com/android/frameworks/coretests/bfscctestapp/BfsccTestAppCmdService.java @@ -30,31 +30,30 @@ import java.util.concurrent.TimeUnit; public class BfsccTestAppCmdService extends Service { private IBfsccTestAppCmdService.Stub mBinder = new IBfsccTestAppCmdService.Stub() { - private final LinkedBlockingQueue<IBinder.IFrozenStateChangeCallback.State> mNotifications = + private final LinkedBlockingQueue<Integer> mNotifications = new LinkedBlockingQueue<>(); @Override public void listenTo(IBinder binder) throws RemoteException { binder.addFrozenStateChangeCallback( - (IBinder who, IBinder.IFrozenStateChangeCallback.State state) - -> mNotifications.offer(state)); + (IBinder who, int state) -> mNotifications.offer(state)); } @Override public boolean[] waitAndConsumeNotifications() { List<Boolean> results = new ArrayList<>(); try { - IBinder.IFrozenStateChangeCallback.State state = - mNotifications.poll(5, TimeUnit.SECONDS); + Integer state = mNotifications.poll(5, TimeUnit.SECONDS); if (state != null) { - results.add(state == IBinder.IFrozenStateChangeCallback.State.FROZEN); + results.add( + state.intValue() == IBinder.FrozenStateChangeCallback.STATE_FROZEN); } } catch (InterruptedException e) { return null; } while (mNotifications.size() > 0) { - results.add(mNotifications.poll() - == IBinder.IFrozenStateChangeCallback.State.FROZEN); + results.add(mNotifications.poll().intValue() + == IBinder.FrozenStateChangeCallback.STATE_FROZEN); } boolean[] convertedResults = new boolean[results.size()]; for (int i = 0; i < results.size(); i++) { diff --git a/core/tests/coretests/src/android/os/BinderFrozenStateChangeNotificationTest.java b/core/tests/coretests/src/android/os/BinderFrozenStateChangeNotificationTest.java index ee2e7e06081e..195a18a5f521 100644 --- a/core/tests/coretests/src/android/os/BinderFrozenStateChangeNotificationTest.java +++ b/core/tests/coretests/src/android/os/BinderFrozenStateChangeNotificationTest.java @@ -52,7 +52,7 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; /** - * Tests functionality of {@link android.os.IBinder.IFrozenStateChangeCallback}. + * Tests functionality of {@link android.os.IBinder.FrozenStateChangeCallback}. */ @RunWith(AndroidJUnit4.class) @IgnoreUnderRavenwood(blockedBy = ActivityManager.class) @@ -157,7 +157,7 @@ public class BinderFrozenStateChangeNotificationTest { @Test public void onStateChangeNotCalledAfterCallbackRemoved() throws Exception { final LinkedBlockingQueue<Boolean> results = new LinkedBlockingQueue<>(); - IBinder.IFrozenStateChangeCallback callback; + IBinder.FrozenStateChangeCallback callback; if ((callback = createCallback(mBfsccTestAppCmdService.asBinder(), results)) == null) { return; } @@ -171,7 +171,7 @@ public class BinderFrozenStateChangeNotificationTest { public void multipleCallbacks() throws Exception { final LinkedBlockingQueue<Boolean> results1 = new LinkedBlockingQueue<>(); final LinkedBlockingQueue<Boolean> results2 = new LinkedBlockingQueue<>(); - IBinder.IFrozenStateChangeCallback callback1; + IBinder.FrozenStateChangeCallback callback1; if ((callback1 = createCallback(mBfsccTestAppCmdService.asBinder(), results1)) == null) { return; } @@ -197,8 +197,8 @@ public class BinderFrozenStateChangeNotificationTest { public void onStateChangeCalledWithTheRightBinder() throws Exception { final IBinder binder = mBfsccTestAppCmdService.asBinder(); final LinkedBlockingQueue<IBinder> results = new LinkedBlockingQueue<>(); - IBinder.IFrozenStateChangeCallback callback = - (IBinder who, IBinder.IFrozenStateChangeCallback.State state) -> results.offer(who); + IBinder.FrozenStateChangeCallback callback = + (IBinder who, int state) -> results.offer(who); try { binder.addFrozenStateChangeCallback(callback); } catch (UnsupportedOperationException e) { @@ -221,12 +221,12 @@ public class BinderFrozenStateChangeNotificationTest { } } - private IBinder.IFrozenStateChangeCallback createCallback(IBinder binder, Queue<Boolean> queue) + private IBinder.FrozenStateChangeCallback createCallback(IBinder binder, Queue<Boolean> queue) throws RemoteException { try { - final IBinder.IFrozenStateChangeCallback callback = - (IBinder who, IBinder.IFrozenStateChangeCallback.State state) -> - queue.offer(state == IBinder.IFrozenStateChangeCallback.State.FROZEN); + final IBinder.FrozenStateChangeCallback callback = + (IBinder who, int state) -> + queue.offer(state == IBinder.FrozenStateChangeCallback.STATE_FROZEN); binder.addFrozenStateChangeCallback(callback); return callback; } catch (UnsupportedOperationException e) { diff --git a/core/tests/coretests/src/android/window/flags/DesktopModeFlagsTest.java b/core/tests/coretests/src/android/window/flags/DesktopModeFlagsTest.java index 32345e606229..dd406955785b 100644 --- a/core/tests/coretests/src/android/window/flags/DesktopModeFlagsTest.java +++ b/core/tests/coretests/src/android/window/flags/DesktopModeFlagsTest.java @@ -16,7 +16,7 @@ package android.window.flags; -import static android.window.flags.DesktopModeFlags.DESKTOP_WINDOWING_MODE; +import static android.window.flags.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODE; import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE; import static com.android.window.flags.Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS; @@ -75,7 +75,7 @@ public class DesktopModeFlagsTest { public void isEnabled_devOptionFlagDisabled_overrideOff_featureFlagOn_returnsTrue() { setOverride(OVERRIDE_OFF_SETTING); // In absence of dev options, follow flag - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue(); + assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isTrue(); } @@ -84,7 +84,7 @@ public class DesktopModeFlagsTest { public void isEnabled_devOptionFlagDisabled_overrideOn_featureFlagOff_returnsFalse() { setOverride(OVERRIDE_ON_SETTING); - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse(); + assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isFalse(); } @Test @@ -93,7 +93,7 @@ public class DesktopModeFlagsTest { setOverride(OVERRIDE_UNSET_SETTING); // For overridableFlag, for unset overrides, follow flag - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue(); + assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isTrue(); } @Test @@ -103,7 +103,7 @@ public class DesktopModeFlagsTest { setOverride(OVERRIDE_UNSET_SETTING); // For overridableFlag, for unset overrides, follow flag - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse(); + assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isFalse(); } @Test @@ -112,7 +112,7 @@ public class DesktopModeFlagsTest { setOverride(null); // For overridableFlag, in absence of overrides, follow flag - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue(); + assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isTrue(); } @Test @@ -122,7 +122,7 @@ public class DesktopModeFlagsTest { setOverride(null); // For overridableFlag, in absence of overrides, follow flag - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse(); + assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isFalse(); } @Test @@ -131,7 +131,7 @@ public class DesktopModeFlagsTest { setOverride(-2); // For overridableFlag, for unrecognized overrides, follow flag - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue(); + assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isTrue(); } @Test @@ -141,7 +141,7 @@ public class DesktopModeFlagsTest { setOverride(-2); // For overridableFlag, for unrecognizable overrides, follow flag - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse(); + assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isFalse(); } @Test @@ -150,7 +150,7 @@ public class DesktopModeFlagsTest { setOverride(OVERRIDE_OFF_SETTING); // For overridableFlag, follow override if they exist - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse(); + assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isFalse(); } @Test @@ -160,7 +160,7 @@ public class DesktopModeFlagsTest { setOverride(OVERRIDE_ON_SETTING); // For overridableFlag, follow override if they exist - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue(); + assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isTrue(); } @Test @@ -169,12 +169,12 @@ public class DesktopModeFlagsTest { setOverride(OVERRIDE_OFF_SETTING); // For overridableFlag, follow override if they exist - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse(); + assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isFalse(); setOverride(OVERRIDE_ON_SETTING); // Keep overrides constant through the process - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse(); + assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isFalse(); } @Test @@ -184,12 +184,12 @@ public class DesktopModeFlagsTest { setOverride(OVERRIDE_ON_SETTING); // For overridableFlag, follow override if they exist - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue(); + assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isTrue(); setOverride(OVERRIDE_OFF_SETTING); // Keep overrides constant through the process - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue(); + assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isTrue(); } @Test @@ -199,7 +199,7 @@ public class DesktopModeFlagsTest { setOverride(OVERRIDE_UNSET_SETTING); // For unset overrides, follow flag - assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isTrue(); + assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isTrue(); } @Test @@ -208,7 +208,7 @@ public class DesktopModeFlagsTest { public void isEnabled_dwFlagOn_overrideUnset_featureFlagOff_returnsFalse() { setOverride(OVERRIDE_UNSET_SETTING); // For unset overrides, follow flag - assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isFalse(); + assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isFalse(); } @Test @@ -221,7 +221,7 @@ public class DesktopModeFlagsTest { setOverride(OVERRIDE_ON_SETTING); // When toggle override matches its default state (dw flag), don't override flags - assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isTrue(); + assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isTrue(); } @Test @@ -231,7 +231,7 @@ public class DesktopModeFlagsTest { setOverride(OVERRIDE_ON_SETTING); // When toggle override matches its default state (dw flag), don't override flags - assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isFalse(); + assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isFalse(); } @Test @@ -244,7 +244,7 @@ public class DesktopModeFlagsTest { setOverride(OVERRIDE_OFF_SETTING); // Follow override if they exist, and is not equal to default toggle state (dw flag) - assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isTrue(); + assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isTrue(); } @Test @@ -254,7 +254,7 @@ public class DesktopModeFlagsTest { setOverride(OVERRIDE_OFF_SETTING); // Follow override if they exist, and is not equal to default toggle state (dw flag) - assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isFalse(); + assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isFalse(); } @Test @@ -267,7 +267,7 @@ public class DesktopModeFlagsTest { setOverride(OVERRIDE_UNSET_SETTING); // For unset overrides, follow flag - assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isTrue(); + assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isTrue(); } @Test @@ -280,7 +280,7 @@ public class DesktopModeFlagsTest { setOverride(OVERRIDE_UNSET_SETTING); // For unset overrides, follow flag - assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isFalse(); + assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isFalse(); } @Test @@ -293,7 +293,7 @@ public class DesktopModeFlagsTest { setOverride(OVERRIDE_ON_SETTING); // Follow override if they exist, and is not equal to default toggle state (dw flag) - assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isTrue(); + assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isTrue(); } @Test @@ -306,7 +306,7 @@ public class DesktopModeFlagsTest { setOverride(OVERRIDE_ON_SETTING); // Follow override if they exist, and is not equal to default toggle state (dw flag) - assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isFalse(); + assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isFalse(); } @Test @@ -319,7 +319,7 @@ public class DesktopModeFlagsTest { setOverride(OVERRIDE_OFF_SETTING); // When toggle override matches its default state (dw flag), don't override flags - assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isTrue(); + assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isTrue(); } @Test @@ -332,7 +332,7 @@ public class DesktopModeFlagsTest { setOverride(OVERRIDE_OFF_SETTING); // When toggle override matches its default state (dw flag), don't override flags - assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isFalse(); + assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isFalse(); } private void setOverride(Integer setting) { diff --git a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java index e0823b874edf..d6b2a782bc0c 100644 --- a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java +++ b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java @@ -24,7 +24,6 @@ import static com.android.internal.jank.InteractionJankMonitor.Configuration.gen import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; @@ -190,25 +189,6 @@ public class InteractionJankMonitorTest { assertThat(generateSessionName(cujName, tooLongTag)).isEqualTo(expectedTrimmedName); } - @Test - public void validateConfiguration_surfaceOnlyAndNotDeferMonitor_throwsError() { - Configuration.Builder builder = Configuration.Builder.withSurface(1, - mActivity.getApplicationContext(), mSurfaceControl, - mActivity.getMainThreadHandler()).setDeferMonitorForAnimationStart(false); - - assertThrows(IllegalStateException.class, builder::build); - } - - @Test - public void validateConfiguration_surfaceOnlyAndDeferMonitor_doesNotThrowError() { - Configuration.Builder builder = Configuration.Builder.withSurface(1, - mActivity.getApplicationContext(), - mSurfaceControl, mActivity.getMainThreadHandler()).setDeferMonitorForAnimationStart( - true); - - builder.build(); // no exception. - } - private InteractionJankMonitor createMockedInteractionJankMonitor() { InteractionJankMonitor monitor = spy(new InteractionJankMonitor(mWorker)); doReturn(true).when(monitor).shouldMonitor(); diff --git a/core/tests/coretests/src/com/android/internal/os/BinderDeathDispatcherTest.java b/core/tests/coretests/src/com/android/internal/os/BinderDeathDispatcherTest.java index 397cdcf6acdd..67de25eede42 100644 --- a/core/tests/coretests/src/com/android/internal/os/BinderDeathDispatcherTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BinderDeathDispatcherTest.java @@ -125,12 +125,12 @@ public class BinderDeathDispatcherTest { } @Override - public void addFrozenStateChangeCallback(IFrozenStateChangeCallback callback) + public void addFrozenStateChangeCallback(FrozenStateChangeCallback callback) throws RemoteException { } @Override - public boolean removeFrozenStateChangeCallback(IFrozenStateChangeCallback callback) { + public boolean removeFrozenStateChangeCallback(FrozenStateChangeCallback callback) { return false; } diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java index 6d31578ac020..e07471cd64bc 100644 --- a/graphics/java/android/graphics/Bitmap.java +++ b/graphics/java/android/graphics/Bitmap.java @@ -128,6 +128,22 @@ public final class Bitmap implements Parcelable { private static final WeakHashMap<Bitmap, Void> sAllBitmaps = new WeakHashMap<>(); /** + * @hide + */ + private static NativeAllocationRegistry getRegistry(boolean malloc, long size) { + final long free = nativeGetNativeFinalizer(); + if (com.android.libcore.Flags.nativeMetrics()) { + Class cls = Bitmap.class; + return malloc ? NativeAllocationRegistry.createMalloced(cls, free, size) + : NativeAllocationRegistry.createNonmalloced(cls, free, size); + } else { + ClassLoader loader = Bitmap.class.getClassLoader(); + return malloc ? NativeAllocationRegistry.createMalloced(loader, free, size) + : NativeAllocationRegistry.createNonmalloced(loader, free, size); + } + } + + /** * Private constructor that must receive an already allocated native bitmap * int (pointer). */ @@ -151,7 +167,6 @@ public final class Bitmap implements Parcelable { mWidth = width; mHeight = height; mRequestPremultiplied = requestPremultiplied; - mNinePatchChunk = ninePatchChunk; mNinePatchInsets = ninePatchInsets; if (density >= 0) { @@ -159,17 +174,9 @@ public final class Bitmap implements Parcelable { } mNativePtr = nativeBitmap; - final int allocationByteCount = getAllocationByteCount(); - NativeAllocationRegistry registry; - if (fromMalloc) { - registry = NativeAllocationRegistry.createMalloced( - Bitmap.class.getClassLoader(), nativeGetNativeFinalizer(), allocationByteCount); - } else { - registry = NativeAllocationRegistry.createNonmalloced( - Bitmap.class.getClassLoader(), nativeGetNativeFinalizer(), allocationByteCount); - } - registry.registerNativeAllocation(this, nativeBitmap); + getRegistry(fromMalloc, allocationByteCount).registerNativeAllocation(this, mNativePtr); + synchronized (Bitmap.class) { sAllBitmaps.put(this, null); } 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 dd86a1a0edbb..83619efefd3b 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 @@ -90,9 +90,6 @@ public class DesktopModeStatus { /** The maximum override density allowed for tasks inside the desktop. */ private static final int DESKTOP_DENSITY_MAX = 1000; - /** The number of [WindowDecorViewHost] instances to warm up on system start. */ - private static final int WINDOW_DECOR_PRE_WARM_SIZE = 2; - /** * Sysprop declaring whether to enters desktop mode by default when the windowing mode of the * display's root TaskDisplayArea is set to WINDOWING_MODE_FREEFORM. @@ -115,14 +112,6 @@ public class DesktopModeStatus { private static final String MAX_TASK_LIMIT_SYS_PROP = "persist.wm.debug.desktop_max_task_limit"; /** - * Sysprop declaring the number of [WindowDecorViewHost] instances to warm up on system start. - * - * <p>If it is not defined, then [WINDOW_DECOR_PRE_WARM_SIZE] is used. - */ - private static final String WINDOW_DECOR_PRE_WARM_SIZE_SYS_PROP = - "persist.wm.debug.desktop_window_decor_pre_warm_size"; - - /** * Return {@code true} if veiled resizing is active. If false, fluid resizing is used. */ public static boolean isVeiledResizeEnabled() { @@ -162,12 +151,6 @@ public class DesktopModeStatus { context.getResources().getInteger(R.integer.config_maxDesktopWindowingActiveTasks)); } - /** The number of [WindowDecorViewHost] instances to warm up on system start. */ - public static int getWindowDecorPreWarmSize() { - return SystemProperties.getInt(WINDOW_DECOR_PRE_WARM_SIZE_SYS_PROP, - WINDOW_DECOR_PRE_WARM_SIZE); - } - /** * Return {@code true} if the current device supports desktop mode. */ 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 c545d73734f0..af4a0c55f28d 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 @@ -1241,8 +1241,9 @@ public class BubbleController implements ConfigurationChangeListener, mBubbleData.dismissBubbleWithKey( bubbleKey, Bubbles.DISMISS_USER_GESTURE_FROM_LAUNCHER, timestamp); } - if (selectedBubbleKey != null && !selectedBubbleKey.equals(bubbleKey)) { - // We did not remove the selected bubble. Expand it again + if (mBubbleData.hasBubbles()) { + // We still have bubbles, if we dragged an individual bubble to dismiss we were expanded + // so re-expand to whatever is selected. showExpandedViewForBubbleBar(); } } @@ -2007,7 +2008,7 @@ public class BubbleController implements ConfigurationChangeListener, @Override public void selectionChanged(BubbleViewProvider selectedBubble) { // Only need to update the layer view if we're currently expanded for selection changes. - if (mLayerView != null && isStackExpanded()) { + if (mLayerView != null && mLayerView.isExpanded()) { mLayerView.showExpandedView(selectedBubble); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java index 1c9c195cf718..1367b7e24bc7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java @@ -186,6 +186,10 @@ public class BubbleBarLayerView extends FrameLayout if (expandedView == null) { return; } + if (mExpandedBubble != null && mIsExpanded && b.getKey().equals(mExpandedBubble.getKey())) { + // Already showing this bubble, skip animating + return; + } if (mExpandedBubble != null && !b.getKey().equals(mExpandedBubble.getKey())) { removeView(mExpandedView); mExpandedView = 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 b47adb43c2a6..2f4d77baae97 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 @@ -116,8 +116,6 @@ import com.android.wm.shell.windowdecor.CaptionWindowDecorViewModel; import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel; import com.android.wm.shell.windowdecor.WindowDecorViewModel; import com.android.wm.shell.windowdecor.viewhost.DefaultWindowDecorViewHostSupplier; -import com.android.wm.shell.windowdecor.viewhost.PooledWindowDecorViewHostSupplier; -import com.android.wm.shell.windowdecor.viewhost.ReusableWindowDecorViewHost; import com.android.wm.shell.windowdecor.viewhost.WindowDecorViewHostSupplier; import dagger.Binds; @@ -384,19 +382,8 @@ public abstract class WMShellModule { @WMSingleton @Provides static WindowDecorViewHostSupplier provideWindowDecorViewHostSupplier( - @NonNull Context context, - @ShellMainThread @NonNull CoroutineScope mainScope, - @NonNull ShellInit shellInit) { - if (DesktopModeStatus.canEnterDesktopMode(context) - && Flags.enableDesktopWindowingScvhCache()) { - final int maxPoolSize = DesktopModeStatus.getMaxTaskLimit(context); - final int preWarmSize = DesktopModeStatus.getWindowDecorPreWarmSize(); - return new PooledWindowDecorViewHostSupplier( - context, mainScope, shellInit, - ReusableWindowDecorViewHost.DefaultFactory.INSTANCE, maxPoolSize, preWarmSize); - } else { - return new DefaultWindowDecorViewHostSupplier(mainScope); - } + @ShellMainThread @NonNull CoroutineScope mainScope) { + return new DefaultWindowDecorViewHostSupplier(mainScope); } // 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 853284a58904..b8ebbcdbfb9d 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 @@ -305,13 +305,18 @@ class DesktopTasksController( private fun getSplitFocusedTask(task1: RunningTaskInfo, task2: RunningTaskInfo) = if (task1.taskId == task2.parentTaskId) task2 else task1 - private fun isFreeformDisplay(displayId: Int): Boolean { + private fun forceEnterDesktop(displayId: Int): Boolean { + if (!DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)) { + return false + } + val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(displayId) requireNotNull(tdaInfo) { "This method can only be called with the ID of a display having non-null DisplayArea." } val tdaWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode - return tdaWindowingMode == WINDOWING_MODE_FREEFORM + val isFreeformDisplay = tdaWindowingMode == WINDOWING_MODE_FREEFORM + return isFreeformDisplay } /** Moves task to desktop mode if task is running, else launches it in desktop mode. */ @@ -1191,10 +1196,11 @@ class DesktopTasksController( val wct = WindowContainerTransaction() if (!isDesktopModeShowing(task.displayId)) { logD("Bring desktop tasks to front on transition=taskId=%d", task.taskId) - // We are outside of desktop mode and already existing desktop task is being launched. - // We should make this task go to fullscreen instead of freeform. Note that this means - // any re-launch of a freeform window outside of desktop will be in fullscreen. - if (taskRepository.isActiveTask(task.taskId)) { + if (taskRepository.isActiveTask(task.taskId) && !forceEnterDesktop(task.displayId)) { + // We are outside of desktop mode and already existing desktop task is being + // launched. We should make this task go to fullscreen instead of freeform. Note + // that this means any re-launch of a freeform window outside of desktop will be in + // fullscreen as long as default-desktop flag is disabled. addMoveToFullscreenChanges(wct, task) return wct } @@ -1231,9 +1237,7 @@ class DesktopTasksController( transition: IBinder ): WindowContainerTransaction? { logV("handleFullscreenTaskLaunch") - val forceEnterDesktop = DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context) && - isFreeformDisplay(task.displayId) - if (isDesktopModeShowing(task.displayId) || forceEnterDesktop) { + if (isDesktopModeShowing(task.displayId) || forceEnterDesktop(task.displayId)) { logD("Switch fullscreen task to freeform on transition: taskId=%d", task.taskId) return WindowContainerTransaction().also { wct -> addMoveToDesktopChanges(wct, task) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 5a905cfd317f..e8eb10c984af 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -2456,6 +2456,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, final StageChangeRecord record = new StageChangeRecord(); final int transitType = info.getType(); TransitionInfo.Change pipChange = null; + int closingSplitTaskId = -1; for (int iC = 0; iC < info.getChanges().size(); ++iC) { final TransitionInfo.Change change = info.getChanges().get(iC); if (change.getMode() == TRANSIT_CHANGE @@ -2516,21 +2517,31 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, + " with " + taskInfo.taskId + " before startAnimation()."); } } + if (isClosingType(change.getMode()) && + getStageOfTask(change.getTaskInfo().taskId) != STAGE_TYPE_UNDEFINED) { + // If either one of the 2 stages is closing we're assuming we'll break split + closingSplitTaskId = change.getTaskInfo().taskId; + } } if (pipChange != null) { TransitionInfo.Change pipReplacingChange = getPipReplacingChange(info, pipChange, mMainStage.mRootTaskInfo.taskId, mSideStage.mRootTaskInfo.taskId, getSplitItemStage(pipChange.getLastParent())); - if (pipReplacingChange != null) { + boolean keepSplitWithPip = pipReplacingChange != null && closingSplitTaskId == -1; + if (keepSplitWithPip) { // Set an enter transition for when startAnimation gets called again mSplitTransitions.setEnterTransition(transition, /*remoteTransition*/ null, TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, /*resizeAnim*/ false); + } else { + int finalClosingTaskId = closingSplitTaskId; + mRecentTasks.ifPresent(recentTasks -> + recentTasks.removeSplitPair(finalClosingTaskId)); + logExit(EXIT_REASON_FULLSCREEN_REQUEST); } mMixedHandler.animatePendingEnterPipFromSplit(transition, info, - startTransaction, finishTransaction, finishCallback, - pipReplacingChange != null); + startTransaction, finishTransaction, finishCallback, keepSplitWithPip); notifySplitAnimationFinished(); return true; } @@ -2821,8 +2832,12 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } callbackWct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token, false); mWindowDecorViewModel.ifPresent(viewModel -> { - viewModel.onTaskInfoChanged(finalMainChild.getTaskInfo()); - viewModel.onTaskInfoChanged(finalSideChild.getTaskInfo()); + if (finalMainChild != null) { + viewModel.onTaskInfoChanged(finalMainChild.getTaskInfo()); + } + if (finalSideChild != null) { + viewModel.onTaskInfoChanged(finalSideChild.getTaskInfo()); + } }); mPausingTasks.clear(); }); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index dc27c82d5ffa..caac2f6bb03e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -862,6 +862,15 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { handleCaptionThroughStatusBar(e, decoration); final boolean wasDragging = mIsDragging; updateDragStatus(e.getActionMasked()); + final boolean upOrCancel = e.getActionMasked() == ACTION_UP + || e.getActionMasked() == ACTION_CANCEL; + if (wasDragging && upOrCancel) { + // When finishing a drag the event will be consumed, which means the pressed + // state of the App Handle must be manually reset to scale its drawable back to + // its original shape. This is necessary for drag gestures of the Handle that + // result in a cancellation (dragging back to the top). + v.setPressed(false); + } // Only prevent onClick from receiving this event if it's a drag. return wasDragging; } @@ -1182,8 +1191,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { : SPLIT_POSITION_TOP_OR_LEFT; final RunningTaskInfo oppositeTaskInfo = mSplitScreenController.getTaskInfo(oppositePosition); - mWindowDecorByTaskId.get(oppositeTaskInfo.taskId) - .disposeStatusBarInputLayer(); + if (oppositeTaskInfo != null) { + mWindowDecorByTaskId.get(oppositeTaskInfo.taskId) + .disposeStatusBarInputLayer(); + } } } mMoveToDesktopAnimator = null; 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 b1fc55f604d2..16036bee75b3 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 @@ -24,6 +24,8 @@ import static android.view.InsetsSource.FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR; import static android.view.MotionEvent.ACTION_CANCEL; import static android.view.MotionEvent.ACTION_DOWN; import static android.view.MotionEvent.ACTION_UP; +import static android.window.flags.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION; +import static android.window.flags.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS; import static com.android.launcher3.icons.BaseIconFactory.MODE_DEFAULT; import static com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON; @@ -604,13 +606,13 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin // their custom content. relayoutParams.mInputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_SPY; } else { - if (Flags.enableCaptionCompatInsetForceConsumption()) { + if (ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION.isEnabled()) { // Force-consume the caption bar insets when the app tries to hide the caption. // This improves app compatibility of immersive apps. relayoutParams.mInsetSourceFlags |= FLAG_FORCE_CONSUMING; } } - if (Flags.enableCaptionCompatInsetForceConsumptionAlways()) { + if (ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS.isEnabled()) { // Always force-consume the caption bar insets for maximum app compatibility, // including non-immersive apps that just don't handle caption insets properly. relayoutParams.mInsetSourceFlags |= FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java index 6f3f41191485..6eb5cca9ad1a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java @@ -26,6 +26,8 @@ import android.graphics.PointF; import android.graphics.Rect; import android.os.Handler; import android.os.IBinder; +import android.os.Looper; +import android.view.Choreographer; import android.view.Surface; import android.view.SurfaceControl; import android.window.TransitionInfo; @@ -124,6 +126,11 @@ public class VeiledResizeTaskPositioner implements TaskPositioner, Transitions.T @Override public Rect onDragPositioningMove(float x, float y) { + if (Looper.myLooper() != mHandler.getLooper()) { + // This method must run on the shell main thread to use the correct Choreographer + // instance below. + throw new IllegalStateException("This method must run on the shell main thread."); + } PointF delta = DragPositioningCallbackUtility.calculateDelta(x, y, mRepositionStartPoint); if (isResizing() && DragPositioningCallbackUtility.changeBounds(mCtrlType, mRepositionTaskBounds, mTaskBoundsAtDragStart, mStableBounds, delta, @@ -141,6 +148,7 @@ public class VeiledResizeTaskPositioner implements TaskPositioner, Transitions.T final SurfaceControl.Transaction t = mTransactionSupplier.get(); DragPositioningCallbackUtility.setPositionOnDrag(mDesktopWindowDecoration, mRepositionTaskBounds, mTaskBoundsAtDragStart, mRepositionStartPoint, t, x, y); + t.setFrameTimeline(Choreographer.getInstance().getVsyncId()); t.apply(); } return new Rect(mRepositionTaskBounds); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/DefaultWindowDecorViewHost.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/DefaultWindowDecorViewHost.kt index 5156e47cfd13..139e6790b744 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/DefaultWindowDecorViewHost.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/DefaultWindowDecorViewHost.kt @@ -19,33 +19,51 @@ import android.content.Context import android.content.res.Configuration import android.view.Display import android.view.SurfaceControl +import android.view.SurfaceControlViewHost import android.view.View import android.view.WindowManager +import android.view.WindowlessWindowManager import androidx.tracing.Trace import com.android.internal.annotations.VisibleForTesting import com.android.wm.shell.shared.annotations.ShellMainThread import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.launch +typealias SurfaceControlViewHostFactory = + (Context, Display, WindowlessWindowManager, String) -> SurfaceControlViewHost /** - * A default implementation of [WindowDecorViewHost] backed by a [SurfaceControlViewHostAdapter]. + * A default implementation of [WindowDecorViewHost] backed by a [SurfaceControlViewHost]. * - * It supports asynchronously updating the view hierarchy using [updateViewAsync], in which + * It does not support swapping the root view added to the VRI of the [SurfaceControlViewHost], and + * any attempts to do will throw, which means that once a [View] is added using [updateView] or + * [updateViewAsync], only its properties and binding may be changed, its children views may be + * added, removed or changed and its [WindowManager.LayoutParams] may be changed. + * It also supports asynchronously updating the view hierarchy using [updateViewAsync], in which * case the update work will be posted on the [ShellMainThread] with no delay. */ class DefaultWindowDecorViewHost( - context: Context, + private val context: Context, @ShellMainThread private val mainScope: CoroutineScope, - display: Display, - @VisibleForTesting val viewHostAdapter: SurfaceControlViewHostAdapter = - SurfaceControlViewHostAdapter(context, display) + private val display: Display, + private val surfaceControlViewHostFactory: SurfaceControlViewHostFactory = { c, d, wwm, s -> + SurfaceControlViewHost(c, d, wwm, s) + } ) : WindowDecorViewHost { + private val rootSurface: SurfaceControl = SurfaceControl.Builder() + .setName("DefaultWindowDecorViewHost surface") + .setContainerLayer() + .setCallsite("DefaultWindowDecorViewHost#init") + .build() + + private var wwm: WindowlessWindowManager? = null + @VisibleForTesting + var viewHost: SurfaceControlViewHost? = null private var currentUpdateJob: Job? = null override val surfaceControl: SurfaceControl - get() = viewHostAdapter.rootSurface + get() = rootSurface override fun updateView( view: View, @@ -74,7 +92,8 @@ class DefaultWindowDecorViewHost( override fun release(t: SurfaceControl.Transaction) { clearCurrentUpdateJob() - viewHostAdapter.release(t) + viewHost?.release() + t.remove(rootSurface) } private fun updateViewHost( @@ -83,15 +102,45 @@ class DefaultWindowDecorViewHost( configuration: Configuration, onDrawTransaction: SurfaceControl.Transaction? ) { - viewHostAdapter.prepareViewHost(configuration) + Trace.beginSection("DefaultWindowDecorViewHost#updateViewHost") + if (wwm == null) { + wwm = WindowlessWindowManager(configuration, rootSurface, null) + } + requireWindowlessWindowManager().setConfiguration(configuration) + if (viewHost == null) { + viewHost = surfaceControlViewHostFactory.invoke( + context, + display, + requireWindowlessWindowManager(), + "DefaultWindowDecorViewHost#updateViewHost" + ) + } onDrawTransaction?.let { - viewHostAdapter.applyTransactionOnDraw(it) + requireViewHost().rootSurfaceControl.applyTransactionOnDraw(it) + } + if (requireViewHost().view == null) { + Trace.beginSection("DefaultWindowDecorViewHost#updateViewHost-setView") + requireViewHost().setView(view, attrs) + Trace.endSection() + } else { + check(requireViewHost().view == view) { "Changing view is not allowed" } + Trace.beginSection("DefaultWindowDecorViewHost#updateViewHost-relayout") + requireViewHost().relayout(attrs) + Trace.endSection() } - viewHostAdapter.updateView(view, attrs) + Trace.endSection() } private fun clearCurrentUpdateJob() { currentUpdateJob?.cancel() currentUpdateJob = null } + + private fun requireWindowlessWindowManager(): WindowlessWindowManager { + return wwm ?: error("Expected non-null windowless window manager") + } + + private fun requireViewHost(): SurfaceControlViewHost { + return viewHost ?: error("Expected non-null view host") + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/PooledWindowDecorViewHostSupplier.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/PooledWindowDecorViewHostSupplier.kt deleted file mode 100644 index b04188fa82a8..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/PooledWindowDecorViewHostSupplier.kt +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.wm.shell.windowdecor.viewhost - -import android.content.Context -import android.os.Trace -import android.util.Pools -import android.view.Display -import android.view.SurfaceControl -import com.android.wm.shell.shared.annotations.ShellMainThread -import com.android.wm.shell.sysui.ShellInit -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch - -/** - * A [WindowDecorViewHostSupplier] backed by a pool to allow recycling view hosts which may be - * expensive to recreate for each new/updated window decoration. - * - * Callers can obtain [ReusableWindowDecorViewHost] using [acquire], which will return a pooled - * object if available, or create a new instance and return it if needed. When done using a - * [ReusableWindowDecorViewHost], it must be released using [release] to allow it to be sent back - * into the pool and reused later on. - * - * This class also supports pre-warming [ReusableWindowDecorViewHost] instances, which will be put - * into the pool immediately after creation. - */ -class PooledWindowDecorViewHostSupplier( - private val context: Context, - @ShellMainThread private val mainScope: CoroutineScope, - shellInit: ShellInit, - private val viewHostFactory: ReusableWindowDecorViewHost.Factory = - ReusableWindowDecorViewHost.DefaultFactory, - maxPoolSize: Int, - private val preWarmSize: Int, -) : WindowDecorViewHostSupplier<ReusableWindowDecorViewHost> { - - private val pool: Pools.Pool<ReusableWindowDecorViewHost> = Pools.SynchronizedPool(maxPoolSize) - private var nextDecorViewHostId = 0 - - init { - require(preWarmSize <= maxPoolSize) { "Pre-warm size should not exceed pool size" } - shellInit.addInitCallback(this::onShellInit, this) - } - - private fun onShellInit() { - if (preWarmSize <= 0) { - return - } - preWarmViewHosts(preWarmSize) - } - - private fun preWarmViewHosts(preWarmSize: Int) { - mainScope.launch { - // Applying isn't needed, as the surface was never actually shown. - val t = SurfaceControl.Transaction() - repeat(preWarmSize) { - val warmedViewHost = create(context, context.display).apply { - warmUp() - } - // Put the warmed view host in the pool by releasing it. - release(warmedViewHost, t) - } - } - } - - override fun acquire(context: Context, display: Display): ReusableWindowDecorViewHost { - val reusedDecorViewHost = pool.acquire() - if (reusedDecorViewHost != null) { - return reusedDecorViewHost - } - Trace.beginSection("WindowDecorViewHostPool#acquire-new") - val newDecorViewHost = create(context, display) - Trace.endSection() - return newDecorViewHost - } - - override fun release(viewHost: ReusableWindowDecorViewHost, t: SurfaceControl.Transaction) { - val cached = pool.release(viewHost) - if (!cached) { - viewHost.release(t) - } - } - - private fun create(context: Context, display: Display): ReusableWindowDecorViewHost { - return viewHostFactory.create( - context, - mainScope, - display, - nextDecorViewHostId++ - ) - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/ReusableWindowDecorViewHost.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/ReusableWindowDecorViewHost.kt deleted file mode 100644 index 64536d1a7897..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/ReusableWindowDecorViewHost.kt +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.wm.shell.windowdecor.viewhost - -import android.content.Context -import android.content.res.Configuration -import android.graphics.PixelFormat -import android.os.Trace -import android.view.Display -import android.view.SurfaceControl -import android.view.SurfaceControlViewHost -import android.view.View -import android.view.WindowManager -import android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE -import android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH -import android.view.WindowManager.LayoutParams.TYPE_APPLICATION -import android.widget.FrameLayout -import com.android.internal.annotations.VisibleForTesting -import com.android.wm.shell.shared.annotations.ShellMainThread -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job -import kotlinx.coroutines.launch - -/** - * An implementation of [WindowDecorViewHost] that supports: - * 1) Replacing the root [View], meaning [WindowDecorViewHost.updateView] maybe be - * called with different [View] instances. This is useful when reusing [WindowDecorViewHost]s - * instances for vastly different view hierarchies, such as Desktop Windowing's App Handles and - * App Headers. - * 2) Pre-warming of the underlying [SurfaceControlViewHost]s. Useful because their creation and - * first root view assignment are expensive, which is undesirable in latency-sensitive code - * paths like during a shell transition. - */ -class ReusableWindowDecorViewHost( - private val context: Context, - @ShellMainThread private val mainScope: CoroutineScope, - display: Display, - val id: Int, - @VisibleForTesting val viewHostAdapter: SurfaceControlViewHostAdapter = - SurfaceControlViewHostAdapter(context, display) -) : WindowDecorViewHost, Warmable { - - @VisibleForTesting - val rootView = FrameLayout(context) - - private var currentUpdateJob: Job? = null - - override val surfaceControl: SurfaceControl - get() = viewHostAdapter.rootSurface - - override fun warmUp() { - if (viewHostAdapter.isInitialized()) { - // Already warmed up. - return - } - Trace.beginSection("$TAG#warmUp") - viewHostAdapter.prepareViewHost(context.resources.configuration) - viewHostAdapter.updateView( - rootView, - WindowManager.LayoutParams( - 0 /* width*/, - 0 /* height */, - TYPE_APPLICATION, - FLAG_NOT_FOCUSABLE or FLAG_SPLIT_TOUCH, - PixelFormat.TRANSPARENT - ).apply { - setTitle("View root of $TAG#$id") - setTrustedOverlay() - } - ) - Trace.endSection() - } - - override fun updateView( - view: View, - attrs: WindowManager.LayoutParams, - configuration: Configuration, - onDrawTransaction: SurfaceControl.Transaction? - ) { - clearCurrentUpdateJob() - updateViewHost(view, attrs, configuration, onDrawTransaction) - } - - override fun updateViewAsync( - view: View, - attrs: WindowManager.LayoutParams, - configuration: Configuration - ) { - clearCurrentUpdateJob() - currentUpdateJob = mainScope.launch { - updateViewHost(view, attrs, configuration, onDrawTransaction = null) - } - } - - override fun release(t: SurfaceControl.Transaction) { - clearCurrentUpdateJob() - viewHostAdapter.release(t) - } - - private fun updateViewHost( - view: View, - attrs: WindowManager.LayoutParams, - configuration: Configuration, - onDrawTransaction: SurfaceControl.Transaction? - ) { - viewHostAdapter.prepareViewHost(configuration) - onDrawTransaction?.let { - viewHostAdapter.applyTransactionOnDraw(it) - } - rootView.removeAllViews() - rootView.addView(view) - viewHostAdapter.updateView(rootView, attrs) - } - - private fun clearCurrentUpdateJob() { - currentUpdateJob?.cancel() - currentUpdateJob = null - } - - interface Factory { - fun create( - context: Context, - @ShellMainThread mainScope: CoroutineScope, - display: Display, - id: Int - ): ReusableWindowDecorViewHost - } - - object DefaultFactory : Factory { - override fun create( - context: Context, - @ShellMainThread mainScope: CoroutineScope, - display: Display, - id: Int - ): ReusableWindowDecorViewHost { - return ReusableWindowDecorViewHost( - context, - mainScope, - display, - id - ) - } - } - - companion object { - private const val TAG = "ReusableWindowDecorViewHost" - } -}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/SurfaceControlViewHostAdapter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/SurfaceControlViewHostAdapter.kt deleted file mode 100644 index a54c9ba67cf8..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/SurfaceControlViewHostAdapter.kt +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.wm.shell.windowdecor.viewhost - -import android.content.Context -import android.content.res.Configuration -import android.view.AttachedSurfaceControl -import android.view.Display -import android.view.SurfaceControl -import android.view.SurfaceControlViewHost -import android.view.View -import android.view.WindowManager -import android.view.WindowlessWindowManager -import androidx.tracing.Trace -import com.android.internal.annotations.VisibleForTesting -typealias SurfaceControlViewHostFactory = - (Context, Display, WindowlessWindowManager, String) -> SurfaceControlViewHost - -/** - * Adapter for a [SurfaceControlViewHost] and its backing [SurfaceControl]. - * - * It does not support swapping the root view added to the VRI of the [SurfaceControlViewHost], and - * any attempts to do will throw, which means that once a [View] is added using [updateView], only - * its properties and binding may be changed, its children views may be added, removed or changed - * and its [WindowManager.LayoutParams] may be changed. - */ -class SurfaceControlViewHostAdapter( - private val context: Context, - private val display: Display, - private val surfaceControlViewHostFactory: SurfaceControlViewHostFactory = { c, d, wwm, s -> - SurfaceControlViewHost(c, d, wwm, s) - } -) { - val rootSurface: SurfaceControl = SurfaceControl.Builder() - .setName("SurfaceControlViewHostAdapter surface") - .setContainerLayer() - .setCallsite("SurfaceControlViewHostAdapter#init") - .build() - - private var wwm: WindowlessWindowManager? = null - @VisibleForTesting - var viewHost: SurfaceControlViewHost? = null - - /** Initialize the [SurfaceControlViewHost] if needed. */ - fun prepareViewHost(configuration: Configuration) { - if (wwm == null) { - wwm = WindowlessWindowManager(configuration, rootSurface, null) - } - requireWindowlessWindowManager().setConfiguration(configuration) - if (viewHost == null) { - viewHost = surfaceControlViewHostFactory.invoke( - context, - display, - requireWindowlessWindowManager(), - "SurfaceControlViewHostAdapter#prepareViewHost" - ) - } - } - - /** - * Request to apply the transaction atomically with the next draw of the view hierarchy. - * See [AttachedSurfaceControl.applyTransactionOnDraw]. - */ - fun applyTransactionOnDraw(t: SurfaceControl.Transaction) { - requireViewHost().rootSurfaceControl.applyTransactionOnDraw(t) - } - - /** Update the view hierarchy of the view host. */ - fun updateView(view: View, attrs: WindowManager.LayoutParams) { - if (requireViewHost().view == null) { - Trace.beginSection("SurfaceControlViewHostAdapter#updateView-setView") - requireViewHost().setView(view, attrs) - Trace.endSection() - } else { - check(requireViewHost().view == view) { "Changing view is not allowed" } - Trace.beginSection("SurfaceControlViewHostAdapter#updateView-relayout") - requireViewHost().relayout(attrs) - Trace.endSection() - } - } - - /** Release the view host and remove the backing surface. */ - fun release(t: SurfaceControl.Transaction) { - viewHost?.release() - t.remove(rootSurface) - } - - /** Whether the view host has had a view hierarchy set. */ - fun isInitialized(): Boolean = viewHost?.view != null - - private fun requireWindowlessWindowManager(): WindowlessWindowManager { - return wwm ?: error("Expected non-null windowless window manager") - } - - private fun requireViewHost(): SurfaceControlViewHost { - return viewHost ?: error("Expected non-null view host") - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/Warmable.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/Warmable.kt deleted file mode 100644 index 0df9bfa2ee78..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/Warmable.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.wm.shell.windowdecor.viewhost - -/** - * An interface for an object that can be warmed up before it's needed. - */ -interface Warmable { - fun warmUp() -} 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 e610ebd6bfab..8f20841e76b3 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 @@ -1764,6 +1764,37 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + fun handleRequest_freeformTask_relaunchTask_enforceDesktop_freeformDisplay_noWinModeChange() { + assumeTrue(ENABLE_SHELL_TRANSITIONS) + whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true) + val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! + tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM + + val freeformTask = setUpFreeformTask() + markTaskHidden(freeformTask) + val wct = controller.handleRequest(Binder(), createTransition(freeformTask)) + + assertNotNull(wct, "should handle request") + assertFalse(wct.anyWindowingModeChange(freeformTask.token)) + } + + @Test + fun handleRequest_freeformTask_relaunchTask_enforceDesktop_fullscreenDisplay_becomesUndefined() { + assumeTrue(ENABLE_SHELL_TRANSITIONS) + whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true) + val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! + tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN + + val freeformTask = setUpFreeformTask() + markTaskHidden(freeformTask) + val wct = controller.handleRequest(Binder(), createTransition(freeformTask)) + + assertNotNull(wct, "should handle request") + assertThat(wct.changes[freeformTask.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_UNDEFINED) + } + + @Test @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) fun handleRequest_freeformTask_desktopWallpaperDisabled_freeformNotVisible_reorderedToTop() { assumeTrue(ENABLE_SHELL_TRANSITIONS) @@ -3493,6 +3524,14 @@ private fun WindowContainerTransaction?.anyDensityConfigChange( } ?: false } +private fun WindowContainerTransaction?.anyWindowingModeChange( + token: WindowContainerToken +): Boolean { +return this?.changes?.any { change -> + change.key == token.asBinder() && change.value.windowingMode >= 0 +} ?: false +} + private fun createTaskInfo(id: Int) = RecentTaskInfo().apply { taskId = id diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt index ab41d9c80177..1273ee823159 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt @@ -23,6 +23,7 @@ import android.graphics.Point import android.graphics.Rect import android.os.Handler import android.os.IBinder +import android.os.Looper import android.testing.AndroidTestingRunner import android.view.Display import android.view.Surface.ROTATION_0 @@ -34,6 +35,7 @@ import android.view.WindowManager.TRANSIT_CHANGE import android.window.TransitionInfo import android.window.WindowContainerToken import androidx.test.filters.SmallTest +import androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread import com.android.internal.jank.InteractionJankMonitor import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.ShellTestCase @@ -108,8 +110,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { private lateinit var mockResources: Resources @Mock private lateinit var mockInteractionJankMonitor: InteractionJankMonitor - @Mock - private lateinit var mockHandler: Handler + private val mainHandler = Handler(Looper.getMainLooper()) private lateinit var taskPositioner: VeiledResizeTaskPositioner @@ -159,12 +160,12 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { mockTransactionFactory, mockTransitions, mockInteractionJankMonitor, - mockHandler, + mainHandler, ) } @Test - fun testDragResize_noMove_doesNotShowResizeVeil() { + fun testDragResize_noMove_doesNotShowResizeVeil() = runOnUiThread { taskPositioner.onDragPositioningStart( CTRL_TYPE_TOP or CTRL_TYPE_RIGHT, STARTING_BOUNDS.left.toFloat(), @@ -176,6 +177,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { STARTING_BOUNDS.left.toFloat(), STARTING_BOUNDS.top.toFloat() ) + verify(mockTransitions, never()).startTransition(eq(TRANSIT_CHANGE), argThat { wct -> return@argThat wct.changes.any { (token, change) -> token == taskBinder && @@ -186,7 +188,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { } @Test - fun testDragResize_movesTask_doesNotShowResizeVeil() { + fun testDragResize_movesTask_doesNotShowResizeVeil() = runOnUiThread { taskPositioner.onDragPositioningStart( CTRL_TYPE_UNDEFINED, STARTING_BOUNDS.left.toFloat(), @@ -221,7 +223,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { } @Test - fun testDragResize_resize_boundsUpdateOnEnd() { + fun testDragResize_resize_boundsUpdateOnEnd() = runOnUiThread { taskPositioner.onDragPositioningStart( CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, STARTING_BOUNDS.right.toFloat(), @@ -262,7 +264,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { } @Test - fun testDragResize_noEffectiveMove_skipsTransactionOnEnd() { + fun testDragResize_noEffectiveMove_skipsTransactionOnEnd() = runOnUiThread { taskPositioner.onDragPositioningStart( CTRL_TYPE_TOP or CTRL_TYPE_RIGHT, STARTING_BOUNDS.left.toFloat(), @@ -294,9 +296,8 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { }) } - @Test - fun testDragResize_drag_setBoundsNotRunIfDragEndsInDisallowedEndArea() { + fun testDragResize_drag_setBoundsNotRunIfDragEndsInDisallowedEndArea() = runOnUiThread { taskPositioner.onDragPositioningStart( CTRL_TYPE_UNDEFINED, // drag STARTING_BOUNDS.left.toFloat(), @@ -321,7 +322,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { } @Test - fun testDragResize_resize_resizingTaskReorderedToTopWhenNotFocused() { + fun testDragResize_resize_resizingTaskReorderedToTopWhenNotFocused() = runOnUiThread { mockDesktopWindowDecoration.mTaskInfo.isFocused = false taskPositioner.onDragPositioningStart( CTRL_TYPE_RIGHT, // Resize right @@ -337,7 +338,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { } @Test - fun testDragResize_resize_resizingTaskNotReorderedToTopWhenFocused() { + fun testDragResize_resize_resizingTaskNotReorderedToTopWhenFocused() = runOnUiThread { mockDesktopWindowDecoration.mTaskInfo.isFocused = true taskPositioner.onDragPositioningStart( CTRL_TYPE_RIGHT, // Resize right @@ -353,7 +354,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { } @Test - fun testDragResize_drag_draggedTaskNotReorderedToTop() { + fun testDragResize_drag_draggedTaskNotReorderedToTop() = runOnUiThread { mockDesktopWindowDecoration.mTaskInfo.isFocused = false taskPositioner.onDragPositioningStart( CTRL_TYPE_UNDEFINED, // drag @@ -370,7 +371,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { } @Test - fun testDragResize_drag_updatesStableBoundsOnRotate() { + fun testDragResize_drag_updatesStableBoundsOnRotate() = runOnUiThread { // Test landscape stable bounds performDrag(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(), STARTING_BOUNDS.right.toFloat() + 2000, STARTING_BOUNDS.bottom.toFloat() + 2000, @@ -416,7 +417,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { } @Test - fun testIsResizingOrAnimatingResizeSet() { + fun testIsResizingOrAnimatingResizeSet() = runOnUiThread { Assert.assertFalse(taskPositioner.isResizingOrAnimating) taskPositioner.onDragPositioningStart( @@ -443,7 +444,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { } @Test - fun testIsResizingOrAnimatingResizeResetAfterStartAnimation() { + fun testIsResizingOrAnimatingResizeResetAfterStartAnimation() = runOnUiThread { performDrag( STARTING_BOUNDS.left.toFloat(), STARTING_BOUNDS.top.toFloat(), STARTING_BOUNDS.left.toFloat() - 20, STARTING_BOUNDS.top.toFloat() - 20, @@ -457,7 +458,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { } @Test - fun testStartAnimation_useEndRelOffset() { + fun testStartAnimation_useEndRelOffset() = runOnUiThread { val changeMock = mock(TransitionInfo.Change::class.java) val startTransaction = mock(Transaction::class.java) val finishTransaction = mock(Transaction::class.java) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/DefaultWindowDecorViewHostTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/DefaultWindowDecorViewHostTest.kt index 1b0b7d95e657..1b2ce9e4df36 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/DefaultWindowDecorViewHostTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/DefaultWindowDecorViewHostTest.kt @@ -18,6 +18,7 @@ package com.android.wm.shell.windowdecor.viewhost import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.SurfaceControl +import android.view.SurfaceControlViewHost import android.view.View import android.view.WindowManager import androidx.test.filters.SmallTest @@ -27,6 +28,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest +import org.junit.Assert.assertThrows import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.mock @@ -57,8 +59,54 @@ class DefaultWindowDecorViewHostTest : ShellTestCase() { onDrawTransaction = null ) - assertThat(windowDecorViewHost.viewHostAdapter.isInitialized()).isTrue() - assertThat(windowDecorViewHost.view()).isEqualTo(view) + assertThat(windowDecorViewHost.viewHost).isNotNull() + assertThat(windowDecorViewHost.viewHost!!.view).isEqualTo(view) + } + + @Test + fun updateView_alreadyLaidOut_relayouts() = runTest { + val windowDecorViewHost = createDefaultViewHost() + val view = View(context) + windowDecorViewHost.updateView( + view = view, + attrs = WindowManager.LayoutParams(100, 100), + configuration = context.resources.configuration, + onDrawTransaction = null + ) + + val otherParams = WindowManager.LayoutParams(200, 200) + windowDecorViewHost.updateView( + view = view, + attrs = otherParams, + configuration = context.resources.configuration, + onDrawTransaction = null + ) + + assertThat(windowDecorViewHost.viewHost!!.view).isEqualTo(view) + assertThat(windowDecorViewHost.viewHost!!.view!!.layoutParams.width) + .isEqualTo(otherParams.width) + } + + @Test + fun updateView_replacingView_throws() = runTest { + val windowDecorViewHost = createDefaultViewHost() + val view = View(context) + windowDecorViewHost.updateView( + view = view, + attrs = WindowManager.LayoutParams(100, 100), + configuration = context.resources.configuration, + onDrawTransaction = null + ) + + val otherView = View(context) + assertThrows(Exception::class.java) { + windowDecorViewHost.updateView( + view = otherView, + attrs = WindowManager.LayoutParams(100, 100), + configuration = context.resources.configuration, + onDrawTransaction = null + ) + } } @OptIn(ExperimentalCoroutinesApi::class) @@ -77,7 +125,7 @@ class DefaultWindowDecorViewHostTest : ShellTestCase() { ) // No view host yet, since the coroutine hasn't run. - assertThat(windowDecorViewHost.viewHostAdapter.isInitialized()).isFalse() + assertThat(windowDecorViewHost.viewHost).isNull() windowDecorViewHost.updateView( view = syncView, @@ -89,13 +137,14 @@ class DefaultWindowDecorViewHostTest : ShellTestCase() { // Would run coroutine if it hadn't been cancelled. advanceUntilIdle() - assertThat(windowDecorViewHost.viewHostAdapter.isInitialized()).isTrue() - assertThat(windowDecorViewHost.view()).isNotNull() + assertThat(windowDecorViewHost.viewHost).isNotNull() + assertThat(windowDecorViewHost.viewHost!!.view).isNotNull() // View host view/attrs should match the ones from the sync call, plus, since the // sync/async were made with different views, if the job hadn't been cancelled there // would've been an exception thrown as replacing views isn't allowed. - assertThat(windowDecorViewHost.view()).isEqualTo(syncView) - assertThat(windowDecorViewHost.view()!!.layoutParams.width).isEqualTo(syncAttrs.width) + assertThat(windowDecorViewHost.viewHost!!.view).isEqualTo(syncView) + assertThat(windowDecorViewHost.viewHost!!.view!!.layoutParams.width) + .isEqualTo(syncAttrs.width) } @OptIn(ExperimentalCoroutinesApi::class) @@ -111,11 +160,11 @@ class DefaultWindowDecorViewHostTest : ShellTestCase() { configuration = context.resources.configuration, ) - assertThat(windowDecorViewHost.viewHostAdapter.isInitialized()).isFalse() + assertThat(windowDecorViewHost.viewHost).isNull() advanceUntilIdle() - assertThat(windowDecorViewHost.viewHostAdapter.isInitialized()).isTrue() + assertThat(windowDecorViewHost.viewHost).isNotNull() } @OptIn(ExperimentalCoroutinesApi::class) @@ -138,8 +187,9 @@ class DefaultWindowDecorViewHostTest : ShellTestCase() { advanceUntilIdle() - assertThat(windowDecorViewHost.viewHostAdapter.isInitialized()).isTrue() - assertThat(windowDecorViewHost.view()).isEqualTo(otherView) + assertThat(windowDecorViewHost.viewHost).isNotNull() + assertThat(windowDecorViewHost.viewHost!!.view).isNotNull() + assertThat(windowDecorViewHost.viewHost!!.view).isEqualTo(otherView) } @Test @@ -157,15 +207,16 @@ class DefaultWindowDecorViewHostTest : ShellTestCase() { val t = mock(SurfaceControl.Transaction::class.java) windowDecorViewHost.release(t) - verify(windowDecorViewHost.viewHostAdapter).release(t) + verify(windowDecorViewHost.viewHost!!).release() + verify(t).remove(windowDecorViewHost.surfaceControl) } private fun CoroutineScope.createDefaultViewHost() = DefaultWindowDecorViewHost( context = context, mainScope = this, display = context.display, - viewHostAdapter = spy(SurfaceControlViewHostAdapter(context, context.display)), + surfaceControlViewHostFactory = { c, d, wwm, s -> + spy(SurfaceControlViewHost(c, d, wwm, s)) + } ) - - private fun DefaultWindowDecorViewHost.view(): View? = viewHostAdapter.viewHost?.view }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/PooledWindowDecorViewHostSupplierTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/PooledWindowDecorViewHostSupplierTest.kt deleted file mode 100644 index a7e4213ad01d..000000000000 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/PooledWindowDecorViewHostSupplierTest.kt +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.wm.shell.windowdecor.viewhost - -import android.testing.AndroidTestingRunner -import android.view.SurfaceControl -import androidx.test.filters.SmallTest -import com.android.wm.shell.ShellTestCase -import com.android.wm.shell.TestShellExecutor -import com.android.wm.shell.sysui.ShellInit -import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.advanceUntilIdle -import kotlinx.coroutines.test.runTest -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.Mock -import org.mockito.MockitoAnnotations -import org.mockito.kotlin.any -import org.mockito.kotlin.mock -import org.mockito.kotlin.never -import org.mockito.kotlin.verify -import org.mockito.kotlin.whenever - -/** - * Tests for [PooledWindowDecorViewHostSupplier]. - * - * Build/Install/Run: - * atest WMShellUnitTests:PooledWindowDecorViewHostSupplierTest - */ -@OptIn(ExperimentalCoroutinesApi::class) -@SmallTest -@RunWith(AndroidTestingRunner::class) -class PooledWindowDecorViewHostSupplierTest : ShellTestCase() { - - private val testExecutor = TestShellExecutor() - private val testShellInit = ShellInit(testExecutor) - @Mock - private lateinit var mockViewHostFactory: ReusableWindowDecorViewHost.Factory - - private lateinit var supplier: PooledWindowDecorViewHostSupplier - - @Test - fun setUp() { - MockitoAnnotations.initMocks(this) - } - - @Test - fun onInit_warmsAndPoolsViewHosts() = runTest { - supplier = createSupplier(maxPoolSize = 5, preWarmSize = 2) - val mockViewHost1 = mock<ReusableWindowDecorViewHost>() - val mockViewHost2 = mock<ReusableWindowDecorViewHost>() - whenever(mockViewHostFactory - .create(context, this, context.display, id = 0)) - .thenReturn(mockViewHost1) - whenever(mockViewHostFactory - .create(context, this, context.display, id = 1)) - .thenReturn(mockViewHost2) - - testExecutor.flushAll() - advanceUntilIdle() - - // Both were warmed up. - verify(mockViewHost1).warmUp() - verify(mockViewHost2).warmUp() - // Both were released, so re-acquiring them provides the same instance. - assertThat(mockViewHost2) - .isEqualTo(supplier.acquire(context, context.display)) - assertThat(mockViewHost1) - .isEqualTo(supplier.acquire(context, context.display)) - } - - @Test(expected = Throwable::class) - fun onInit_warmUpSizeExceedsPoolSize_throws() = runTest { - createSupplier(maxPoolSize = 3, preWarmSize = 4) - } - - @Test - fun acquire_poolHasInstances_reuses() = runTest { - supplier = createSupplier(maxPoolSize = 5, preWarmSize = 0) - - // Prepare the pool with one instance. - val mockViewHost = mock<ReusableWindowDecorViewHost>() - supplier.release(mockViewHost, SurfaceControl.Transaction()) - - assertThat(mockViewHost) - .isEqualTo(supplier.acquire(context, context.display)) - verify(mockViewHostFactory, never()).create(any(), any(), any(), any()) - } - - @Test - fun acquire_pooledHasZeroInstances_creates() = runTest { - supplier = createSupplier(maxPoolSize = 5, preWarmSize = 0) - - supplier.acquire(context, context.display) - - verify(mockViewHostFactory).create(context, this, context.display, id = 0) - } - - @Test - fun release_poolBelowLimit_caches() = runTest { - supplier = createSupplier(maxPoolSize = 5, preWarmSize = 0) - - val mockViewHost = mock<ReusableWindowDecorViewHost>() - val mockT = mock<SurfaceControl.Transaction>() - supplier.release(mockViewHost, mockT) - - assertThat(mockViewHost) - .isEqualTo(supplier.acquire(context, context.display)) - } - - @Test - fun release_poolBelowLimit_doesNotReleaseViewHost() = runTest { - supplier = createSupplier(maxPoolSize = 5, preWarmSize = 0) - - val mockViewHost = mock<ReusableWindowDecorViewHost>() - val mockT = mock<SurfaceControl.Transaction>() - supplier.release(mockViewHost, mockT) - - verify(mockViewHost, never()).release(mockT) - } - - @Test - fun release_poolAtLimit_doesNotCache() = runTest { - supplier = createSupplier(maxPoolSize = 1, preWarmSize = 0) - val mockT = mock<SurfaceControl.Transaction>() - val mockViewHost = mock<ReusableWindowDecorViewHost>() - supplier.release(mockViewHost, mockT) // Maxes pool. - - val mockViewHost2 = mock<ReusableWindowDecorViewHost>() - supplier.release(mockViewHost2, mockT) // Beyond limit. - - assertThat(mockViewHost) - .isEqualTo(supplier.acquire(context, context.display)) - // Second one wasn't cached, so the acquired one should've been a new instance. - assertThat(mockViewHost2) - .isNotEqualTo(supplier.acquire(context, context.display)) - } - - @Test - fun release_poolAtLimit_releasesViewHost() = runTest { - supplier = createSupplier(maxPoolSize = 1, preWarmSize = 0) - val mockT = mock<SurfaceControl.Transaction>() - val mockViewHost = mock<ReusableWindowDecorViewHost>() - supplier.release(mockViewHost, mockT) // Maxes pool. - - val mockViewHost2 = mock<ReusableWindowDecorViewHost>() - supplier.release(mockViewHost2, mockT) // Beyond limit. - - // Second one doesn't fit, so it needs to be released. - verify(mockViewHost2).release(mockT) - } - - private fun CoroutineScope.createSupplier( - maxPoolSize: Int, - preWarmSize: Int - ) = PooledWindowDecorViewHostSupplier( - context, - this, - testShellInit, - mockViewHostFactory, - maxPoolSize, - preWarmSize - ).also { - testShellInit.init() - } -} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/ReusableWindowDecorViewHostTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/ReusableWindowDecorViewHostTest.kt deleted file mode 100644 index de2444e34ca9..000000000000 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/ReusableWindowDecorViewHostTest.kt +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.wm.shell.windowdecor.viewhost - -import android.testing.AndroidTestingRunner -import android.testing.TestableLooper -import android.view.SurfaceControl -import android.view.View -import android.view.WindowManager -import androidx.test.filters.SmallTest -import com.android.wm.shell.ShellTestCase -import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.advanceUntilIdle -import kotlinx.coroutines.test.runTest -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.Mockito.mock -import org.mockito.kotlin.spy -import org.mockito.kotlin.verify - -/** - * Tests for [ReusableWindowDecorViewHost]. - * - * Build/Install/Run: - * atest WMShellUnitTests:ReusableWindowDecorViewHostTest - */ -@SmallTest -@TestableLooper.RunWithLooper -@RunWith(AndroidTestingRunner::class) -class ReusableWindowDecorViewHostTest : ShellTestCase() { - - @Test - fun warmUp_addsRootView() = runTest { - val reusableVH = createReusableViewHost().apply { - warmUp() - } - - assertThat(reusableVH.viewHostAdapter.isInitialized()).isTrue() - assertThat(reusableVH.view()).isEqualTo(reusableVH.rootView) - } - - @Test - fun update_differentView_replacesView() = runTest { - val view = View(context) - val lp = WindowManager.LayoutParams() - val reusableVH = createReusableViewHost() - reusableVH.updateView(view, lp, context.resources.configuration, null) - - assertThat(reusableVH.rootView.childCount).isEqualTo(1) - assertThat(reusableVH.rootView.getChildAt(0)).isEqualTo(view) - - val newView = View(context) - val newLp = WindowManager.LayoutParams() - reusableVH.updateView(newView, newLp, context.resources.configuration, null) - - assertThat(reusableVH.rootView.childCount).isEqualTo(1) - assertThat(reusableVH.rootView.getChildAt(0)).isEqualTo(newView) - } - - @OptIn(ExperimentalCoroutinesApi::class) - @Test - fun updateView_clearsPendingAsyncJob() = runTest { - val reusableVH = createReusableViewHost() - val asyncView = View(context) - val syncView = View(context) - val asyncAttrs = WindowManager.LayoutParams(100, 100) - val syncAttrs = WindowManager.LayoutParams(200, 200) - - reusableVH.updateViewAsync( - view = asyncView, - attrs = asyncAttrs, - configuration = context.resources.configuration, - ) - - // No view host yet, since the coroutine hasn't run. - assertThat(reusableVH.viewHostAdapter.isInitialized()).isFalse() - - reusableVH.updateView( - view = syncView, - attrs = syncAttrs, - configuration = context.resources.configuration, - onDrawTransaction = null - ) - - // Would run coroutine if it hadn't been cancelled. - advanceUntilIdle() - - assertThat(reusableVH.viewHostAdapter.isInitialized()).isTrue() - // View host view/attrs should match the ones from the sync call, plus, since the - // sync/async were made with different views, if the job hadn't been cancelled there - // would've been an exception thrown as replacing views isn't allowed. - assertThat(reusableVH.rootView.getChildAt(0)).isEqualTo(syncView) - assertThat(reusableVH.view()!!.layoutParams.width).isEqualTo(syncAttrs.width) - } - - @OptIn(ExperimentalCoroutinesApi::class) - @Test - fun updateViewAsync() = runTest { - val reusableVH = createReusableViewHost() - val view = View(context) - val attrs = WindowManager.LayoutParams(100, 100) - - reusableVH.updateViewAsync( - view = view, - attrs = attrs, - configuration = context.resources.configuration, - ) - - assertThat(reusableVH.viewHostAdapter.isInitialized()).isFalse() - - advanceUntilIdle() - - assertThat(reusableVH.viewHostAdapter.isInitialized()).isTrue() - } - - @OptIn(ExperimentalCoroutinesApi::class) - @Test - fun updateViewAsync_clearsPendingAsyncJob() = runTest { - val reusableVH = createReusableViewHost() - - val view = View(context) - reusableVH.updateViewAsync( - view = view, - attrs = WindowManager.LayoutParams(100, 100), - configuration = context.resources.configuration, - ) - val otherView = View(context) - reusableVH.updateViewAsync( - view = otherView, - attrs = WindowManager.LayoutParams(100, 100), - configuration = context.resources.configuration, - ) - - advanceUntilIdle() - - assertThat(reusableVH.viewHostAdapter.isInitialized()).isTrue() - assertThat(reusableVH.rootView.getChildAt(0)).isEqualTo(otherView) - } - - @Test - fun release() = runTest { - val reusableVH = createReusableViewHost() - - val view = View(context) - reusableVH.updateView( - view = view, - attrs = WindowManager.LayoutParams(100, 100), - configuration = context.resources.configuration, - onDrawTransaction = null - ) - - val t = mock(SurfaceControl.Transaction::class.java) - reusableVH.release(t) - - verify(reusableVH.viewHostAdapter).release(t) - } - - private fun CoroutineScope.createReusableViewHost() = ReusableWindowDecorViewHost( - context = context, - mainScope = this, - display = context.display, - id = 1, - viewHostAdapter = spy(SurfaceControlViewHostAdapter(context, context.display)), - ) - - private fun ReusableWindowDecorViewHost.view(): View? = viewHostAdapter.viewHost?.view -} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/SurfaceControlViewHostAdapterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/SurfaceControlViewHostAdapterTest.kt deleted file mode 100644 index d6c80a7fffc1..000000000000 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/SurfaceControlViewHostAdapterTest.kt +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.wm.shell.windowdecor.viewhost - -import android.testing.AndroidTestingRunner -import android.testing.TestableLooper -import android.view.SurfaceControl -import android.view.SurfaceControlViewHost -import android.view.View -import android.view.WindowManager -import androidx.test.filters.SmallTest -import com.android.wm.shell.ShellTestCase -import com.google.common.truth.Truth.assertThat -import org.junit.Assert.assertThrows -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.Mockito.mock -import org.mockito.kotlin.spy -import org.mockito.kotlin.verify - -/** - * Tests for [SurfaceControlViewHostAdapter]. - * - * Build/Install/Run: - * atest WMShellUnitTests:SurfaceControlViewHostAdapterTest - */ -@SmallTest -@TestableLooper.RunWithLooper -@RunWith(AndroidTestingRunner::class) -class SurfaceControlViewHostAdapterTest : ShellTestCase() { - - private lateinit var adapter: SurfaceControlViewHostAdapter - - @Before - fun setUp() { - adapter = SurfaceControlViewHostAdapter( - context, - context.display, - surfaceControlViewHostFactory = { c, d, wwm, s -> - spy(SurfaceControlViewHost(c, d, wwm, s)) - } - ) - } - - @Test - fun prepareViewHost() { - adapter.prepareViewHost(context.resources.configuration) - - assertThat(adapter.viewHost).isNotNull() - } - - @Test - fun prepareViewHost_alreadyCreated_skips() { - adapter.prepareViewHost(context.resources.configuration) - - val viewHost = adapter.viewHost!! - - adapter.prepareViewHost(context.resources.configuration) - - assertThat(adapter.viewHost).isEqualTo(viewHost) - } - - @Test - fun updateView_layoutInViewHost() { - val view = View(context) - adapter.prepareViewHost(context.resources.configuration) - - adapter.updateView( - view = view, - attrs = WindowManager.LayoutParams(100, 100) - ) - - assertThat(adapter.isInitialized()).isTrue() - assertThat(adapter.view()).isEqualTo(view) - } - - @Test - fun updateView_alreadyLaidOut_relayouts() { - val view = View(context) - adapter.prepareViewHost(context.resources.configuration) - adapter.updateView( - view = view, - attrs = WindowManager.LayoutParams(100, 100) - ) - - val otherParams = WindowManager.LayoutParams(200, 200) - adapter.updateView( - view = view, - attrs = otherParams - ) - - assertThat(adapter.view()).isEqualTo(view) - assertThat(adapter.view()!!.layoutParams.width).isEqualTo(otherParams.width) - } - - @Test - fun updateView_replacingView_throws() { - val view = View(context) - adapter.prepareViewHost(context.resources.configuration) - adapter.updateView( - view = view, - attrs = WindowManager.LayoutParams(100, 100) - ) - - val otherView = View(context) - assertThrows(Exception::class.java) { - adapter.updateView( - view = otherView, - attrs = WindowManager.LayoutParams(100, 100) - ) - } - } - - @Test - fun release() { - adapter.prepareViewHost(context.resources.configuration) - adapter.updateView( - view = View(context), - attrs = WindowManager.LayoutParams(100, 100) - ) - - val mockT = mock(SurfaceControl.Transaction::class.java) - adapter.release(mockT) - - verify(adapter.viewHost!!).release() - verify(mockT).remove(adapter.rootSurface) - } - - private fun SurfaceControlViewHostAdapter.view(): View? = viewHost?.view -} diff --git a/libs/appfunctions/Android.bp b/libs/appfunctions/Android.bp new file mode 100644 index 000000000000..09e2f423c3ba --- /dev/null +++ b/libs/appfunctions/Android.bp @@ -0,0 +1,31 @@ +// Copyright (C) 2024 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +java_sdk_library { + name: "com.google.android.appfunctions.sidecar", + owner: "google", + srcs: ["java/**/*.java"], + api_packages: ["com.google.android.appfunctions.sidecar"], + dex_preopt: { + enabled: false, + }, + system_ext_specific: true, + no_dist: true, + unsafe_ignore_missing_latest_api: true, +} diff --git a/libs/appfunctions/api/current.txt b/libs/appfunctions/api/current.txt new file mode 100644 index 000000000000..504e3290b0ae --- /dev/null +++ b/libs/appfunctions/api/current.txt @@ -0,0 +1,49 @@ +// Signature format: 2.0 +package com.google.android.appfunctions.sidecar { + + public final class AppFunctionManager { + ctor public AppFunctionManager(android.content.Context); + method public void executeAppFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>); + } + + public abstract class AppFunctionService extends android.app.Service { + ctor public AppFunctionService(); + method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent); + method @MainThread public abstract void onExecuteFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>); + field @NonNull public static final String BIND_APP_FUNCTION_SERVICE = "android.permission.BIND_APP_FUNCTION_SERVICE"; + field @NonNull public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService"; + } + + public final class ExecuteAppFunctionRequest { + 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(); + } + + public static final class ExecuteAppFunctionRequest.Builder { + ctor public ExecuteAppFunctionRequest.Builder(@NonNull String, @NonNull String); + method @NonNull public com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest build(); + method @NonNull public com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest.Builder setExtras(@NonNull android.os.Bundle); + method @NonNull public com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest.Builder setParameters(@NonNull android.app.appsearch.GenericDocument); + } + + public final class ExecuteAppFunctionResponse { + method @Nullable public String getErrorMessage(); + method @NonNull public android.os.Bundle getExtras(); + method public int getResultCode(); + method @NonNull public android.app.appsearch.GenericDocument getResultDocument(); + method public boolean isSuccess(); + method @NonNull public static com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse newFailure(int, @Nullable String, @Nullable android.os.Bundle); + method @NonNull public static com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse newSuccess(@NonNull android.app.appsearch.GenericDocument, @Nullable android.os.Bundle); + field public static final String PROPERTY_RETURN_VALUE = "returnValue"; + field public static final int RESULT_APP_UNKNOWN_ERROR = 2; // 0x2 + field public static final int RESULT_DENIED = 1; // 0x1 + field public static final int RESULT_INTERNAL_ERROR = 3; // 0x3 + field public static final int RESULT_INVALID_ARGUMENT = 4; // 0x4 + field public static final int RESULT_OK = 0; // 0x0 + field public static final int RESULT_TIMED_OUT = 5; // 0x5 + } + +} + diff --git a/libs/appfunctions/api/removed.txt b/libs/appfunctions/api/removed.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/libs/appfunctions/api/removed.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/libs/appfunctions/api/system-current.txt b/libs/appfunctions/api/system-current.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/libs/appfunctions/api/system-current.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/libs/appfunctions/api/system-removed.txt b/libs/appfunctions/api/system-removed.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/libs/appfunctions/api/system-removed.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/libs/appfunctions/api/test-current.txt b/libs/appfunctions/api/test-current.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/libs/appfunctions/api/test-current.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/libs/appfunctions/api/test-removed.txt b/libs/appfunctions/api/test-removed.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/libs/appfunctions/api/test-removed.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java new file mode 100644 index 000000000000..b1dd4676a35e --- /dev/null +++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java @@ -0,0 +1,82 @@ +/* + * 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.google.android.appfunctions.sidecar; + +import android.annotation.CallbackExecutor; +import android.annotation.NonNull; +import android.content.Context; + +import java.util.Objects; +import java.util.concurrent.Executor; +import java.util.function.Consumer; + + +/** + * Provides app functions related functionalities. + * + * <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. + * + * <p>This class wraps {@link android.app.appfunctions.AppFunctionManager} functionalities and + * exposes it here as a sidecar library (avoiding direct dependency on the platform API). + */ +// TODO(b/357551503): Implement get and set enabled app function APIs. +// TODO(b/367329899): Add sidecar library to Android B builds. +public final class AppFunctionManager { + private final android.app.appfunctions.AppFunctionManager mManager; + private final Context mContext; + + /** + * Creates an instance. + * + * @param context A {@link Context}. + * @throws java.lang.IllegalStateException if the underlying {@link + * android.app.appfunctions.AppFunctionManager} is not found. + */ + public AppFunctionManager(Context context) { + mContext = Objects.requireNonNull(context); + mManager = context.getSystemService(android.app.appfunctions.AppFunctionManager.class); + if (mManager == null) { + throw new IllegalStateException( + "Underlying AppFunctionManager system service not found."); + } + } + + /** + * Executes the app function. + * + * <p>Proxies request and response to the underlying {@link + * android.app.appfunctions.AppFunctionManager#executeAppFunction}, converting the request and + * response in the appropriate type required by the function. + */ + public void executeAppFunction( + @NonNull ExecuteAppFunctionRequest sidecarRequest, + @NonNull @CallbackExecutor Executor executor, + @NonNull Consumer<ExecuteAppFunctionResponse> callback) { + Objects.requireNonNull(sidecarRequest); + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); + + android.app.appfunctions.ExecuteAppFunctionRequest platformRequest = + SidecarConverter.getPlatformExecuteAppFunctionRequest(sidecarRequest); + mManager.executeAppFunction( + platformRequest, executor, (platformResponse) -> { + callback.accept(SidecarConverter.getSidecarExecuteAppFunctionResponse( + platformResponse)); + }); + } +} diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java new file mode 100644 index 000000000000..65959dfdf561 --- /dev/null +++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.appfunctions.sidecar; + +import static android.Manifest.permission.BIND_APP_FUNCTION_SERVICE; + +import android.annotation.MainThread; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.Service; +import android.content.Intent; +import android.os.Binder; +import android.os.IBinder; + +import java.util.function.Consumer; + +/** + * Abstract base class to provide app functions to the system. + * + * <p>Include the following in the manifest: + * + * <pre> + * {@literal + * <service android:name=".YourService" + * android:permission="android.permission.BIND_APP_FUNCTION_SERVICE"> + * <intent-filter> + * <action android:name="android.app.appfunctions.AppFunctionService" /> + * </intent-filter> + * </service> + * } + * </pre> + * + * <p>This class wraps {@link android.app.appfunctions.AppFunctionService} functionalities and + * exposes it here as a sidecar library (avoiding direct dependency on the platform API). + * + * @see AppFunctionManager + */ +public abstract class AppFunctionService extends Service { + /** + * The permission to only allow system access to the functions through {@link + * AppFunctionManagerService}. + */ + @NonNull + public static final String BIND_APP_FUNCTION_SERVICE = + "android.permission.BIND_APP_FUNCTION_SERVICE"; + + /** + * The {@link Intent} that must be declared as handled by the service. To be supported, the + * service must also require the {@link BIND_APP_FUNCTION_SERVICE} permission so that other + * applications can not abuse it. + */ + @NonNull + public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService"; + + private final Binder mBinder = + android.app.appfunctions.AppFunctionService.createBinder( + /* context= */ this, + /* onExecuteFunction= */ (platformRequest, callback) -> { + AppFunctionService.this.onExecuteFunction( + SidecarConverter.getSidecarExecuteAppFunctionRequest( + platformRequest), + (sidecarResponse) -> { + callback.accept( + SidecarConverter.getPlatformExecuteAppFunctionResponse( + sidecarResponse)); + }); + } + ); + + @NonNull + @Override + public final IBinder onBind(@Nullable Intent intent) { + return mBinder; + } + + /** + * Called by the system to execute a specific app function. + * + * <p>This method is triggered when the system requests your AppFunctionService to handle a + * particular function you have registered and made available. + * + * <p>To ensure proper routing of function requests, assign a unique identifier to each + * function. This identifier doesn't need to be globally unique, but it must be unique within + * your app. For example, a function to order food could be identified as "orderFood". In most + * cases this identifier should come from the ID automatically generated by the AppFunctions + * SDK. You can determine the specific function to invoke by calling {@link + * ExecuteAppFunctionRequest#getFunctionIdentifier()}. + * + * <p>This method is always triggered in the main thread. You should run heavy tasks on a worker + * thread and dispatch the result with the given callback. You should always report back the + * result using the callback, no matter if the execution was successful or not. + * + * @param request The function execution request. + * @param callback A callback to report back the result. + */ + @MainThread + public abstract void onExecuteFunction( + @NonNull ExecuteAppFunctionRequest request, + @NonNull Consumer<ExecuteAppFunctionResponse> callback); +} diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionRequest.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionRequest.java new file mode 100644 index 000000000000..fa6d2ff12313 --- /dev/null +++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionRequest.java @@ -0,0 +1,137 @@ +/* + * 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.google.android.appfunctions.sidecar; + +import android.annotation.NonNull; +import android.app.appsearch.GenericDocument; +import android.os.Bundle; + +import java.util.Objects; + +/** + * A request to execute an app function. + * + * <p>This class copies {@link android.app.appfunctions.ExecuteAppFunctionRequest} without parcel + * functionality and exposes it here as a sidecar library (avoiding direct dependency on the + * platform API). + */ +public final class ExecuteAppFunctionRequest { + /** 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. + * + * <p>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; + } + + /** 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) { + Objects.requireNonNull(parameters); + mParameters = parameters; + return this; + } + + /** Builds the {@link ExecuteAppFunctionRequest}. */ + @NonNull + public ExecuteAppFunctionRequest build() { + return new ExecuteAppFunctionRequest( + mTargetPackageName, + mFunctionIdentifier, + mExtras, + mParameters); + } + } +} diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java new file mode 100644 index 000000000000..60c25fae58d1 --- /dev/null +++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java @@ -0,0 +1,240 @@ +/* + * 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.google.android.appfunctions.sidecar; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.appsearch.GenericDocument; +import android.os.Bundle; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; + +/** + * The response to an app function execution. + * + * <p>This class copies {@link android.app.appfunctions.ExecuteAppFunctionResponse} without parcel + * functionality and exposes it here as a sidecar library (avoiding direct dependency on the + * platform API). + */ +public final class ExecuteAppFunctionResponse { + /** + * The name of the property that stores the function return value within the {@code + * resultDocument}. + * + * <p>See {@link GenericDocument#getProperty(String)} for more information. + * + * <p>If the function returns {@code void} or throws an error, the {@code resultDocument} will + * be empty {@link GenericDocument}. + * + * <p>If the {@code resultDocument} is empty, {@link GenericDocument#getProperty(String)} will + * return {@code null}. + * + * <p>See {@link #getResultDocument} for more information on extracting the return value. + */ + public static final String PROPERTY_RETURN_VALUE = "returnValue"; + + /** The call was successful. */ + public static final int RESULT_OK = 0; + + /** The caller does not have the permission to execute an app function. */ + public static final int RESULT_DENIED = 1; + + /** An unknown error occurred while processing the call in the AppFunctionService. */ + public static final int RESULT_APP_UNKNOWN_ERROR = 2; + + /** + * An internal error occurred within AppFunctionManagerService. + * + * <p>This error may be considered similar to {@link IllegalStateException} + */ + public static final int RESULT_INTERNAL_ERROR = 3; + + /** + * The caller supplied invalid arguments to the call. + * + * <p>This error may be considered similar to {@link IllegalArgumentException}. + */ + public static final int RESULT_INVALID_ARGUMENT = 4; + + /** The operation was timed out. */ + public static final int RESULT_TIMED_OUT = 5; + + /** The result code of the app function execution. */ + @ResultCode private final int mResultCode; + + /** + * The error message associated with the result, if any. This is {@code null} if the result code + * is {@link #RESULT_OK}. + */ + @Nullable private final String mErrorMessage; + + /** + * Returns the return value of the executed function. + * + * <p>The return value is stored in a {@link GenericDocument} with the key {@link + * #PROPERTY_RETURN_VALUE}. + * + * <p>See {@link #getResultDocument} for more information on extracting the return value. + */ + @NonNull private final GenericDocument mResultDocument; + + /** Returns the additional metadata data relevant to this function execution response. */ + @NonNull private final Bundle mExtras; + + private ExecuteAppFunctionResponse( + @NonNull GenericDocument resultDocument, + @NonNull Bundle extras, + @ResultCode int resultCode, + @Nullable String errorMessage) { + mResultDocument = Objects.requireNonNull(resultDocument); + mExtras = Objects.requireNonNull(extras); + mResultCode = resultCode; + mErrorMessage = errorMessage; + } + + /** + * Returns result codes from throwable. + * + * @hide + */ + static @ResultCode int getResultCode(@NonNull Throwable t) { + if (t instanceof IllegalArgumentException) { + return ExecuteAppFunctionResponse.RESULT_INVALID_ARGUMENT; + } + return ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR; + } + + /** + * Returns a successful response. + * + * @param resultDocument The return value of the executed function. + * @param extras The additional metadata data relevant to this function execution response. + */ + @NonNull + public static ExecuteAppFunctionResponse newSuccess( + @NonNull GenericDocument resultDocument, @Nullable Bundle extras) { + Objects.requireNonNull(resultDocument); + Bundle actualExtras = getActualExtras(extras); + + return new ExecuteAppFunctionResponse( + resultDocument, actualExtras, RESULT_OK, /* errorMessage= */ null); + } + + /** + * Returns a failure response. + * + * @param resultCode The result code of the app function execution. + * @param extras The additional metadata data relevant to this function execution response. + * @param errorMessage The error message associated with the result, if any. + */ + @NonNull + public static ExecuteAppFunctionResponse newFailure( + @ResultCode int resultCode, @Nullable String errorMessage, @Nullable Bundle extras) { + if (resultCode == RESULT_OK) { + throw new IllegalArgumentException("resultCode must not be RESULT_OK"); + } + Bundle actualExtras = getActualExtras(extras); + GenericDocument emptyDocument = new GenericDocument.Builder<>("", "", "").build(); + return new ExecuteAppFunctionResponse( + emptyDocument, actualExtras, resultCode, errorMessage); + } + + private static Bundle getActualExtras(@Nullable Bundle extras) { + if (extras == null) { + return Bundle.EMPTY; + } + return extras; + } + + /** + * Returns a generic document containing the return value of the executed function. + * + * <p>The {@link #PROPERTY_RETURN_VALUE} key can be used to obtain the return value. + * + * <p>An empty document is returned if {@link #isSuccess} is {@code false} or if the executed + * function does not produce a return value. + * + * <p>Sample code for extracting the return value: + * + * <pre> + * GenericDocument resultDocument = response.getResultDocument(); + * Object returnValue = resultDocument.getProperty(PROPERTY_RETURN_VALUE); + * if (returnValue != null) { + * // Cast returnValue to expected type, or use {@link GenericDocument#getPropertyString}, + * // {@link GenericDocument#getPropertyLong} etc. + * // Do something with the returnValue + * } + * </pre> + */ + @NonNull + public GenericDocument getResultDocument() { + return mResultDocument; + } + + /** Returns the extras of the app function execution. */ + @NonNull + public Bundle getExtras() { + return mExtras; + } + + /** + * Returns {@code true} if {@link #getResultCode} equals {@link + * ExecuteAppFunctionResponse#RESULT_OK}. + */ + public boolean isSuccess() { + return getResultCode() == RESULT_OK; + } + + /** + * Returns one of the {@code RESULT} constants defined in {@link ExecuteAppFunctionResponse}. + */ + @ResultCode + public int getResultCode() { + return mResultCode; + } + + /** + * Returns the error message associated with this result. + * + * <p>If {@link #isSuccess} is {@code true}, the error message is always {@code null}. + */ + @Nullable + public String getErrorMessage() { + return mErrorMessage; + } + + /** + * Result codes. + * + * @hide + */ + @IntDef( + prefix = {"RESULT_"}, + value = { + RESULT_OK, + RESULT_DENIED, + RESULT_APP_UNKNOWN_ERROR, + RESULT_INTERNAL_ERROR, + RESULT_INVALID_ARGUMENT, + RESULT_TIMED_OUT, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ResultCode {} +} diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/SidecarConverter.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/SidecarConverter.java new file mode 100644 index 000000000000..b1b05f79f33f --- /dev/null +++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/SidecarConverter.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.appfunctions.sidecar; + +import android.annotation.NonNull; + +/** + * Utility class containing methods to convert Sidecar objects of AppFunctions API into the + * underlying platform classes. + * + * @hide + */ +public final class SidecarConverter { + private SidecarConverter() {} + + /** + * Converts sidecar's {@link com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest} + * into platform's {@link android.app.appfunctions.ExecuteAppFunctionRequest} + * + * @hide + */ + @NonNull + public static android.app.appfunctions.ExecuteAppFunctionRequest + getPlatformExecuteAppFunctionRequest(@NonNull ExecuteAppFunctionRequest request) { + return new + android.app.appfunctions.ExecuteAppFunctionRequest.Builder( + request.getTargetPackageName(), + request.getFunctionIdentifier()) + .setExtras(request.getExtras()) + .setParameters(request.getParameters()) + .build(); + } + + /** + * Converts sidecar's {@link com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse} + * into platform's {@link android.app.appfunctions.ExecuteAppFunctionResponse} + * + * @hide + */ + @NonNull + public static android.app.appfunctions.ExecuteAppFunctionResponse + getPlatformExecuteAppFunctionResponse(@NonNull ExecuteAppFunctionResponse response) { + if (response.isSuccess()) { + return android.app.appfunctions.ExecuteAppFunctionResponse.newSuccess( + response.getResultDocument(), response.getExtras()); + } else { + return android.app.appfunctions.ExecuteAppFunctionResponse.newFailure( + response.getResultCode(), + response.getErrorMessage(), + response.getExtras()); + } + } + + /** + * Converts platform's {@link android.app.appfunctions.ExecuteAppFunctionRequest} + * into sidecar's {@link com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest} + * + * @hide + */ + @NonNull + public static ExecuteAppFunctionRequest getSidecarExecuteAppFunctionRequest( + @NonNull android.app.appfunctions.ExecuteAppFunctionRequest request) { + return new ExecuteAppFunctionRequest.Builder( + request.getTargetPackageName(), + request.getFunctionIdentifier()) + .setExtras(request.getExtras()) + .setParameters(request.getParameters()) + .build(); + } + + /** + * Converts platform's {@link android.app.appfunctions.ExecuteAppFunctionResponse} + * into sidecar's {@link com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse} + * + * @hide + */ + @NonNull + public static ExecuteAppFunctionResponse getSidecarExecuteAppFunctionResponse( + @NonNull android.app.appfunctions.ExecuteAppFunctionResponse response) { + if (response.isSuccess()) { + return ExecuteAppFunctionResponse.newSuccess( + response.getResultDocument(), response.getExtras()); + } else { + return ExecuteAppFunctionResponse.newFailure( + response.getResultCode(), + response.getErrorMessage(), + response.getExtras()); + } + } +} diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp index 185436160349..84bd45dfc012 100644 --- a/libs/hwui/hwui/Bitmap.cpp +++ b/libs/hwui/hwui/Bitmap.cpp @@ -264,6 +264,7 @@ Bitmap::Bitmap(void* address, size_t size, const SkImageInfo& info, size_t rowBy , mPixelStorageType(PixelStorageType::Heap) { mPixelStorage.heap.address = address; mPixelStorage.heap.size = size; + traceBitmapCreate(); } Bitmap::Bitmap(SkPixelRef& pixelRef, const SkImageInfo& info) @@ -272,6 +273,7 @@ Bitmap::Bitmap(SkPixelRef& pixelRef, const SkImageInfo& info) , mPixelStorageType(PixelStorageType::WrappedPixelRef) { pixelRef.ref(); mPixelStorage.wrapped.pixelRef = &pixelRef; + traceBitmapCreate(); } Bitmap::Bitmap(void* address, int fd, size_t mappedSize, const SkImageInfo& info, size_t rowBytes) @@ -281,6 +283,7 @@ Bitmap::Bitmap(void* address, int fd, size_t mappedSize, const SkImageInfo& info mPixelStorage.ashmem.address = address; mPixelStorage.ashmem.fd = fd; mPixelStorage.ashmem.size = mappedSize; + traceBitmapCreate(); } #ifdef __ANDROID__ // Layoutlib does not support hardware acceleration @@ -297,10 +300,12 @@ Bitmap::Bitmap(AHardwareBuffer* buffer, const SkImageInfo& info, size_t rowBytes setImmutable(); // HW bitmaps are always immutable mImage = SkImages::DeferredFromAHardwareBuffer(buffer, mInfo.alphaType(), mInfo.refColorSpace()); + traceBitmapCreate(); } #endif Bitmap::~Bitmap() { + traceBitmapDelete(); switch (mPixelStorageType) { case PixelStorageType::WrappedPixelRef: mPixelStorage.wrapped.pixelRef->unref(); @@ -572,4 +577,28 @@ void Bitmap::setGainmap(sp<uirenderer::Gainmap>&& gainmap) { mGainmap = std::move(gainmap); } +std::mutex Bitmap::mLock{}; +size_t Bitmap::mTotalBitmapBytes = 0; +size_t Bitmap::mTotalBitmapCount = 0; + +void Bitmap::traceBitmapCreate() { + if (ATRACE_ENABLED()) { + std::lock_guard lock{mLock}; + mTotalBitmapBytes += getAllocationByteCount(); + mTotalBitmapCount++; + ATRACE_INT64("Bitmap Memory", mTotalBitmapBytes); + ATRACE_INT64("Bitmap Count", mTotalBitmapCount); + } +} + +void Bitmap::traceBitmapDelete() { + if (ATRACE_ENABLED()) { + std::lock_guard lock{mLock}; + mTotalBitmapBytes -= getAllocationByteCount(); + mTotalBitmapCount--; + ATRACE_INT64("Bitmap Memory", mTotalBitmapBytes); + ATRACE_INT64("Bitmap Count", mTotalBitmapCount); + } +} + } // namespace android diff --git a/libs/hwui/hwui/Bitmap.h b/libs/hwui/hwui/Bitmap.h index dd344e2f5517..3d55d859ed5f 100644 --- a/libs/hwui/hwui/Bitmap.h +++ b/libs/hwui/hwui/Bitmap.h @@ -25,6 +25,7 @@ #include <cutils/compiler.h> #include <utils/StrongPointer.h> +#include <mutex> #include <optional> #ifdef __ANDROID__ // Layoutlib does not support hardware acceleration @@ -227,6 +228,13 @@ private: } mPixelStorage; sk_sp<SkImage> mImage; // Cache is used only for HW Bitmaps with Skia pipeline. + + // for tracing total number and memory usage of bitmaps + static std::mutex mLock; + static size_t mTotalBitmapBytes; + static size_t mTotalBitmapCount; + void traceBitmapCreate(); + void traceBitmapDelete(); }; } // namespace android diff --git a/libs/hwui/hwui/DrawTextFunctor.h b/libs/hwui/hwui/DrawTextFunctor.h index d7bf20130b71..e13e136550ca 100644 --- a/libs/hwui/hwui/DrawTextFunctor.h +++ b/libs/hwui/hwui/DrawTextFunctor.h @@ -73,6 +73,7 @@ static void simplifyPaint(int color, Paint* paint) { } paint->setStrokeJoin(SkPaint::kRound_Join); paint->setLooper(nullptr); + paint->setBlendMode(SkBlendMode::kSrcOver); } class DrawTextFunctor { diff --git a/libs/input/MouseCursorController.cpp b/libs/input/MouseCursorController.cpp index 1afef75bc741..d993b8715260 100644 --- a/libs/input/MouseCursorController.cpp +++ b/libs/input/MouseCursorController.cpp @@ -64,25 +64,6 @@ MouseCursorController::~MouseCursorController() { mLocked.pointerSprite.clear(); } -std::optional<FloatRect> MouseCursorController::getBounds() const { - std::scoped_lock lock(mLock); - - return getBoundsLocked(); -} - -std::optional<FloatRect> MouseCursorController::getBoundsLocked() const REQUIRES(mLock) { - if (!mLocked.viewport.isValid()) { - return {}; - } - - return FloatRect{ - static_cast<float>(mLocked.viewport.logicalLeft), - static_cast<float>(mLocked.viewport.logicalTop), - static_cast<float>(mLocked.viewport.logicalRight - 1), - static_cast<float>(mLocked.viewport.logicalBottom - 1), - }; -} - void MouseCursorController::move(float deltaX, float deltaY) { #if DEBUG_MOUSE_CURSOR_UPDATES ALOGD("Move pointer by deltaX=%0.3f, deltaY=%0.3f", deltaX, deltaY); @@ -105,11 +86,20 @@ void MouseCursorController::setPosition(float x, float y) { } void MouseCursorController::setPositionLocked(float x, float y) REQUIRES(mLock) { - const auto bounds = getBoundsLocked(); - if (!bounds) return; + const auto& v = mLocked.viewport; + if (!v.isValid()) return; - mLocked.pointerX = std::max(bounds->left, std::min(bounds->right, x)); - mLocked.pointerY = std::max(bounds->top, std::min(bounds->bottom, y)); + // The valid bounds for a mouse cursor. Since the right and bottom edges are considered outside + // the display, clip the bounds by one pixel instead of letting the cursor get arbitrarily + // close to the outside edge. + const FloatRect bounds{ + static_cast<float>(mLocked.viewport.logicalLeft), + static_cast<float>(mLocked.viewport.logicalTop), + static_cast<float>(mLocked.viewport.logicalRight - 1), + static_cast<float>(mLocked.viewport.logicalBottom - 1), + }; + mLocked.pointerX = std::max(bounds.left, std::min(bounds.right, x)); + mLocked.pointerY = std::max(bounds.top, std::min(bounds.bottom, y)); updatePointerLocked(); } @@ -216,9 +206,11 @@ void MouseCursorController::setDisplayViewport(const DisplayViewport& viewport, // Reset cursor position to center if size or display changed. if (oldViewport.displayId != viewport.displayId || oldDisplayWidth != newDisplayWidth || oldDisplayHeight != newDisplayHeight) { - if (const auto bounds = getBoundsLocked(); bounds) { - mLocked.pointerX = (bounds->left + bounds->right) * 0.5f; - mLocked.pointerY = (bounds->top + bounds->bottom) * 0.5f; + if (viewport.isValid()) { + // Use integer coordinates as the starting point for the cursor location. + // We usually expect display sizes to be even numbers, so the flooring is precautionary. + mLocked.pointerX = std::floor((viewport.logicalLeft + viewport.logicalRight) / 2); + mLocked.pointerY = std::floor((viewport.logicalTop + viewport.logicalBottom) / 2); // Reload icon resources for density may be changed. loadResourcesLocked(getAdditionalMouseResources); } else { diff --git a/libs/input/MouseCursorController.h b/libs/input/MouseCursorController.h index 860034141a0b..12b31a8c531a 100644 --- a/libs/input/MouseCursorController.h +++ b/libs/input/MouseCursorController.h @@ -43,7 +43,6 @@ public: MouseCursorController(PointerControllerContext& context); ~MouseCursorController(); - std::optional<FloatRect> getBounds() const; void move(float deltaX, float deltaY); void setPosition(float x, float y); FloatPoint getPosition() const; @@ -104,7 +103,6 @@ private: } mLocked GUARDED_BY(mLock); - std::optional<FloatRect> getBoundsLocked() const; void setPositionLocked(float x, float y); void updatePointerLocked(); diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp index 5ae967bc369a..78d7d3a7051b 100644 --- a/libs/input/PointerController.cpp +++ b/libs/input/PointerController.cpp @@ -138,10 +138,6 @@ std::mutex& PointerController::getLock() const { return mDisplayInfoListener->mLock; } -std::optional<FloatRect> PointerController::getBounds() const { - return mCursorController.getBounds(); -} - void PointerController::move(float deltaX, float deltaY) { const ui::LogicalDisplayId displayId = mCursorController.getDisplayId(); vec2 transformed; diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h index 4d1e1d733cc1..ee8d1211341f 100644 --- a/libs/input/PointerController.h +++ b/libs/input/PointerController.h @@ -51,7 +51,6 @@ public: ~PointerController() override; - std::optional<FloatRect> getBounds() const override; void move(float deltaX, float deltaY) override; void setPosition(float x, float y) override; FloatPoint getPosition() const override; @@ -166,9 +165,6 @@ public: ~TouchPointerController() override; - std::optional<FloatRect> getBounds() const override { - LOG_ALWAYS_FATAL("Should not be called"); - } void move(float, float) override { LOG_ALWAYS_FATAL("Should not be called"); } diff --git a/location/java/android/location/flags/location.aconfig b/location/java/android/location/flags/location.aconfig index dcf5c5b46478..cddc337d95b8 100644 --- a/location/java/android/location/flags/location.aconfig +++ b/location/java/android/location/flags/location.aconfig @@ -111,6 +111,17 @@ flag { } flag { + name: "enable_ni_supl_message_injection_by_carrier_config_bugfix" + namespace: "location" + description: "Flag for enabling NI SUPL message injection by carrier config" + bug: "242105192" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "enable_ni_supl_message_injection_by_carrier_config" namespace: "location" description: "Flag for enabling NI SUPL message injection by carrier config" diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index e2e7a46a0ad0..cdb517b3fd3e 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -6318,7 +6318,14 @@ public class AudioManager { /** * @hide * Get the audio devices that would be used for the routing of the given audio attributes. - * @param attributes the {@link AudioAttributes} for which the routing is being queried + * @param attributes the {@link AudioAttributes} for which the routing is being queried. + * For queries about output devices (playback use cases), a valid usage must be specified in + * the audio attributes via AudioAttributes.Builder.setUsage(). The capture preset MUST NOT + * be changed from default. + * For queries about input devices (capture use case), a valid capture preset MUST be + * specified in the audio attributes via AudioAttributes.Builder.setCapturePreset(). If a + * capture preset is present, then this has precedence over any usage or content type also + * present in the audio attrirutes. * @return an empty list if there was an issue with the request, a list of audio devices * otherwise (typically one device, except for duplicated paths). */ diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt index bc8a7afd94e9..9603c0a9f7c7 100644 --- a/nfc/api/system-current.txt +++ b/nfc/api/system-current.txt @@ -60,8 +60,13 @@ package android.nfc { 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.NFC_SET_CONTROLLER_ALWAYS_ON) public void setControllerAlwaysOn(int); method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void synchronizeScreenState(); method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void unregisterCallback(@NonNull android.nfc.NfcOemExtension.Callback); + field @FlaggedApi("android.nfc.nfc_oem_extension") public static final int DISABLE = 0; // 0x0 + field @FlaggedApi("android.nfc.nfc_oem_extension") public static final int ENABLE_DEFAULT = 1; // 0x1 + field @FlaggedApi("android.nfc.nfc_oem_extension") public static final int ENABLE_EE = 3; // 0x3 + field @FlaggedApi("android.nfc.nfc_oem_extension") public static final int ENABLE_TRANSPARENT = 2; // 0x2 field public static final int HCE_ACTIVATE = 1; // 0x1 field public static final int HCE_DATA_TRANSFERRED = 2; // 0x2 field public static final int HCE_DEACTIVATE = 3; // 0x3 diff --git a/nfc/java/android/nfc/INfcAdapter.aidl b/nfc/java/android/nfc/INfcAdapter.aidl index e2ec95215d1a..246efc7ca557 100644 --- a/nfc/java/android/nfc/INfcAdapter.aidl +++ b/nfc/java/android/nfc/INfcAdapter.aidl @@ -73,7 +73,7 @@ interface INfcAdapter boolean setNfcSecure(boolean enable); NfcAntennaInfo getNfcAntennaInfo(); - boolean setControllerAlwaysOn(boolean value); + void setControllerAlwaysOn(int mode); boolean isControllerAlwaysOn(); boolean isControllerAlwaysOnSupported(); void registerControllerAlwaysOnListener(in INfcControllerAlwaysOnListener listener); diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java index f47879385070..de85f1eeabe3 100644 --- a/nfc/java/android/nfc/NfcAdapter.java +++ b/nfc/java/android/nfc/NfcAdapter.java @@ -559,6 +559,18 @@ public final class NfcAdapter { @Retention(RetentionPolicy.SOURCE) public @interface TagIntentAppPreferenceResult {} + /** + * Mode Type for {@link NfcOemExtension#setControllerAlwaysOn(int)}. + * @hide + */ + public static final int CONTROLLER_ALWAYS_ON_MODE_DEFAULT = 1; + + /** + * Mode Type for {@link NfcOemExtension#setControllerAlwaysOn(int)}. + * @hide + */ + public static final int CONTROLLER_ALWAYS_ON_DISABLE = 0; + // Guarded by sLock static boolean sIsInitialized = false; static boolean sHasNfcFeature; @@ -2330,7 +2342,8 @@ public final class NfcAdapter { * FEATURE_NFC_HOST_CARD_EMULATION, FEATURE_NFC_HOST_CARD_EMULATION_NFCF, * FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC and FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE * are unavailable - * @return void + * @return true if feature is supported by the device and operation has bee initiated, + * false if the feature is not supported by the device. * @hide */ @SystemApi @@ -2339,8 +2352,13 @@ public final class NfcAdapter { if (!sHasNfcFeature && !sHasCeFeature) { throw new UnsupportedOperationException(); } - return callServiceReturn(() -> sService.setControllerAlwaysOn(value), false); - + int mode = value ? CONTROLLER_ALWAYS_ON_MODE_DEFAULT : CONTROLLER_ALWAYS_ON_DISABLE; + try { + callService(() -> sService.setControllerAlwaysOn(mode)); + } catch (UnsupportedOperationException e) { + return false; + } + return true; } /** diff --git a/nfc/java/android/nfc/NfcOemExtension.java b/nfc/java/android/nfc/NfcOemExtension.java index d51b704a7e7f..45038d41e237 100644 --- a/nfc/java/android/nfc/NfcOemExtension.java +++ b/nfc/java/android/nfc/NfcOemExtension.java @@ -70,6 +70,58 @@ public final class NfcOemExtension { private boolean mRfDiscoveryStarted = false; /** + * Mode Type for {@link #setControllerAlwaysOn(int)}. + * Enables the controller in default mode when NFC is disabled (existing API behavior). + * works same as {@link NfcAdapter#setControllerAlwaysOn(boolean)}. + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION) + public static final int ENABLE_DEFAULT = NfcAdapter.CONTROLLER_ALWAYS_ON_MODE_DEFAULT; + + /** + * Mode Type for {@link #setControllerAlwaysOn(int)}. + * Enables the controller in transparent mode when NFC is disabled. + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION) + public static final int ENABLE_TRANSPARENT = 2; + + /** + * Mode Type for {@link #setControllerAlwaysOn(int)}. + * Enables the controller and initializes and enables the EE subsystem when NFC is disabled. + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION) + public static final int ENABLE_EE = 3; + + /** + * Mode Type for {@link #setControllerAlwaysOn(int)}. + * Disable the Controller Always On Mode. + * works same as {@link NfcAdapter#setControllerAlwaysOn(boolean)}. + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION) + public static final int DISABLE = NfcAdapter.CONTROLLER_ALWAYS_ON_DISABLE; + + /** + * Possible controller modes for {@link #setControllerAlwaysOn(int)}. + * + * @hide + */ + @IntDef(prefix = { "" }, value = { + ENABLE_DEFAULT, + ENABLE_TRANSPARENT, + ENABLE_EE, + DISABLE, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ControllerMode{} + + /** * Event that Host Card Emulation is activated. */ public static final int HCE_ACTIVATE = 1; @@ -389,6 +441,32 @@ public final class NfcOemExtension { NfcAdapter.sService.fetchActiveNfceeList(), new ArrayList<String>()); } + /** + * Sets NFC controller always on feature. + * <p>This API is for the NFCC internal state management. It allows to discriminate + * the controller function from the NFC function by keeping the NFC controller on without + * any NFC RF enabled if necessary. + * <p>This call is asynchronous, register listener {@link NfcAdapter.ControllerAlwaysOnListener} + * by {@link NfcAdapter#registerControllerAlwaysOnListener} to find out when the operation is + * complete. + * @param mode one of {@link ControllerMode} modes + * @throws UnsupportedOperationException if + * <li> if FEATURE_NFC, FEATURE_NFC_HOST_CARD_EMULATION, FEATURE_NFC_HOST_CARD_EMULATION_NFCF, + * FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC and FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE + * are unavailable </li> + * <li> if the feature is unavailable @see NfcAdapter#isNfcControllerAlwaysOnSupported() </li> + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION) + @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) + public void setControllerAlwaysOn(@ControllerMode int mode) { + if (!NfcAdapter.sHasNfcFeature && !NfcAdapter.sHasCeFeature) { + throw new UnsupportedOperationException(); + } + NfcAdapter.callService(() -> NfcAdapter.sService.setControllerAlwaysOn(mode)); + } + private final class NfcOemExtensionCallback extends INfcOemExtensionCallback.Stub { @Override diff --git a/packages/PackageInstaller/Android.bp b/packages/PackageInstaller/Android.bp index bd84b58aa0f4..a30c0c3c6d4c 100644 --- a/packages/PackageInstaller/Android.bp +++ b/packages/PackageInstaller/Android.bp @@ -13,6 +13,7 @@ // limitations under the License. package { + default_team: "trendy_team_framework_android_packages", default_applicable_licenses: [ "frameworks_base_packages_PackageInstaller_license", ], diff --git a/packages/PackageInstaller/TEST_MAPPING b/packages/PackageInstaller/TEST_MAPPING index 717ec02d9dd6..91882fde1537 100644 --- a/packages/PackageInstaller/TEST_MAPPING +++ b/packages/PackageInstaller/TEST_MAPPING @@ -65,6 +65,17 @@ "name": "CtsIntentSignatureTestCases" }, { + "name": "CtsPackageInstallerCUJDeviceAdminTestCases", + "options":[ + { + "exclude-annotation":"androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation":"org.junit.Ignore" + } + ] + }, + { "name": "CtsPackageInstallerCUJInstallationTestCases", "options":[ { @@ -76,6 +87,17 @@ ] }, { + "name": "CtsPackageInstallerCUJMultiUsersTestCases", + "options":[ + { + "exclude-annotation":"androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation":"org.junit.Ignore" + } + ] + }, + { "name": "CtsPackageInstallerCUJUninstallationTestCases", "options":[ { diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt index 022ddedd1062..265864e1b3fd 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt @@ -17,11 +17,16 @@ package com.android.settingslib.spa.widget.dialog import android.content.res.Configuration +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Button +import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable @@ -32,9 +37,11 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.window.DialogProperties +import com.android.settingslib.spa.framework.theme.isSpaExpressiveEnabled data class AlertDialogButton( val text: String, @@ -85,27 +92,41 @@ private fun AlertDialogPresenter.SettingsAlertDialog( AlertDialog( onDismissRequest = ::close, modifier = Modifier.width(getDialogWidth()), - confirmButton = { confirmButton?.let { Button(it) } }, - dismissButton = dismissButton?.let { { Button(it) } }, - title = title?.let { { Text(it) } }, - text = text?.let { - { - Column(Modifier.verticalScroll(rememberScrollState())) { - text() - } - } + confirmButton = { + confirmButton?.let { if (isSpaExpressiveEnabled) ConfirmButton(it) else Button(it) } }, + dismissButton = + dismissButton?.let { + { if (isSpaExpressiveEnabled) DismissButton(it) else Button(it) } + }, + title = title?.let { { CenterRow { Text(it) } } }, + text = + text?.let { + { CenterRow { Column(Modifier.verticalScroll(rememberScrollState())) { text() } } } + }, properties = DialogProperties(usePlatformDefaultWidth = false), ) } @Composable +internal fun CenterRow(content: @Composable (() -> Unit)) { + if (isSpaExpressiveEnabled) { + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) { + content() + } + } else { + content() + } +} + +@Composable fun getDialogWidth(): Dp { val configuration = LocalConfiguration.current - return configuration.screenWidthDp.dp * when (configuration.orientation) { - Configuration.ORIENTATION_LANDSCAPE -> 0.65f - else -> 0.85f - } + return configuration.screenWidthDp.dp * + when (configuration.orientation) { + Configuration.ORIENTATION_LANDSCAPE -> 0.65f + else -> 0.85f + } } @Composable @@ -120,3 +141,47 @@ private fun AlertDialogPresenter.Button(button: AlertDialogButton) { Text(button.text) } } + +@Composable +private fun AlertDialogPresenter.DismissButton(button: AlertDialogButton) { + OutlinedButton( + onClick = { + close() + button.onClick() + }, + enabled = button.enabled, + ) { + Text(button.text) + } +} + +@Composable +private fun AlertDialogPresenter.ConfirmButton(button: AlertDialogButton) { + Button( + onClick = { + close() + button.onClick() + }, + enabled = button.enabled, + ) { + Text(button.text) + } +} + +@Preview +@Composable +private fun AlertDialogPreview() { + val alertDialogPresenter = remember { + object : AlertDialogPresenter { + override fun open() {} + + override fun close() {} + } + } + alertDialogPresenter.SettingsAlertDialog( + confirmButton = AlertDialogButton("Ok"), + dismissButton = AlertDialogButton("Cancel"), + title = "Title", + text = { Text("Text") }, + ) +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt index 030522d73b26..58a83fa72532 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt @@ -40,10 +40,7 @@ fun SettingsAlertDialogWithIcon( dismissButton: AlertDialogButton?, title: String?, icon: @Composable (() -> Unit)? = { - Icon( - Icons.Default.WarningAmber, - contentDescription = null - ) + Icon(Icons.Default.WarningAmber, contentDescription = null) }, text: @Composable (() -> Unit)?, ) { @@ -52,43 +49,22 @@ fun SettingsAlertDialogWithIcon( icon = icon, modifier = Modifier.width(getDialogWidth()), confirmButton = { - confirmButton?.let { - Button( - onClick = { - it.onClick() - }, - ) { - Text(it.text) - } - } + confirmButton?.let { Button(onClick = { it.onClick() }) { Text(it.text) } } }, - dismissButton = dismissButton?.let { - { - OutlinedButton( - onClick = { - it.onClick() - }, - ) { - Text(it.text) + dismissButton = + dismissButton?.let { { OutlinedButton(onClick = { it.onClick() }) { Text(it.text) } } }, + title = + title?.let { + { + CenterRow { + Text(it, modifier = Modifier.fillMaxWidth(), textAlign = TextAlign.Center) + } } - } - }, - title = title?.let { - { - Text( - it, - modifier = Modifier.fillMaxWidth(), - textAlign = TextAlign.Center - ) - } - }, - text = text?.let { - { - Column(Modifier.verticalScroll(rememberScrollState())) { - text() - } - } - }, + }, + text = + text?.let { + { CenterRow { Column(Modifier.verticalScroll(rememberScrollState())) { text() } } } + }, properties = DialogProperties(usePlatformDefaultWidth = false), ) -}
\ No newline at end of file +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlterDialogContent.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlterDialogContent.kt index bef0bca1c5a4..9f2210d852a9 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlterDialogContent.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlterDialogContent.kt @@ -58,10 +58,7 @@ fun SettingsAlertDialogContent( dismissButton: AlertDialogButton?, title: String?, icon: @Composable (() -> Unit)? = { - Icon( - Icons.Default.WarningAmber, - contentDescription = null - ) + Icon(Icons.Default.WarningAmber, contentDescription = null) }, text: @Composable (() -> Unit)?, ) { @@ -69,42 +66,22 @@ fun SettingsAlertDialogContent( buttons = { AlertDialogFlowRow( mainAxisSpacing = ButtonsMainAxisSpacing, - crossAxisSpacing = ButtonsCrossAxisSpacing + crossAxisSpacing = ButtonsCrossAxisSpacing, ) { - dismissButton?.let { - OutlinedButton(onClick = it.onClick) { - Text(it.text) - } - } - confirmButton?.let { - Button( - onClick = { - it.onClick() - }, - ) { - Text(it.text) - } - } + dismissButton?.let { OutlinedButton(onClick = it.onClick) { Text(it.text) } } + confirmButton?.let { Button(onClick = { it.onClick() }) { Text(it.text) } } } }, icon = icon, modifier = Modifier.width(getDialogWidth()), - title = title?.let { - { - Text( - it, - modifier = Modifier.fillMaxWidth(), - textAlign = TextAlign.Center - ) - } - }, - text = text?.let { - { - Column(Modifier.verticalScroll(rememberScrollState())) { - text() - } - } - }, + title = + title?.let { + { Text(it, modifier = Modifier.fillMaxWidth(), textAlign = TextAlign.Center) } + }, + text = + text?.let { + { CenterRow { Column(Modifier.verticalScroll(rememberScrollState())) { text() } } } + }, ) } @@ -121,18 +98,12 @@ internal fun SettingsAlertDialogContent( shape = SettingsShape.CornerExtraLarge, color = MaterialTheme.colorScheme.surfaceContainerHigh, ) { - Column( - modifier = Modifier.padding(DialogPadding) - ) { + Column(modifier = Modifier.padding(DialogPadding)) { icon?.let { CompositionLocalProvider( - LocalContentColor provides AlertDialogDefaults.iconContentColor, + LocalContentColor provides AlertDialogDefaults.iconContentColor ) { - Box( - Modifier - .padding(IconPadding) - .align(Alignment.CenterHorizontally) - ) { + Box(Modifier.padding(IconPadding).align(Alignment.CenterHorizontally)) { icon() } } @@ -144,8 +115,7 @@ internal fun SettingsAlertDialogContent( ) { Box( // Align the title to the center when an icon is present. - Modifier - .padding(TitlePadding) + Modifier.padding(TitlePadding) .align( if (icon == null) { Alignment.Start @@ -161,11 +131,10 @@ internal fun SettingsAlertDialogContent( text?.let { ProvideContentColorTextStyle( contentColor = AlertDialogDefaults.textContentColor, - textStyle = MaterialTheme.typography.bodyMedium + textStyle = MaterialTheme.typography.bodyMedium, ) { Box( - Modifier - .weight(weight = 1f, fill = false) + Modifier.weight(weight = 1f, fill = false) .padding(TextPadding) .align(Alignment.Start) ) { @@ -177,7 +146,7 @@ internal fun SettingsAlertDialogContent( ProvideContentColorTextStyle( contentColor = MaterialTheme.colorScheme.primary, textStyle = MaterialTheme.typography.labelLarge, - content = buttons + content = buttons, ) } } @@ -188,7 +157,7 @@ internal fun SettingsAlertDialogContent( internal fun AlertDialogFlowRow( mainAxisSpacing: Dp, crossAxisSpacing: Dp, - content: @Composable () -> Unit + content: @Composable () -> Unit, ) { Layout(content) { measurables, constraints -> val sequences = mutableListOf<List<Placeable>>() @@ -204,8 +173,9 @@ internal fun AlertDialogFlowRow( // Return whether the placeable can be added to the current sequence. fun canAddToCurrentSequence(placeable: Placeable) = - currentSequence.isEmpty() || currentMainAxisSize + mainAxisSpacing.roundToPx() + - placeable.width <= constraints.maxWidth + currentSequence.isEmpty() || + currentMainAxisSize + mainAxisSpacing.roundToPx() + placeable.width <= + constraints.maxWidth // Store current sequence information and start a new sequence. fun startNewSequence() { @@ -213,8 +183,7 @@ internal fun AlertDialogFlowRow( crossAxisSpace += crossAxisSpacing.roundToPx() } // Ensures that confirming actions appear above dismissive actions. - @Suppress("ListIterator") - sequences.add(0, currentSequence.toList()) + @Suppress("ListIterator") sequences.add(0, currentSequence.toList()) crossAxisSizes += currentCrossAxisSize crossAxisPositions += crossAxisSpace @@ -254,23 +223,23 @@ internal fun AlertDialogFlowRow( layout(layoutWidth, layoutHeight) { sequences.fastForEachIndexed { i, placeables -> - val childrenMainAxisSizes = IntArray(placeables.size) { j -> - placeables[j].width + - if (j < placeables.lastIndex) mainAxisSpacing.roundToPx() else 0 - } + val childrenMainAxisSizes = + IntArray(placeables.size) { j -> + placeables[j].width + + if (j < placeables.lastIndex) mainAxisSpacing.roundToPx() else 0 + } val arrangement = Arrangement.End val mainAxisPositions = IntArray(childrenMainAxisSizes.size) { 0 } with(arrangement) { arrange( - mainAxisLayoutSize, childrenMainAxisSizes, - layoutDirection, mainAxisPositions + mainAxisLayoutSize, + childrenMainAxisSizes, + layoutDirection, + mainAxisPositions, ) } placeables.fastForEachIndexed { j, placeable -> - placeable.place( - x = mainAxisPositions[j], - y = crossAxisPositions[i] - ) + placeable.place(x = mainAxisPositions[j], y = crossAxisPositions[i]) } } } @@ -289,8 +258,8 @@ private val ButtonsCrossAxisSpacing = 12.dp /** * ProvideContentColorTextStyle * - * A convenience method to provide values to both LocalContentColor and LocalTextStyle in - * one call. This is less expensive than nesting calls to CompositionLocalProvider. + * A convenience method to provide values to both LocalContentColor and LocalTextStyle in one call. + * This is less expensive than nesting calls to CompositionLocalProvider. * * Text styles will be merged with the current value of LocalTextStyle. */ @@ -298,12 +267,12 @@ private val ButtonsCrossAxisSpacing = 12.dp private fun ProvideContentColorTextStyle( contentColor: Color, textStyle: TextStyle, - content: @Composable () -> Unit + content: @Composable () -> Unit, ) { val mergedStyle = LocalTextStyle.current.merge(textStyle) CompositionLocalProvider( LocalContentColor provides contentColor, LocalTextStyle provides mergedStyle, - content = content + content = content, ) } diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index 0b094a2f70e2..34e33c0df8f5 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -1639,7 +1639,7 @@ <string name="cached_apps_freezer_reboot_dialog_text">Your device must be rebooted for this change to apply. Reboot now or cancel.</string> <!-- Name of the 3.5mm and usb audio device. [CHAR LIMIT=50] --> - <string name="media_transfer_wired_usb_device_name">Wired headphone</string> + <string name="media_transfer_wired_headphone_name">Wired headphone</string> <!-- Name of the 3.5mm headphone, used in desktop devices. [CHAR LIMIT=50] --> <string name="media_transfer_headphone_name">Headphone</string> diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java index 6f2567b9c5dc..a3f9e515a0bc 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java @@ -80,7 +80,9 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { "com.android.settings.action.BLUETOOTH_LE_AUDIO_SHARING_STATE_CHANGE"; public static final String EXTRA_LE_AUDIO_SHARING_STATE = "BLUETOOTH_LE_AUDIO_SHARING_STATE"; public static final String EXTRA_BLUETOOTH_DEVICE = "BLUETOOTH_DEVICE"; + public static final String EXTRA_BT_DEVICE_TO_AUTO_ADD_SOURCE = "BT_DEVICE_TO_AUTO_ADD_SOURCE"; public static final String EXTRA_START_LE_AUDIO_SHARING = "START_LE_AUDIO_SHARING"; + public static final String EXTRA_PAIR_AND_JOIN_SHARING = "PAIR_AND_JOIN_SHARING"; public static final int BROADCAST_STATE_UNKNOWN = 0; public static final int BROADCAST_STATE_ON = 1; public static final int BROADCAST_STATE_OFF = 2; @@ -1224,7 +1226,7 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { Intent intent = new Intent(ACTION_LE_AUDIO_SHARING_STATE_CHANGE); intent.putExtra(EXTRA_LE_AUDIO_SHARING_STATE, state); intent.setPackage(mContext.getPackageName()); - Log.e(TAG, "notifyBroadcastStateChange for state = " + state); + Log.d(TAG, "notifyBroadcastStateChange for state = " + state); mContext.sendBroadcast(intent); } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreference.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreference.java index 01bb6f013d16..7ee7180811dc 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreference.java @@ -33,6 +33,7 @@ public class MultiTogglePreference extends DeviceSettingPreference implements Pa private final String mTitle; private final ImmutableList<ToggleInfo> mToggleInfos; private final int mState; + private final boolean mIsActive; private final boolean mIsAllowedChangingState; private final Bundle mExtras; @@ -40,6 +41,7 @@ public class MultiTogglePreference extends DeviceSettingPreference implements Pa @NonNull String title, List<ToggleInfo> toggleInfos, int state, + boolean isActive, boolean allowChangingState, Bundle extras) { super(DeviceSettingType.DEVICE_SETTING_TYPE_MULTI_TOGGLE); @@ -47,6 +49,7 @@ public class MultiTogglePreference extends DeviceSettingPreference implements Pa mTitle = title; mToggleInfos = ImmutableList.copyOf(toggleInfos); mState = state; + mIsActive = isActive; mIsAllowedChangingState = allowChangingState; mExtras = extras; } @@ -67,9 +70,11 @@ public class MultiTogglePreference extends DeviceSettingPreference implements Pa List<ToggleInfo> toggleInfos = new ArrayList<>(); in.readTypedList(toggleInfos, ToggleInfo.CREATOR); int state = in.readInt(); + boolean isActive = in.readBoolean(); boolean allowChangingState = in.readBoolean(); Bundle extras = in.readBundle(Bundle.class.getClassLoader()); - return new MultiTogglePreference(title, toggleInfos, state, allowChangingState, extras); + return new MultiTogglePreference( + title, toggleInfos, state, isActive, allowChangingState, extras); } public static final Creator<MultiTogglePreference> CREATOR = @@ -99,6 +104,7 @@ public class MultiTogglePreference extends DeviceSettingPreference implements Pa dest.writeString(mTitle); dest.writeTypedList(mToggleInfos, flags); dest.writeInt(mState); + dest.writeBoolean(mIsActive); dest.writeBoolean(mIsAllowedChangingState); dest.writeBundle(mExtras); } @@ -108,6 +114,7 @@ public class MultiTogglePreference extends DeviceSettingPreference implements Pa private String mTitle; private ImmutableList.Builder<ToggleInfo> mToggleInfos = new ImmutableList.Builder<>(); private int mState; + private boolean mIsActive; private boolean mAllowChangingState; private Bundle mExtras = Bundle.EMPTY; @@ -148,6 +155,19 @@ public class MultiTogglePreference extends DeviceSettingPreference implements Pa } /** + * Sets whether the current state is considered as an "active" state. If it's set to true, + * the toggle will be highlighted in UI. + * + * @param isActive The active state. + * @return Returns the Builder object. + */ + @NonNull + public Builder setIsActive(boolean isActive) { + mIsActive = isActive; + return this; + } + + /** * Sets whether state can be changed by user. * * @param allowChangingState Whether user is allowed to change state. @@ -178,7 +198,7 @@ public class MultiTogglePreference extends DeviceSettingPreference implements Pa @NonNull public MultiTogglePreference build() { return new MultiTogglePreference( - mTitle, mToggleInfos.build(), mState, mAllowChangingState, mExtras); + mTitle, mToggleInfos.build(), mState, mIsActive, mAllowChangingState, mExtras); } } @@ -202,6 +222,16 @@ public class MultiTogglePreference extends DeviceSettingPreference implements Pa } /** + * Whether the current state is considered as an active state. If it's set to true, the toggle + * will be highlighted in UI. + * + * @return Returns the active state. + */ + public boolean isActive() { + return mIsActive; + } + + /** * Gets the toggle list in the preference. * * @return the toggle list. 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 29664f63d3b2..851b614f5279 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 @@ -159,7 +159,7 @@ class DeviceSettingRepositoryImpl( title = pref.title, toggles = pref.toggleInfos.map { it.toModel() }, isAllowedChangingState = pref.isAllowedChangingState, - isActive = true, + isActive = pref.isActive, state = DeviceSettingStateModel.MultiTogglePreferenceState(pref.state), updateState = { newState -> coroutineScope.launch(backgroundCoroutineContext) { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt index 7eae5b2a1f5f..3d8ff86c9377 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt @@ -56,11 +56,13 @@ import kotlinx.coroutines.flow.flatMapConcat import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch +import kotlinx.coroutines.plus import kotlinx.coroutines.withContext @OptIn(ExperimentalCoroutinesApi::class) @@ -101,8 +103,7 @@ class DeviceSettingServiceConnection( } else if (allStatus.all { it is ServiceConnectionStatus.Connected }) { allStatus .filterIsInstance< - ServiceConnectionStatus.Connected< - IDeviceSettingsProviderService> + ServiceConnectionStatus.Connected<IDeviceSettingsProviderService> >() .all { it.service.serviceStatus?.enabled == true } } else { @@ -232,7 +233,7 @@ class DeviceSettingServiceConnection( IDeviceSettingsProviderService.Stub::asInterface, ) .stateIn( - coroutineScope, + coroutineScope.plus(backgroundCoroutineContext), SharingStarted.WhileSubscribed(), ServiceConnectionStatus.Connecting, ) @@ -263,21 +264,30 @@ class DeviceSettingServiceConnection( transform: ((IBinder) -> T), ): Flow<ServiceConnectionStatus<T>> { return callbackFlow { - val serviceConnection = - object : ServiceConnection { - override fun onServiceConnected(name: ComponentName, service: IBinder) { - launch { send(ServiceConnectionStatus.Connected(transform(service))) } - } + val serviceConnection = + object : ServiceConnection { + override fun onServiceConnected(name: ComponentName, service: IBinder) { + launch { send(ServiceConnectionStatus.Connected(transform(service))) } + } - override fun onServiceDisconnected(name: ComponentName?) { - launch { send(ServiceConnectionStatus.Connecting) } + override fun onServiceDisconnected(name: ComponentName?) { + launch { send(ServiceConnectionStatus.Connecting) } + } } + if ( + !context.bindService( + intent, + Context.BIND_AUTO_CREATE, + { launch { it.run() } }, + serviceConnection, + ) + ) { + Log.w(TAG, "Fail to bind service $intent") + launch { send(ServiceConnectionStatus.Failed) } } - if (!context.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)) { - launch { send(ServiceConnectionStatus.Failed) } + awaitClose { context.unbindService(serviceConnection) } } - awaitClose { context.unbindService(serviceConnection) } - } + .flowOn(backgroundCoroutineContext) } private suspend fun tryGetEndpointFromMetadata(cachedDevice: CachedBluetoothDevice): EndPoint? = diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java index 116de567a7da..0b8fb22cef3a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java @@ -90,7 +90,7 @@ public class PhoneMediaDevice extends MediaDevice { name = inputRoutingEnabledAndIsDesktop() ? context.getString(R.string.media_transfer_headphone_name) - : context.getString(R.string.media_transfer_wired_usb_device_name); + : context.getString(R.string.media_transfer_wired_headphone_name); break; case TYPE_USB_DEVICE: case TYPE_USB_HEADSET: @@ -98,7 +98,7 @@ public class PhoneMediaDevice extends MediaDevice { name = inputRoutingEnabledAndIsDesktop() ? context.getString(R.string.media_transfer_usb_speaker_name) - : context.getString(R.string.media_transfer_wired_usb_device_name); + : context.getString(R.string.media_transfer_wired_headphone_name); break; case TYPE_DOCK: name = context.getString(R.string.media_transfer_dock_speaker_device_name); diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java b/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java index 251cd3615897..9a9a96012984 100644 --- a/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java +++ b/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java @@ -87,7 +87,7 @@ public class ConversationIconFactory extends BaseIconFactory { * Returns the conversation info drawable */ public Drawable getBaseIconDrawable(ShortcutInfo shortcutInfo) { - return mLauncherApps.getShortcutIconDrawable(shortcutInfo, mFillResIconDpi); + return mLauncherApps.getShortcutIconDrawable(shortcutInfo, mFullResIconDpi); } /** diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreferenceTest.java index 62fcb5ec3011..1c7b5bceafe7 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreferenceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreferenceTest.java @@ -120,6 +120,7 @@ public final class MultiTogglePreferenceTest { .addToggleInfo(TOGGLE_INFO_1) .addToggleInfo(TOGGLE_INFO_2) .setState(123) + .setIsActive(true) .setAllowChangingState(true) .setExtras(buildBundle("key1", "value1")) .build(); @@ -130,6 +131,7 @@ public final class MultiTogglePreferenceTest { assertThat(fromParcel.getToggleInfos().stream().map(ToggleInfo::getLabel).toList()) .containsExactly("label1", "label2"); assertThat(fromParcel.getState()).isEqualTo(preference.getState()); + assertThat(fromParcel.isActive()).isEqualTo(preference.isActive()); assertThat(fromParcel.isAllowedChangingState()) .isEqualTo(preference.isAllowedChangingState()); assertThat(fromParcel.getExtras().getString("key1")) 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 81b56343ceed..0cb6bc1b1261 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 @@ -102,9 +102,9 @@ class DeviceSettingRepositoryTest { `when`(settingProviderService2.queryLocalInterface(anyString())) .thenReturn(settingProviderService2) - `when`(context.bindService(any(), any(), anyInt())).then { input -> + `when`(context.bindService(any(), anyInt(), any(), any())).then { input -> val intent = input.getArgument<Intent?>(0) - val connection = input.getArgument<ServiceConnection>(1) + val connection = input.getArgument<ServiceConnection>(3) when (intent?.action) { CONFIG_SERVICE_INTENT_ACTION -> @@ -210,7 +210,7 @@ class DeviceSettingRepositoryTest { fun getDeviceSettingsConfig_bindingServiceFail_returnNull() { testScope.runTest { `when`(configService.getDeviceSettingsConfig(any())).thenReturn(DEVICE_SETTING_CONFIG) - doReturn(false).`when`(context).bindService(any(), any(), anyInt()) + doReturn(false).`when`(context).bindService(any(), anyInt(), any(), any()) val config = underTest.getDeviceSettingsConfig(cachedDevice) diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java index 23cfc01b07b8..da5f428ce23b 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java @@ -106,12 +106,12 @@ public class PhoneMediaDeviceTest { when(mInfo.getName()).thenReturn(deviceName); assertThat(mPhoneMediaDevice.getName()) - .isEqualTo(mContext.getString(R.string.media_transfer_wired_usb_device_name)); + .isEqualTo(mContext.getString(R.string.media_transfer_wired_headphone_name)); when(mInfo.getType()).thenReturn(TYPE_USB_DEVICE); assertThat(mPhoneMediaDevice.getName()) - .isEqualTo(mContext.getString(R.string.media_transfer_wired_usb_device_name)); + .isEqualTo(mContext.getString(R.string.media_transfer_wired_headphone_name)); when(mInfo.getType()).thenReturn(TYPE_BUILTIN_SPEAKER); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index ba59ce81d362..0ae4da53045e 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -545,6 +545,10 @@ public class SettingsProvider extends ContentProvider { reportDeviceConfigAccess(prefix); return result; } + case Settings.CALL_METHOD_LIST_NAMESPACES_CONFIG -> { + Bundle result = packageNamespacesForCallResult(getAllConfigFlagNamespaces()); + return result; + } case Settings.CALL_METHOD_REGISTER_MONITOR_CALLBACK_CONFIG -> { RemoteCallback callback = args.getParcelable( Settings.CALL_METHOD_MONITOR_CALLBACK_KEY); @@ -1337,6 +1341,23 @@ public class SettingsProvider extends ContentProvider { } @NonNull + private HashSet<String> getAllConfigFlagNamespaces() { + Set<String> flagNames = getAllConfigFlags(null).keySet(); + HashSet<String> namespaces = new HashSet(); + for (String name : flagNames) { + int slashIndex = name.indexOf("/"); + boolean validSlashIndex = slashIndex != -1 + && slashIndex != 0 + && slashIndex != name.length(); + if (validSlashIndex) { + String namespace = name.substring(0, slashIndex); + namespaces.add(namespace); + } + } + return namespaces; + } + + @NonNull private HashMap<String, String> getAllConfigFlags(@Nullable String prefix) { if (DEBUG) { Slog.v(LOG_TAG, "getAllConfigFlags() for " + prefix); @@ -2561,6 +2582,12 @@ public class SettingsProvider extends ContentProvider { return result; } + private Bundle packageNamespacesForCallResult(@NonNull HashSet<String> namespaces) { + Bundle result = new Bundle(); + result.putSerializable(Settings.NameValueTable.VALUE, namespaces); + return result; + } + private void setMonitorCallback(RemoteCallback callback) { if (callback == null) { return; diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 98c491b82e2e..c49ffb49a1da 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -555,6 +555,13 @@ flag { } flag { + name: "status_bar_simple_fragment" + namespace: "systemui" + description: "Feature flag for refactoring the collapsed status bar fragment" + bug: "364360986" +} + +flag { name: "new_volume_panel" namespace: "systemui" description: "Switches to the new volume panel (without Slices)." @@ -1056,6 +1063,13 @@ flag { } flag { + name: "communal_widget_resizing" + namespace: "systemui" + description: "Allow resizing of widgets on glanceable hub" + bug: "368053818" +} + +flag { name: "app_clips_backlinks" namespace: "systemui" description: "Enables Backlinks improvement feature in App Clips" @@ -1408,3 +1422,13 @@ flag { description: "Allow non-touchscreen devices to bypass falsing" bug: "319809270" } + +flag { + name: "media_projection_dialog_behind_lockscreen" + namespace: "systemui" + description: "Ensure MediaProjection Dialog appears behind the lockscreen" + bug: "351409536" + metadata { + purpose: PURPOSE_BUGFIX + } +}
\ No newline at end of file diff --git a/packages/SystemUI/compose/core/src/com/android/compose/windowsizeclass/WindowSizeClass.kt b/packages/SystemUI/compose/core/src/com/android/compose/windowsizeclass/WindowSizeClass.kt index 7468650e88d3..4674d6e5f25a 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/windowsizeclass/WindowSizeClass.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/windowsizeclass/WindowSizeClass.kt @@ -16,16 +16,15 @@ package com.android.compose.windowsizeclass -import android.view.WindowManager import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi import androidx.compose.material3.windowsizeclass.WindowSizeClass import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.ui.graphics.toComposeRect import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity +import androidx.window.layout.WindowMetricsCalculator val LocalWindowSizeClass = staticCompositionLocalOf<WindowSizeClass> { @@ -42,9 +41,7 @@ fun calculateWindowSizeClass(): WindowSizeClass { LocalConfiguration.current val density = LocalDensity.current val context = LocalContext.current - val metrics = - remember(context) { context.getSystemService(WindowManager::class.java)!! } - .currentWindowMetrics + val metrics = WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(context) val size = with(density) { metrics.bounds.toComposeRect().size.toDpSize() } return WindowSizeClass.calculateFromSize(size) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java index 956c12916c98..2028d28804bd 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java @@ -25,6 +25,8 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.view.MotionEvent; import android.view.accessibility.AccessibilityManager; @@ -33,6 +35,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.testing.FakeMetricsLogger; +import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -201,6 +204,7 @@ public class BrightLineFalsingManagerTest extends SysuiTestCase { } @Test + @DisableFlags(Flags.FLAG_NON_TOUCHSCREEN_DEVICES_BYPASS_FALSING) public void testTrackpadGesture() { assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isTrue(); when(mFalsingDataProvider.isFromTrackpad()).thenReturn(true); @@ -208,6 +212,14 @@ public class BrightLineFalsingManagerTest extends SysuiTestCase { } @Test + @EnableFlags(Flags.FLAG_NON_TOUCHSCREEN_DEVICES_BYPASS_FALSING) + public void testTrackpadGesture_touchScreenSource_false() { + assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isTrue(); + when(mFalsingDataProvider.isTouchScreenSource()).thenReturn(false); + assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isFalse(); + } + + @Test public void testAddAndRemoveFalsingBeliefListener() { verify(mHistoryTracker, never()).addBeliefListener(any()); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/FalsingDataProviderTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/FalsingDataProviderTest.java index df4b0480f5c7..5a4799cecae5 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/FalsingDataProviderTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/FalsingDataProviderTest.java @@ -312,6 +312,7 @@ public class FalsingDataProviderTest extends ClassifierTest { } @Test + @DisableFlags(Flags.FLAG_NON_TOUCHSCREEN_DEVICES_BYPASS_FALSING) public void test_IsFromTrackpad() { MotionEvent motionEventOrigin = appendTrackpadDownEvent(0, 0); 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 70529cc762e0..ee65fbd810ae 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt @@ -21,6 +21,8 @@ import android.platform.test.annotations.EnableFlags import android.provider.Settings import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.internal.logging.uiEventLogger +import com.android.internal.logging.uiEventLoggerFake import com.android.systemui.Flags.FLAG_COMMUNAL_HUB import com.android.systemui.Flags.FLAG_COMMUNAL_SCENE_KTF_REFACTOR import com.android.systemui.SysuiTestCase @@ -28,6 +30,7 @@ import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.communal.domain.interactor.communalSceneInteractor import com.android.systemui.communal.domain.interactor.communalSettingsInteractor import com.android.systemui.communal.domain.interactor.setCommunalAvailable +import com.android.systemui.communal.shared.log.CommunalUiEvent import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.communal.shared.model.EditModeState import com.android.systemui.coroutines.collectLastValue @@ -35,7 +38,6 @@ import com.android.systemui.dock.dockManager import com.android.systemui.dock.fakeDockManager import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED import com.android.systemui.flags.fakeFeatureFlagsClassic -import com.android.systemui.flags.featureFlagsClassic import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.keyguardInteractor @@ -93,6 +95,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { bgScope = applicationCoroutineScope, mainDispatcher = testDispatcher, centralSurfacesOpt = centralSurfacesOptional, + uiEventLogger = uiEventLoggerFake, ) .apply { start() } @@ -119,7 +122,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { fakeKeyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.PRIMARY_BOUNCER, to = KeyguardState.GONE, - testScope = this + testScope = this, ) assertThat(scene).isEqualTo(CommunalScenes.Communal) @@ -140,7 +143,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { fakeKeyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.PRIMARY_BOUNCER, to = KeyguardState.GONE, - testScope = this + testScope = this, ) assertThat(scene).isEqualTo(CommunalScenes.Communal) @@ -161,7 +164,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { fakeKeyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.PRIMARY_BOUNCER, to = KeyguardState.GONE, - testScope = this + testScope = this, ) assertThat(scene).isEqualTo(CommunalScenes.Blank) @@ -181,7 +184,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { fakeKeyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.ALTERNATE_BOUNCER, to = KeyguardState.GONE, - testScope = this + testScope = this, ) // Scene change will be handled in EditWidgetsActivity not here assertThat(scene).isEqualTo(CommunalScenes.Communal) @@ -200,7 +203,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { fakeKeyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN, - testScope = this + testScope = this, ) assertThat(scene).isEqualTo(CommunalScenes.Communal) } @@ -220,7 +223,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { fakeKeyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.GLANCEABLE_HUB, to = KeyguardState.OCCLUDED, - testScope = this + testScope = this, ) assertThat(scene).isEqualTo(CommunalScenes.Blank) } @@ -240,7 +243,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { fakeKeyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.GLANCEABLE_HUB, to = KeyguardState.OCCLUDED, - testScope = this + testScope = this, ) assertThat(scene).isEqualTo(CommunalScenes.Communal) } @@ -258,7 +261,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { fakeKeyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.GLANCEABLE_HUB, to = KeyguardState.LOCKSCREEN, - testScope = this + testScope = this, ) assertThat(scene).isEqualTo(CommunalScenes.Blank) } @@ -276,7 +279,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { fakeKeyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.GLANCEABLE_HUB, to = KeyguardState.OFF, - testScope = this + testScope = this, ) assertThat(scene).isEqualTo(CommunalScenes.Communal) @@ -298,7 +301,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { fakeKeyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.GLANCEABLE_HUB, to = KeyguardState.OFF, - testScope = this + testScope = this, ) assertThat(scene).isEqualTo(CommunalScenes.Communal) advanceTimeBy(CommunalSceneStartable.AWAKE_DEBOUNCE_DELAY / 2) @@ -307,7 +310,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { fakeKeyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.OFF, to = KeyguardState.GLANCEABLE_HUB, - testScope = this + testScope = this, ) advanceTimeBy(CommunalSceneStartable.AWAKE_DEBOUNCE_DELAY) @@ -327,7 +330,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { fakeKeyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.GLANCEABLE_HUB, to = KeyguardState.LOCKSCREEN, - testScope = this + testScope = this, ) updateDocked(true) @@ -349,7 +352,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { fakeKeyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.GLANCEABLE_HUB, to = KeyguardState.LOCKSCREEN, - testScope = this + testScope = this, ) updateDocked(true) @@ -361,7 +364,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { fakeKeyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.LOCKSCREEN, to = KeyguardState.DREAMING, - testScope = this + testScope = this, ) advanceTimeBy(CommunalSceneStartable.DOCK_DEBOUNCE_DELAY) assertThat(scene).isEqualTo(CommunalScenes.Blank) @@ -511,6 +514,9 @@ class CommunalSceneStartableTest : SysuiTestCase() { advanceTimeBy(SCREEN_TIMEOUT.milliseconds) assertThat(scene).isEqualTo(CommunalScenes.Blank) + assertThat(uiEventLoggerFake.logs.first().eventId) + .isEqualTo(CommunalUiEvent.COMMUNAL_HUB_TIMEOUT.id) + assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1) } } @@ -526,7 +532,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { fakeKeyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.DOZING, to = KeyguardState.GLANCEABLE_HUB, - testScope = this + testScope = this, ) assertThat(scene).isEqualTo(CommunalScenes.Communal) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt index 361e768a5b51..8f9e23824809 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt @@ -17,11 +17,13 @@ package com.android.systemui.keyboard.data.repository -import android.hardware.input.InputManager +import android.hardware.input.FakeInputManager +import android.hardware.input.InputManager.InputDeviceListener import android.hardware.input.InputManager.KeyboardBacklightListener import android.hardware.input.KeyboardBacklightState +import android.hardware.input.fakeInputManager import android.testing.TestableLooper -import android.view.InputDevice +import android.view.InputDevice.SOURCE_KEYBOARD import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -30,10 +32,7 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues import com.android.systemui.inputdevice.data.repository.InputDeviceRepository import com.android.systemui.keyboard.data.model.Keyboard -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.mock -import com.android.systemui.util.mockito.nullable -import com.android.systemui.util.mockito.whenever +import com.android.systemui.testKosmos import com.android.systemui.utils.os.FakeHandler import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.CoroutineDispatcher @@ -50,9 +49,10 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.Captor -import org.mockito.Mock import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations +import org.mockito.kotlin.any +import org.mockito.kotlin.anyOrNull @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @@ -60,10 +60,9 @@ import org.mockito.MockitoAnnotations @RunWith(AndroidJUnit4::class) class KeyboardRepositoryTest : SysuiTestCase() { - @Captor - private lateinit var deviceListenerCaptor: ArgumentCaptor<InputManager.InputDeviceListener> + @Captor private lateinit var deviceListenerCaptor: ArgumentCaptor<InputDeviceListener> @Captor private lateinit var backlightListenerCaptor: ArgumentCaptor<KeyboardBacklightListener> - @Mock private lateinit var inputManager: InputManager + private lateinit var fakeInputManager: FakeInputManager private lateinit var underTest: KeyboardRepository private lateinit var dispatcher: CoroutineDispatcher @@ -73,16 +72,14 @@ class KeyboardRepositoryTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) - whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf()) - whenever(inputManager.getInputDevice(any())).then { invocation -> - val id = invocation.arguments.first() - INPUT_DEVICES_MAP[id] - } + fakeInputManager = testKosmos().fakeInputManager dispatcher = StandardTestDispatcher() testScope = TestScope(dispatcher) val handler = FakeHandler(TestableLooper.get(this).looper) - inputDeviceRepo = InputDeviceRepository(handler, testScope.backgroundScope, inputManager) - underTest = KeyboardRepositoryImpl(dispatcher, inputManager, inputDeviceRepo) + inputDeviceRepo = + InputDeviceRepository(handler, testScope.backgroundScope, fakeInputManager.inputManager) + underTest = + KeyboardRepositoryImpl(dispatcher, fakeInputManager.inputManager, inputDeviceRepo) } @Test @@ -95,7 +92,7 @@ class KeyboardRepositoryTest : SysuiTestCase() { @Test fun emitsConnected_ifKeyboardAlreadyConnectedAtTheStart() = testScope.runTest { - whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf(PHYSICAL_FULL_KEYBOARD_ID)) + fakeInputManager.addPhysicalKeyboard(PHYSICAL_FULL_KEYBOARD_ID) val initialValue = underTest.isAnyKeyboardConnected.first() assertThat(initialValue).isTrue() } @@ -103,74 +100,77 @@ class KeyboardRepositoryTest : SysuiTestCase() { @Test fun emitsConnected_whenNewPhysicalKeyboardConnects() = testScope.runTest { - val deviceListener = captureDeviceListener() + captureDeviceListener() val isKeyboardConnected by collectLastValue(underTest.isAnyKeyboardConnected) - deviceListener.onInputDeviceAdded(PHYSICAL_FULL_KEYBOARD_ID) + fakeInputManager.addPhysicalKeyboard(PHYSICAL_FULL_KEYBOARD_ID) assertThat(isKeyboardConnected).isTrue() } @Test - fun emitsDisconnected_whenDeviceWithIdDoesNotExist() = + fun emitsDisconnected_whenDeviceNotFound() = testScope.runTest { - val deviceListener = captureDeviceListener() + captureDeviceListener() val isKeyboardConnected by collectLastValue(underTest.isAnyKeyboardConnected) - - deviceListener.onInputDeviceAdded(NULL_DEVICE_ID) + fakeInputManager.addDevice(NULL_DEVICE_ID, SOURCE_KEYBOARD, isNotFound = true) assertThat(isKeyboardConnected).isFalse() } @Test fun emitsDisconnected_whenKeyboardDisconnects() = testScope.runTest { - val deviceListener = captureDeviceListener() + captureDeviceListener() val isKeyboardConnected by collectLastValue(underTest.isAnyKeyboardConnected) - deviceListener.onInputDeviceAdded(PHYSICAL_FULL_KEYBOARD_ID) + fakeInputManager.addPhysicalKeyboard(PHYSICAL_FULL_KEYBOARD_ID) assertThat(isKeyboardConnected).isTrue() - deviceListener.onInputDeviceRemoved(PHYSICAL_FULL_KEYBOARD_ID) + fakeInputManager.removeDevice(PHYSICAL_FULL_KEYBOARD_ID) assertThat(isKeyboardConnected).isFalse() } - private suspend fun captureDeviceListener(): InputManager.InputDeviceListener { + private suspend fun captureDeviceListener() { underTest.isAnyKeyboardConnected.first() - verify(inputManager).registerInputDeviceListener(deviceListenerCaptor.capture(), nullable()) - return deviceListenerCaptor.value + verify(fakeInputManager.inputManager) + .registerInputDeviceListener(deviceListenerCaptor.capture(), anyOrNull()) + fakeInputManager.registerInputDeviceListener(deviceListenerCaptor.value) } @Test fun emitsDisconnected_whenVirtualOrNotFullKeyboardConnects() = testScope.runTest { - val deviceListener = captureDeviceListener() + captureDeviceListener() val isKeyboardConnected by collectLastValue(underTest.isAnyKeyboardConnected) - deviceListener.onInputDeviceAdded(PHYSICAL_NOT_FULL_KEYBOARD_ID) + fakeInputManager.addPhysicalKeyboard( + PHYSICAL_NOT_FULL_KEYBOARD_ID, + isFullKeyboard = false + ) assertThat(isKeyboardConnected).isFalse() - deviceListener.onInputDeviceAdded(VIRTUAL_FULL_KEYBOARD_ID) + fakeInputManager.addDevice(VIRTUAL_FULL_KEYBOARD_ID, SOURCE_KEYBOARD) assertThat(isKeyboardConnected).isFalse() } @Test fun emitsDisconnected_whenKeyboardDisconnectsAndWasAlreadyConnectedAtTheStart() = testScope.runTest { - val deviceListener = captureDeviceListener() + captureDeviceListener() val isKeyboardConnected by collectLastValue(underTest.isAnyKeyboardConnected) - deviceListener.onInputDeviceRemoved(PHYSICAL_FULL_KEYBOARD_ID) + fakeInputManager.removeDevice(PHYSICAL_FULL_KEYBOARD_ID) assertThat(isKeyboardConnected).isFalse() } @Test fun emitsConnected_whenAnotherDeviceDisconnects() = testScope.runTest { - val deviceListener = captureDeviceListener() + captureDeviceListener() val isKeyboardConnected by collectLastValue(underTest.isAnyKeyboardConnected) - deviceListener.onInputDeviceAdded(PHYSICAL_FULL_KEYBOARD_ID) - deviceListener.onInputDeviceRemoved(VIRTUAL_FULL_KEYBOARD_ID) + fakeInputManager.addPhysicalKeyboard(PHYSICAL_FULL_KEYBOARD_ID) + fakeInputManager.removeDevice(VIRTUAL_FULL_KEYBOARD_ID) assertThat(isKeyboardConnected).isTrue() } @@ -178,12 +178,12 @@ class KeyboardRepositoryTest : SysuiTestCase() { @Test fun emitsConnected_whenOnePhysicalKeyboardDisconnectsButAnotherRemainsConnected() = testScope.runTest { - val deviceListener = captureDeviceListener() + captureDeviceListener() val isKeyboardConnected by collectLastValue(underTest.isAnyKeyboardConnected) - deviceListener.onInputDeviceAdded(PHYSICAL_FULL_KEYBOARD_ID) - deviceListener.onInputDeviceAdded(ANOTHER_PHYSICAL_FULL_KEYBOARD_ID) - deviceListener.onInputDeviceRemoved(ANOTHER_PHYSICAL_FULL_KEYBOARD_ID) + fakeInputManager.addPhysicalKeyboard(PHYSICAL_FULL_KEYBOARD_ID) + fakeInputManager.addPhysicalKeyboard(ANOTHER_PHYSICAL_FULL_KEYBOARD_ID) + fakeInputManager.removeDevice(ANOTHER_PHYSICAL_FULL_KEYBOARD_ID) assertThat(isKeyboardConnected).isTrue() } @@ -195,7 +195,7 @@ class KeyboardRepositoryTest : SysuiTestCase() { // subscribed to and listener is actually registered in inputManager val backlight by collectLastValueImmediately(underTest.backlight) - verify(inputManager) + verify(fakeInputManager.inputManager) .registerKeyboardBacklightListener(any(), backlightListenerCaptor.capture()) backlightListenerCaptor.value.onBacklightChanged(current = 1, max = 5) @@ -217,7 +217,7 @@ class KeyboardRepositoryTest : SysuiTestCase() { fun keyboardBacklightValuesNotPassed_fromBacklightListener_whenNotTriggeredByKeyPress() { testScope.runTest { val backlight by collectLastValueImmediately(underTest.backlight) - verify(inputManager) + verify(fakeInputManager.inputManager) .registerKeyboardBacklightListener(any(), backlightListenerCaptor.capture()) backlightListenerCaptor.value.onBacklightChanged( @@ -233,7 +233,7 @@ class KeyboardRepositoryTest : SysuiTestCase() { fun passesKeyboardBacklightValues_fromBacklightListener_whenTriggeredByKeyPress() { testScope.runTest { val backlight by collectLastValueImmediately(underTest.backlight) - verify(inputManager) + verify(fakeInputManager.inputManager) .registerKeyboardBacklightListener(any(), backlightListenerCaptor.capture()) backlightListenerCaptor.value.onBacklightChanged( @@ -248,14 +248,11 @@ class KeyboardRepositoryTest : SysuiTestCase() { @Test fun passessAllKeyboards_thatWereAlreadyConnectedOnInitialization() { testScope.runTest { - whenever(inputManager.inputDeviceIds) - .thenReturn( - intArrayOf( - PHYSICAL_FULL_KEYBOARD_ID, - ANOTHER_PHYSICAL_FULL_KEYBOARD_ID, - VIRTUAL_FULL_KEYBOARD_ID // not a physical keyboard - that's why result is 2 - ) - ) + fakeInputManager.addPhysicalKeyboard(PHYSICAL_FULL_KEYBOARD_ID) + fakeInputManager.addPhysicalKeyboard(ANOTHER_PHYSICAL_FULL_KEYBOARD_ID) + // not a physical keyboard - that's why result is 2 + fakeInputManager.addDevice(VIRTUAL_FULL_KEYBOARD_ID, SOURCE_KEYBOARD) + val keyboards by collectValues(underTest.newlyConnectedKeyboard) assertThat(keyboards).hasSize(2) @@ -265,9 +262,9 @@ class KeyboardRepositoryTest : SysuiTestCase() { @Test fun passesNewlyConnectedKeyboard() { testScope.runTest { - val deviceListener = captureDeviceListener() + captureDeviceListener() - deviceListener.onInputDeviceAdded(PHYSICAL_FULL_KEYBOARD_ID) + fakeInputManager.addPhysicalKeyboard(PHYSICAL_FULL_KEYBOARD_ID, VENDOR_ID, PRODUCT_ID) assertThat(underTest.newlyConnectedKeyboard.first()) .isEqualTo(Keyboard(VENDOR_ID, PRODUCT_ID)) @@ -277,13 +274,12 @@ class KeyboardRepositoryTest : SysuiTestCase() { @Test fun emitsOnlyNewlyConnectedKeyboards() { testScope.runTest { - whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf(PHYSICAL_FULL_KEYBOARD_ID)) + fakeInputManager.addPhysicalKeyboard(PHYSICAL_FULL_KEYBOARD_ID) underTest.newlyConnectedKeyboard.first() - verify(inputManager) - .registerInputDeviceListener(deviceListenerCaptor.capture(), nullable()) - val deviceListener = deviceListenerCaptor.value - deviceListener.onInputDeviceAdded(ANOTHER_PHYSICAL_FULL_KEYBOARD_ID) + captureDeviceListener() + + fakeInputManager.addPhysicalKeyboard(ANOTHER_PHYSICAL_FULL_KEYBOARD_ID) val keyboards by collectValues(underTest.newlyConnectedKeyboard) assertThat(keyboards).hasSize(1) @@ -293,14 +289,11 @@ class KeyboardRepositoryTest : SysuiTestCase() { @Test fun stillEmitsNewKeyboardEvenIfFlowWasSubscribedAfterOtherFlows() { testScope.runTest { - whenever(inputManager.inputDeviceIds) - .thenReturn( - intArrayOf( - PHYSICAL_FULL_KEYBOARD_ID, - ANOTHER_PHYSICAL_FULL_KEYBOARD_ID, - VIRTUAL_FULL_KEYBOARD_ID // not a physical keyboard - that's why result is 2 - ) - ) + fakeInputManager.addPhysicalKeyboard(PHYSICAL_FULL_KEYBOARD_ID) + fakeInputManager.addPhysicalKeyboard(ANOTHER_PHYSICAL_FULL_KEYBOARD_ID) + // not a physical keyboard - that's why result is 2 + fakeInputManager.addDevice(VIRTUAL_FULL_KEYBOARD_ID, SOURCE_KEYBOARD) + collectLastValueImmediately(underTest.isAnyKeyboardConnected) // let's pretend second flow is subscribed after some delay @@ -314,12 +307,12 @@ class KeyboardRepositoryTest : SysuiTestCase() { @Test fun emitsKeyboardWhenItWasReconnected() { testScope.runTest { - val deviceListener = captureDeviceListener() + captureDeviceListener() val keyboards by collectValues(underTest.newlyConnectedKeyboard) - deviceListener.onInputDeviceAdded(PHYSICAL_FULL_KEYBOARD_ID) - deviceListener.onInputDeviceRemoved(PHYSICAL_FULL_KEYBOARD_ID) - deviceListener.onInputDeviceAdded(PHYSICAL_FULL_KEYBOARD_ID) + fakeInputManager.addPhysicalKeyboard(PHYSICAL_FULL_KEYBOARD_ID) + fakeInputManager.removeDevice(PHYSICAL_FULL_KEYBOARD_ID) + fakeInputManager.addPhysicalKeyboard(PHYSICAL_FULL_KEYBOARD_ID) assertThat(keyboards).hasSize(2) } @@ -339,30 +332,13 @@ class KeyboardRepositoryTest : SysuiTestCase() { private companion object { private const val PHYSICAL_FULL_KEYBOARD_ID = 1 - private const val VIRTUAL_FULL_KEYBOARD_ID = 2 + private const val VIRTUAL_FULL_KEYBOARD_ID = -2 // Virtual keyboards has id with minus value private const val PHYSICAL_NOT_FULL_KEYBOARD_ID = 3 private const val ANOTHER_PHYSICAL_FULL_KEYBOARD_ID = 4 - private const val NULL_DEVICE_ID = 5 + private const val NULL_DEVICE_ID = -5 private const val VENDOR_ID = 99 private const val PRODUCT_ID = 101 - - private val INPUT_DEVICES_MAP: Map<Int, InputDevice> = - mapOf( - PHYSICAL_FULL_KEYBOARD_ID to inputDevice(virtual = false, fullKeyboard = true), - VIRTUAL_FULL_KEYBOARD_ID to inputDevice(virtual = true, fullKeyboard = true), - PHYSICAL_NOT_FULL_KEYBOARD_ID to inputDevice(virtual = false, fullKeyboard = false), - ANOTHER_PHYSICAL_FULL_KEYBOARD_ID to - inputDevice(virtual = false, fullKeyboard = true) - ) - - private fun inputDevice(virtual: Boolean, fullKeyboard: Boolean): InputDevice = - mock<InputDevice>().also { - whenever(it.isVirtual).thenReturn(virtual) - whenever(it.isFullKeyboard).thenReturn(fullKeyboard) - whenever(it.vendorId).thenReturn(VENDOR_ID) - whenever(it.productId).thenReturn(PRODUCT_ID) - } } private class TestBacklightState( diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt index c18deb134075..fac931273ac7 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt @@ -14,22 +14,6 @@ * limitations under the License. */ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - package com.android.systemui.keyguard.domain.interactor import android.os.PowerManager @@ -47,7 +31,6 @@ import com.android.systemui.communal.data.repository.FakeCommunalSceneRepository import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository import com.android.systemui.communal.domain.interactor.setCommunalAvailable import com.android.systemui.communal.shared.model.CommunalScenes -import com.android.systemui.communal.shared.model.CommunalTransitionKeys import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository @@ -129,7 +112,7 @@ class FromDozingTransitionInteractorTest(flags: FlagsParameterization?) : SysuiT transitionRepository.sendTransitionSteps( from = KeyguardState.LOCKSCREEN, to = KeyguardState.DOZING, - testScope + testScope, ) kosmos.fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockMode.NONE) reset(transitionRepository) @@ -145,10 +128,7 @@ class FromDozingTransitionInteractorTest(flags: FlagsParameterization?) : SysuiT // Under default conditions, we should transition to LOCKSCREEN when waking up. assertThat(transitionRepository) - .startedTransition( - from = KeyguardState.DOZING, - to = KeyguardState.LOCKSCREEN, - ) + .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.LOCKSCREEN) } @Test @@ -166,10 +146,7 @@ class FromDozingTransitionInteractorTest(flags: FlagsParameterization?) : SysuiT // If dreaming is possible and communal is available, then we should transition to // GLANCEABLE_HUB when waking up due to power button press. assertThat(transitionRepository) - .startedTransition( - from = KeyguardState.DOZING, - to = KeyguardState.GLANCEABLE_HUB, - ) + .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.GLANCEABLE_HUB) } @Test @@ -186,8 +163,7 @@ class FromDozingTransitionInteractorTest(flags: FlagsParameterization?) : SysuiT // If dreaming is possible and communal is available, then we should transition to // GLANCEABLE_HUB when waking up due to power button press. - verify(kosmos.fakeCommunalSceneRepository) - .changeScene(CommunalScenes.Communal, CommunalTransitionKeys.Immediately) + verify(kosmos.fakeCommunalSceneRepository).snapToScene(CommunalScenes.Communal) } @Test @@ -204,10 +180,7 @@ class FromDozingTransitionInteractorTest(flags: FlagsParameterization?) : SysuiT // If dreaming is NOT possible but communal is available, then we should transition to // LOCKSCREEN when waking up due to power button press. assertThat(transitionRepository) - .startedTransition( - from = KeyguardState.DOZING, - to = KeyguardState.LOCKSCREEN, - ) + .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.LOCKSCREEN) } @Test @@ -224,10 +197,7 @@ class FromDozingTransitionInteractorTest(flags: FlagsParameterization?) : SysuiT // If dreaming is possible but communal is NOT available, then we should transition to // LOCKSCREEN when waking up due to power button press. assertThat(transitionRepository) - .startedTransition( - from = KeyguardState.DOZING, - to = KeyguardState.LOCKSCREEN, - ) + .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.LOCKSCREEN) } @Test @@ -245,10 +215,7 @@ class FromDozingTransitionInteractorTest(flags: FlagsParameterization?) : SysuiT // Under default conditions, we should transition to LOCKSCREEN when waking up. assertThat(transitionRepository) - .startedTransition( - from = KeyguardState.DOZING, - to = KeyguardState.GLANCEABLE_HUB, - ) + .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.GLANCEABLE_HUB) } @Test @@ -261,10 +228,7 @@ class FromDozingTransitionInteractorTest(flags: FlagsParameterization?) : SysuiT // Waking with a SHOW_WHEN_LOCKED activity on top should transition to OCCLUDED. assertThat(transitionRepository) - .startedTransition( - from = KeyguardState.DOZING, - to = KeyguardState.OCCLUDED, - ) + .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.OCCLUDED) } @Test @@ -282,10 +246,7 @@ class FromDozingTransitionInteractorTest(flags: FlagsParameterization?) : SysuiT // Waking with a SHOW_WHEN_LOCKED activity on top should transition to OCCLUDED. assertThat(transitionRepository) - .startedTransition( - from = KeyguardState.DOZING, - to = KeyguardState.OCCLUDED, - ) + .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.OCCLUDED) } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt index d594f3a2f932..62d06254e541 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt @@ -121,7 +121,7 @@ class MediaControlInteractorTest : SysuiTestCase() { MediaData( userId = USER_ID, instanceId = InstanceId.fakeInstanceId(2), - artist = ARTIST + artist = ARTIST, ) mediaDataFilter.onMediaDataLoaded(KEY, KEY, mediaData) @@ -145,10 +145,17 @@ class MediaControlInteractorTest : SysuiTestCase() { val clickIntent = mock<PendingIntent> { whenever(it.isActivity).thenReturn(true) } val expandable = mock<Expandable>() + val activityController = mock<ActivityTransitionAnimator.Controller>() + whenever(expandable.activityTransitionController(any())).thenReturn(activityController) underTest.startClickIntent(expandable, clickIntent, SMARTSPACE_CARD_CLICK_EVENT, 1) - verify(clickIntent).send(any<Bundle>()) + verify(activityStarter) + .startPendingIntentMaybeDismissingKeyguard( + eq(clickIntent), + eq(null), + eq(activityController), + ) } @Test @@ -174,7 +181,7 @@ class MediaControlInteractorTest : SysuiTestCase() { mediaData.appUid, surface = SURFACE, cardinality = 2, - rank = 1 + rank = 1, ) verify(activityStarter) .postStartActivityDismissingKeyguard(eq(clickIntent), eq(activityController)) @@ -232,7 +239,7 @@ class MediaControlInteractorTest : SysuiTestCase() { eq(true), eq(dialogTransitionController), eq(null), - eq(null) + eq(null), ) } @@ -248,7 +255,7 @@ class MediaControlInteractorTest : SysuiTestCase() { .createBroadcastDialogWithController( eq(APP_NAME), eq(PACKAGE_NAME), - eq(dialogTransitionController) + eq(dialogTransitionController), ) } @@ -279,7 +286,7 @@ class MediaControlInteractorTest : SysuiTestCase() { anyInt(), anyInt(), anyInt(), - anyBoolean() + anyBoolean(), ) verify(listener).onMediaDataRemoved(eq(KEY), eq(true)) } @@ -307,7 +314,7 @@ class MediaControlInteractorTest : SysuiTestCase() { mediaData.appUid, surface = SURFACE, cardinality = 2, - rank = 1 + rank = 1, ) verify(listener).onMediaDataRemoved(eq(KEY), eq(true)) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModelTest.kt index 9558e5d23a25..01220285e10c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModelTest.kt @@ -24,26 +24,25 @@ import android.media.session.MediaSession import android.media.session.PlaybackState import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.internal.logging.InstanceId import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testScope -import com.android.systemui.media.controls.domain.pipeline.MediaDataFilterImpl import com.android.systemui.media.controls.domain.pipeline.mediaDataFilter import com.android.systemui.media.controls.shared.model.MediaData import com.android.systemui.media.controls.shared.model.MediaDeviceData import com.android.systemui.media.controls.util.mediaInstanceId import com.android.systemui.statusbar.notificationLockscreenUserManager import com.android.systemui.testKosmos -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.eq -import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runTest +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers +import org.mockito.ArgumentMatchers.any import org.mockito.Mockito +import org.mockito.kotlin.eq +import org.mockito.kotlin.whenever @SmallTest @RunWith(AndroidJUnit4::class) @@ -52,30 +51,31 @@ class MediaControlViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope - private val mediaDataFilter: MediaDataFilterImpl = kosmos.mediaDataFilter + private val mediaDataFilter = kosmos.mediaDataFilter private val notificationLockscreenUserManager = kosmos.notificationLockscreenUserManager private val packageManager = kosmos.packageManager private val drawable = context.getDrawable(R.drawable.ic_media_play) - private val instanceId: InstanceId = kosmos.mediaInstanceId - + private val instanceId = kosmos.mediaInstanceId private val underTest: MediaControlViewModel = kosmos.mediaControlViewModel + @Before + fun setUp() { + whenever(packageManager.getApplicationIcon(Mockito.anyString())).thenReturn(drawable) + whenever(packageManager.getApplicationIcon(any(ApplicationInfo::class.java))) + .thenReturn(drawable) + whenever(packageManager.getApplicationInfo(eq(PACKAGE_NAME), ArgumentMatchers.anyInt())) + .thenReturn(ApplicationInfo()) + whenever(packageManager.getApplicationLabel(any())).thenReturn(PACKAGE_NAME) + whenever(notificationLockscreenUserManager.isCurrentProfile(USER_ID)).thenReturn(true) + whenever(notificationLockscreenUserManager.isProfileAvailable(USER_ID)).thenReturn(true) + context.setMockPackageManager(packageManager) + } + @Test fun addMediaControl_mediaControlViewModelIsLoaded() = testScope.runTest { - whenever(packageManager.getApplicationIcon(Mockito.anyString())).thenReturn(drawable) - whenever(packageManager.getApplicationIcon(any(ApplicationInfo::class.java))) - .thenReturn(drawable) - whenever(packageManager.getApplicationInfo(eq(PACKAGE_NAME), ArgumentMatchers.anyInt())) - .thenReturn(ApplicationInfo()) - whenever(packageManager.getApplicationLabel(any())).thenReturn(PACKAGE_NAME) - whenever(notificationLockscreenUserManager.isCurrentProfile(USER_ID)).thenReturn(true) - whenever(notificationLockscreenUserManager.isProfileAvailable(USER_ID)).thenReturn(true) val playerModel by collectLastValue(underTest.player) - - context.setMockPackageManager(packageManager) - - val mediaData = initMediaData() + val mediaData = initMediaData(ARTIST, TITLE) mediaDataFilter.onMediaDataLoaded(KEY, KEY, mediaData) @@ -88,7 +88,51 @@ class MediaControlViewModelTest : SysuiTestCase() { assertThat(playerModel?.playTurbulenceNoise).isFalse() } - private fun initMediaData(): MediaData { + @Test + fun emitDuplicateMediaControls_mediaControlIsNotBound() = + testScope.runTest { + val playerModel by collectLastValue(underTest.player) + val mediaData = initMediaData(ARTIST, TITLE) + + mediaDataFilter.onMediaDataLoaded(KEY, KEY, mediaData) + + assertThat(playerModel).isNotNull() + assertThat(playerModel?.titleName).isEqualTo(TITLE) + assertThat(playerModel?.artistName).isEqualTo(ARTIST) + assertThat(underTest.isNewPlayer(playerModel!!)).isTrue() + + mediaDataFilter.onMediaDataLoaded(KEY, KEY, mediaData) + + assertThat(playerModel).isNotNull() + assertThat(playerModel?.titleName).isEqualTo(TITLE) + assertThat(playerModel?.artistName).isEqualTo(ARTIST) + assertThat(underTest.isNewPlayer(playerModel!!)).isFalse() + } + + @Test + fun emitDifferentMediaControls_mediaControlIsBound() = + testScope.runTest { + val playerModel by collectLastValue(underTest.player) + var mediaData = initMediaData(ARTIST, TITLE) + + mediaDataFilter.onMediaDataLoaded(KEY, KEY, mediaData) + + assertThat(playerModel).isNotNull() + assertThat(playerModel?.titleName).isEqualTo(TITLE) + assertThat(playerModel?.artistName).isEqualTo(ARTIST) + assertThat(underTest.isNewPlayer(playerModel!!)).isTrue() + + mediaData = initMediaData(ARTIST_2, TITLE_2) + + mediaDataFilter.onMediaDataLoaded(KEY, KEY, mediaData) + + assertThat(playerModel).isNotNull() + assertThat(playerModel?.titleName).isEqualTo(TITLE_2) + assertThat(playerModel?.artistName).isEqualTo(ARTIST_2) + assertThat(underTest.isNewPlayer(playerModel!!)).isTrue() + } + + private fun initMediaData(artist: String, title: String): MediaData { val device = MediaDeviceData(true, null, DEVICE_NAME, null, showBroadcastButton = true) // Create media session @@ -111,12 +155,12 @@ class MediaControlViewModelTest : SysuiTestCase() { return MediaData( userId = USER_ID, - artist = ARTIST, - song = TITLE, + artist = artist, + song = title, packageName = PACKAGE, token = session.sessionToken, device = device, - instanceId = instanceId + instanceId = instanceId, ) } @@ -127,6 +171,8 @@ class MediaControlViewModelTest : SysuiTestCase() { private const val PACKAGE = "PKG" private const val ARTIST = "ARTIST" private const val TITLE = "TITLE" + private const val ARTIST_2 = "ARTIST_2" + private const val TITLE_2 = "TITLE_2" private const val DEVICE_NAME = "DEVICE_NAME" private const val SESSION_KEY = "SESSION_KEY" private const val SESSION_ARTIST = "SESSION_ARTIST" diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/NavBarHelperTest.java index a770ee199ba6..a770ee199ba6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/NavBarHelperTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java index 85bc634c28b1..85bc634c28b1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt index eae6cdbe4d2c..eae6cdbe4d2c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt index f3cea3e8bb96..f3cea3e8bb96 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarInflaterViewTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarInflaterViewTest.java index e58c8f281fc1..e58c8f281fc1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarInflaterViewTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarInflaterViewTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java index 2905a7329d21..2905a7329d21 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTransitionsTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTransitionsTest.java index 3621ab975daf..3621ab975daf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTransitionsTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTransitionsTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/buttons/KeyButtonViewTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/buttons/KeyButtonViewTest.java index 403a883e1760..403a883e1760 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/buttons/KeyButtonViewTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/buttons/KeyButtonViewTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/buttons/NavigationBarContextTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/buttons/NavigationBarContextTest.java index 79c4a96e84bb..79c4a96e84bb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/buttons/NavigationBarContextTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/buttons/NavigationBarContextTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/buttons/NearestTouchFrameTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/buttons/NearestTouchFrameTest.java index 7941a68fa91e..7941a68fa91e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/buttons/NearestTouchFrameTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/buttons/NearestTouchFrameTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/FakeNoteTaskBubbleController.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/FakeNoteTaskBubbleController.kt index 450aadd70171..450aadd70171 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/FakeNoteTaskBubbleController.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/FakeNoteTaskBubbleController.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskBubblesServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskBubblesServiceTest.kt index 9ef6b9c13315..9ef6b9c13315 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskBubblesServiceTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskBubblesServiceTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskEventLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskEventLoggerTest.kt index c9a5d0620c65..c9a5d0620c65 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskEventLoggerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskEventLoggerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt index 0c88da750885..0c88da750885 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt index 8f4078b88fc0..8f4078b88fc0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/NotificationHelperTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/people/NotificationHelperTest.java index 3673a25b68b2..3673a25b68b2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/people/NotificationHelperTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/people/NotificationHelperTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleBackupFollowUpJobTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/people/PeopleBackupFollowUpJobTest.java index dae34524c203..dae34524c203 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleBackupFollowUpJobTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/people/PeopleBackupFollowUpJobTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleProviderTestable.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/people/PeopleProviderTestable.java index 3e6d6746a997..3e6d6746a997 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleProviderTestable.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/people/PeopleProviderTestable.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/SharedPreferencesHelperTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/people/SharedPreferencesHelperTest.java index ae7fba939944..ae7fba939944 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/people/SharedPreferencesHelperTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/people/SharedPreferencesHelperTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java index e701dc63325f..e701dc63325f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleBackupHelperTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/people/widget/PeopleBackupHelperTest.java index b3ded15f582f..b3ded15f582f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleBackupHelperTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/people/widget/PeopleBackupHelperTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyChipBuilderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/privacy/PrivacyChipBuilderTest.kt index 0d6652cca0e8..0d6652cca0e8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyChipBuilderTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/privacy/PrivacyChipBuilderTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/privacy/PrivacyDialogTest.kt index 9ac04cf98375..9ac04cf98375 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/privacy/PrivacyDialogTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/process/condition/SystemProcessConditionTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/process/condition/SystemProcessConditionTest.java index 5250d56edc0b..5250d56edc0b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/process/condition/SystemProcessConditionTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/process/condition/SystemProcessConditionTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt index 3ae7a1672821..3ae7a1672821 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/LeftRightArrowPressedListenerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/LeftRightArrowPressedListenerTest.kt index 466a09be4ff3..466a09be4ff3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/LeftRightArrowPressedListenerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/LeftRightArrowPressedListenerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/PagedTileLayoutTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/PagedTileLayoutTest.kt index db8612ad4182..db8612ad4182 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/PagedTileLayoutTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/PagedTileLayoutTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSContainerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSContainerImplTest.kt index 52ad931185e2..52ad931185e2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSContainerImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSContainerImplTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSDisableFlagsLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSDisableFlagsLoggerTest.kt index cc3e27c71564..cc3e27c71564 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSDisableFlagsLoggerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSDisableFlagsLoggerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseSceneContainerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelControllerBaseSceneContainerTest.kt index 07ec38e6ae6c..07ec38e6ae6c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseSceneContainerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelControllerBaseSceneContainerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java index 225adab04ff0..225adab04ff0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelControllerTest.kt index 02c5b5ad214c..02c5b5ad214c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelControllerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelSwitchToParentTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelSwitchToParentTest.kt index 56e25fcd580c..56e25fcd580c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelSwitchToParentTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelSwitchToParentTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSquishinessControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSSquishinessControllerTest.kt index ecdabbf9853e..ecdabbf9853e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSquishinessControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSSquishinessControllerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt index 369bb228494c..369bb228494c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QuickQSPanelTest.kt index 3d6ba94556b7..3d6ba94556b7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QuickQSPanelTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt index a0ccec11fec3..a0ccec11fec3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/TileStateToProtoTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/TileStateToProtoTest.kt index be388a17ab5d..be388a17ab5d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/TileStateToProtoTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/TileStateToProtoTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/TouchAnimatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/TouchAnimatorTest.java index 29e2a8a8824f..29e2a8a8824f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/TouchAnimatorTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/TouchAnimatorTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/UserSettingObserverTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/UserSettingObserverTest.kt index 0c2b59fed078..0c2b59fed078 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/UserSettingObserverTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/UserSettingObserverTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterDelegateTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/customize/TileAdapterDelegateTest.java index 9f2b1ea9e37e..9f2b1ea9e37e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterDelegateTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/customize/TileAdapterDelegateTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/customize/TileAdapterTest.java index cbcd8104ce35..cbcd8104ce35 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/customize/TileAdapterTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java index 09a6c2c7f1f7..09a6c2c7f1f7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/CustomTileTest.kt index bd03acb6e7ec..bd03acb6e7ec 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/CustomTileTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileColorPickerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/TileColorPickerTest.java index eb013c5975b9..eb013c5975b9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileColorPickerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/TileColorPickerTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/TileServiceManagerTest.java index 03483c98856e..03483c98856e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/TileServiceManagerTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt index c5a2370adcda..c5a2370adcda 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt index f90e1e931099..f90e1e931099 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepositoryTest.kt index 090a85b0d3f9..090a85b0d3f9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepositoryTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt index 12c566ca9e2d..12c566ca9e2d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java index 2580ac2c8da7..2580ac2c8da7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/ResourceIconTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/ResourceIconTest.kt index e112bb05ab2e..e112bb05ab2e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/ResourceIconTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/ResourceIconTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt index 7a99aefc98fe..7a99aefc98fe 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt index d6be31450fc0..d6be31450fc0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt index 093cdf22a64b..093cdf22a64b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/CastTileTest.java index 9f12b189d76a..9f12b189d76a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/CastTileTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorCorrectionTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ColorCorrectionTileTest.java index 028beb599644..028beb599644 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorCorrectionTileTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ColorCorrectionTileTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java index 1343527e631b..1343527e631b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt index 73ae4ee5aa0d..73ae4ee5aa0d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt index f90463e7f589..f90463e7f589 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt index c854920cbf1f..c854920cbf1f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileTest.java index 0cf96047fcc0..0cf96047fcc0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/LocationTileTest.kt index 0a1455fe12cc..0a1455fe12cc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/LocationTileTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt index dbdf3a499f8b..dbdf3a499f8b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NfcTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/NfcTileTest.java index 442a94887157..442a94887157 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NfcTileTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/NfcTileTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt index f1c589512895..f1c589512895 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/OneHandedModeTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/OneHandedModeTileTest.java index d6fa124f3f91..d6fa124f3f91 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/OneHandedModeTileTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/OneHandedModeTileTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java index f8f82f2c2ed8..f8f82f2c2ed8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java index 1aff45bf581d..1aff45bf581d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java index 41930636cfa3..41930636cfa3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java index 0d12483bad0a..0d12483bad0a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt index 8324a7303cff..8324a7303cff 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt index c764c548ed92..c764c548ed92 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt index c918ed82604c..c918ed82604c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java index b88861756889..b88861756889 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/WifiStateWorkerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/dialog/WifiStateWorkerTest.java index 57484c21c767..57484c21c767 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/WifiStateWorkerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/dialog/WifiStateWorkerTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt index e7bde681fe6f..e7bde681fe6f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt index ad6c64b32ddc..ad6c64b32ddc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/recordissue/CustomTraceStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/CustomTraceStateTest.kt index d8618fa71aab..d8618fa71aab 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/recordissue/CustomTraceStateTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/CustomTraceStateTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/recordissue/IssueRecordingServiceCommandHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/IssueRecordingServiceCommandHandlerTest.kt index 57cfe1b9e902..57cfe1b9e902 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/recordissue/IssueRecordingServiceCommandHandlerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/IssueRecordingServiceCommandHandlerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/recordissue/IssueRecordingStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/IssueRecordingStateTest.kt index 4ab3c7b203c4..4ab3c7b203c4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/recordissue/IssueRecordingStateTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/IssueRecordingStateTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt index 8d84c3e7392e..8d84c3e7392e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt index ba7a65dd8e65..ba7a65dd8e65 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/retail/domain/interactor/RetailModeInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/retail/domain/interactor/RetailModeInteractorImplTest.kt index b53652067755..b53652067755 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/retail/domain/interactor/RetailModeInteractorImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/retail/domain/interactor/RetailModeInteractorImplTest.kt diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java index 40fb7691c0c2..614d51e7ac99 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java @@ -39,6 +39,7 @@ import static org.mockito.Mockito.when; import android.app.IActivityManager; import android.content.pm.ActivityInfo; import android.content.res.Configuration; +import android.graphics.Rect; import android.platform.test.flag.junit.FlagsParameterization; import android.testing.TestableLooper.RunWithLooper; import android.view.View; @@ -53,6 +54,7 @@ import com.android.systemui.biometrics.AuthController; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.communal.domain.interactor.CommunalInteractor; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.EnableSceneContainer; import com.android.systemui.flags.SceneContainerFlagParameterizationKt; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.kosmos.KosmosJavaAdapter; @@ -466,6 +468,32 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { assertThat(lp.preferredMinDisplayRefreshRate).isEqualTo(0); } + @Test + @EnableSceneContainer + public void configChanged_boundsUpdate() { + when(mNotificationShadeWindowView.getWidth()).thenReturn(1600); + when(mNotificationShadeWindowView.getHeight()).thenReturn(800); + when(mNotificationShadeWindowView.getVisibility()).thenReturn(View.INVISIBLE); + Configuration newConfig = new Configuration(); + // swap width and height in new bounds to simulate auto-rotate + newConfig.windowConfiguration.setBounds(new Rect(0, 0, 800, 1600)); + mNotificationShadeWindowController.onConfigChanged(newConfig); + verify(mWindowManager, atLeastOnce()).updateViewLayout(any(), any()); + } + + @Test + @EnableSceneContainer + public void configChanged_boundsDontUpdate() { + when(mNotificationShadeWindowView.getWidth()).thenReturn(1600); + when(mNotificationShadeWindowView.getHeight()).thenReturn(800); + when(mNotificationShadeWindowView.getVisibility()).thenReturn(View.INVISIBLE); + Configuration newConfig = new Configuration(); + // same bounds as view's current bounds + newConfig.windowConfiguration.setBounds(new Rect(0, 0, 1600, 800)); + mNotificationShadeWindowController.onConfigChanged(newConfig); + verify(mWindowManager, never()).updateViewLayout(any(), any()); + } + private void setKeyguardShowing() { mNotificationShadeWindowController.setKeyguardShowing(true); mNotificationShadeWindowController.setKeyguardGoingAway(false); diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java index 769976ef5058..ae4b679dd4b8 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java @@ -282,6 +282,8 @@ public class FalsingDataProvider { return !mRecentKeyEvents.isEmpty(); } + // Deprecated in favor of {@code isTouchScreenSource}, b/329221787 + @Deprecated public boolean isFromTrackpad() { if (Flags.nonTouchscreenDevicesBypassFalsing()) { return false; diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt index 6e01393015ed..08a7c395e57f 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt @@ -19,11 +19,13 @@ package com.android.systemui.communal import android.provider.Settings import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.TransitionKey +import com.android.internal.logging.UiEventLogger import com.android.systemui.CoreStartable import com.android.systemui.Flags.communalSceneKtfRefactor import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor +import com.android.systemui.communal.shared.log.CommunalUiEvent import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.communal.shared.model.CommunalTransitionKeys import com.android.systemui.communal.shared.model.EditModeState @@ -84,6 +86,7 @@ constructor( @Application private val applicationScope: CoroutineScope, @Background private val bgScope: CoroutineScope, @Main private val mainDispatcher: CoroutineDispatcher, + private val uiEventLogger: UiEventLogger, ) : CoreStartable { private var screenTimeout: Int = DEFAULT_SCREEN_TIMEOUT @@ -146,7 +149,7 @@ constructor( screenTimeout = systemSettings.getInt( Settings.System.SCREEN_OFF_TIMEOUT, - DEFAULT_SCREEN_TIMEOUT + DEFAULT_SCREEN_TIMEOUT, ) } .launchIn(bgScope) @@ -160,7 +163,7 @@ constructor( combine( communalSceneInteractor.currentScene, // Emit a value on start so the combine starts. - communalInteractor.userActivity.emitOnStart() + communalInteractor.userActivity.emitOnStart(), ) { scene, _ -> // Only timeout if we're on the hub is open. scene == CommunalScenes.Communal @@ -184,6 +187,7 @@ constructor( CommunalScenes.Blank, "dream started after timeout", ) + uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_TIMEOUT) } } } @@ -212,6 +216,7 @@ constructor( newScene = CommunalScenes.Blank, loggingReason = "hub timeout", ) + uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_TIMEOUT) } timeoutJob = null } @@ -219,7 +224,7 @@ constructor( } private suspend fun determineSceneAfterTransition( - lastStartedTransition: TransitionStep, + lastStartedTransition: TransitionStep ): Pair<SceneKey, TransitionKey>? { val to = lastStartedTransition.to val from = lastStartedTransition.from @@ -251,9 +256,8 @@ constructor( Pair(CommunalScenes.Blank, CommunalTransitionKeys.SimpleFade) } from == KeyguardState.DOZING && to == KeyguardState.GLANCEABLE_HUB -> { - // Make sure the communal hub is showing (immediately, not fading in) when - // transitioning from dozing to hub. - Pair(CommunalScenes.Communal, CommunalTransitionKeys.Immediately) + // Make sure the communal hub is showing when transitioning from dozing to hub. + Pair(CommunalScenes.Communal, CommunalTransitionKeys.SimpleFade) } else -> null } diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalTransitionKeys.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalTransitionKeys.kt index 11fb2332dc5f..78156dbc8964 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalTransitionKeys.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalTransitionKeys.kt @@ -30,6 +30,4 @@ object CommunalTransitionKeys { val ToEditMode = TransitionKey("ToEditMode") /** Transition to the glanceable hub after exiting edit mode */ val FromEditMode = TransitionKey("FromEditMode") - /** Immediately transitions without any delay */ - val Immediately = TransitionKey("Immediately") } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java index 8f913ff01337..78a8a42e2743 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java @@ -113,6 +113,7 @@ import android.view.accessibility.CaptioningManager; import android.view.inputmethod.InputMethodManager; import android.view.textclassifier.TextClassificationManager; +import androidx.annotation.NonNull; import androidx.asynclayoutinflater.view.AsyncLayoutInflater; import androidx.core.app.NotificationManagerCompat; @@ -718,6 +719,19 @@ public class FrameworkServicesModule { @Provides @Singleton + static ViewCaptureAwareWindowManager.Factory viewCaptureAwareWindowManagerFactory( + Lazy<ViewCapture> daggerLazyViewCapture) { + return new ViewCaptureAwareWindowManager.Factory() { + @NonNull + @Override + public ViewCaptureAwareWindowManager create(@NonNull WindowManager windowManager) { + return provideViewCaptureAwareWindowManager(windowManager, daggerLazyViewCapture); + } + }; + } + + @Provides + @Singleton static PermissionManager providePermissionManager(Context context) { PermissionManager pm = context.getSystemService(PermissionManager.class); if (pm != null) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 1a0525d97d30..ba23eb341b89 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -346,6 +346,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, private boolean mShuttingDown; private boolean mDozing; private boolean mAnimatingScreenOff; + private boolean mIgnoreDismiss; private final Context mContext; private final FalsingCollector mFalsingCollector; @@ -627,18 +628,20 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, public void onUserSwitching(int userId) { Log.d(TAG, String.format("onUserSwitching %d", userId)); synchronized (KeyguardViewMediator.this) { + mIgnoreDismiss = true; notifyTrustedChangedLocked(mUpdateMonitor.getUserHasTrust(userId)); resetKeyguardDonePendingLocked(); - dismiss(null /* callback */, null /* message */); + resetStateLocked(); adjustStatusBarLocked(); } } @Override public void onUserSwitchComplete(int userId) { + mIgnoreDismiss = false; Log.d(TAG, String.format("onUserSwitchComplete %d", userId)); - // We are calling dismiss again and with a delay as there are race conditions - // in some scenarios caused by async layout listeners + // We are calling dismiss with a delay as there are race conditions in some scenarios + // caused by async layout listeners mHandler.postDelayed(() -> dismiss(null /* callback */, null /* message */), 500); } @@ -2442,6 +2445,10 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } public void dismiss(IKeyguardDismissCallback callback, CharSequence message) { + if (mIgnoreDismiss) { + android.util.Log.i(TAG, "Ignoring request to dismiss (user switch in progress?)"); + return; + } mHandler.obtainMessage(DISMISS, new DismissMessage(callback, message)).sendToTarget(); } 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 80a0cee4f319..b0820a747e17 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 @@ -24,7 +24,6 @@ import com.android.systemui.Flags.communalSceneKtfRefactor import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor import com.android.systemui.communal.shared.model.CommunalScenes -import com.android.systemui.communal.shared.model.CommunalTransitionKeys import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main @@ -95,10 +94,7 @@ constructor( scope.launch { powerInteractor.isAwake .filterRelevantKeyguardStateAnd { isAwake -> isAwake } - .sample( - keyguardInteractor.biometricUnlockState, - ::Pair, - ) + .sample(keyguardInteractor.biometricUnlockState, ::Pair) .collect { ( _, @@ -203,21 +199,21 @@ constructor( if (!SceneContainerFlag.isEnabled) { startTransitionTo( KeyguardState.GONE, - ownerReason = "waking from dozing" + ownerReason = "waking from dozing", ) } } else if (primaryBouncerShowing) { if (!SceneContainerFlag.isEnabled) { startTransitionTo( KeyguardState.PRIMARY_BOUNCER, - ownerReason = "waking from dozing" + ownerReason = "waking from dozing", ) } } else if (isIdleOnCommunal && !communalSceneKtfRefactor()) { if (!SceneContainerFlag.isEnabled) { startTransitionTo( KeyguardState.GLANCEABLE_HUB, - ownerReason = "waking from dozing" + ownerReason = "waking from dozing", ) } } else if (isCommunalAvailable && dreamManager.canStartDreaming(true)) { @@ -227,7 +223,7 @@ constructor( } else { startTransitionTo( KeyguardState.LOCKSCREEN, - ownerReason = "waking from dozing" + ownerReason = "waking from dozing", ) } } @@ -237,11 +233,9 @@ constructor( private suspend fun transitionToGlanceableHub() { if (communalSceneKtfRefactor()) { - communalSceneInteractor.changeScene( + communalSceneInteractor.snapToScene( newScene = CommunalScenes.Communal, loggingReason = "from dozing to hub", - // Immediately show the hub when transitioning from dozing to hub. - transitionKey = CommunalTransitionKeys.Immediately, ) } else { startTransitionTo(KeyguardState.GLANCEABLE_HUB) 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 199caa168e31..7759298cb32a 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 @@ -162,10 +162,9 @@ constructor( .filterRelevantKeyguardStateAnd { isAsleep -> isAsleep } .collect { if (communalSceneKtfRefactor()) { - communalSceneInteractor.changeScene( + communalSceneInteractor.snapToScene( newScene = CommunalScenes.Blank, loggingReason = "hub to dozing", - transitionKey = CommunalTransitionKeys.Immediately, keyguardState = KeyguardState.DOZING, ) } else { @@ -210,12 +209,12 @@ constructor( // ends, to avoid transitioning to OCCLUDED erroneously when exiting // the dream. .debounce(100.milliseconds), - ::Pair + ::Pair, ) .sampleFilter( // When launching activities from widgets on the hub, we have a // custom occlusion animation. - communalSceneInteractor.isLaunchingWidget, + communalSceneInteractor.isLaunchingWidget ) { launchingWidget -> !launchingWidget } @@ -253,7 +252,7 @@ constructor( noneOf( // When launching activities from widgets on the hub, we wait to change // scenes until the activity launch is complete. - communalSceneInteractor.isLaunchingWidget, + communalSceneInteractor.isLaunchingWidget ), ) .filterRelevantKeyguardStateAnd { isKeyguardGoingAway -> isKeyguardGoingAway } @@ -270,7 +269,7 @@ constructor( newScene = CommunalScenes.Blank, loggingReason = "hub to gone", transitionKey = CommunalTransitionKeys.SimpleFade, - keyguardState = KeyguardState.GONE + keyguardState = KeyguardState.GONE, ) } } 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 deb0b2d8f848..b5f6b418e322 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 @@ -261,7 +261,10 @@ object KeyguardRootViewBinder { -> if (biometricMessage?.message != null) { chipbarCoordinator!!.displayView( - createChipbarInfo(biometricMessage.message, R.drawable.ic_lock) + createChipbarInfo( + biometricMessage.message, + R.drawable.ic_lock, + ) ) } else { chipbarCoordinator!!.removeView(ID, "occludingAppMsgNull") @@ -324,16 +327,12 @@ object KeyguardRootViewBinder { .getDimensionPixelSize(R.dimen.shelf_appear_translation) .stateIn(this) viewModel.isNotifIconContainerVisible.collect { isVisible -> - if (isVisible.value) { - blueprintViewModel.refreshBlueprint() - } else { - childViews[aodNotificationIconContainerId] - ?.setAodNotifIconContainerIsVisible( - isVisible, - iconsAppearTranslationPx.value, - screenOffAnimationController, - ) - } + childViews[aodNotificationIconContainerId] + ?.setAodNotifIconContainerIsVisible( + isVisible, + iconsAppearTranslationPx.value, + screenOffAnimationController, + ) } } @@ -383,7 +382,7 @@ object KeyguardRootViewBinder { if (msdlFeedback()) { msdlPlayer?.playToken( MSDLToken.UNLOCK, - authInteractionProperties, + authInteractionProperties ) } else { vibratorHelper.performHapticFeedback( @@ -399,7 +398,7 @@ object KeyguardRootViewBinder { if (msdlFeedback()) { msdlPlayer?.playToken( MSDLToken.FAILURE, - authInteractionProperties, + authInteractionProperties ) } else { vibratorHelper.performHapticFeedback( @@ -426,7 +425,7 @@ object KeyguardRootViewBinder { blueprintViewModel, clockViewModel, childViews, - burnInParams, + burnInParams ) ) @@ -465,7 +464,11 @@ object KeyguardRootViewBinder { */ private fun createChipbarInfo(message: String, @DrawableRes icon: Int): ChipbarInfo { return ChipbarInfo( - startIcon = TintedIcon(Icon.Resource(icon, null), ChipbarInfo.DEFAULT_ICON_TINT), + startIcon = + TintedIcon( + Icon.Resource(icon, null), + ChipbarInfo.DEFAULT_ICON_TINT, + ), text = Text.Loaded(message), endItem = null, vibrationEffect = null, @@ -496,7 +499,7 @@ object KeyguardRootViewBinder { oldLeft: Int, oldTop: Int, oldRight: Int, - oldBottom: Int, + oldBottom: Int ) { // After layout, ensure the notifications are positioned correctly childViews[nsslPlaceholderId]?.let { notificationListPlaceholder -> @@ -512,7 +515,7 @@ object KeyguardRootViewBinder { viewModel.onNotificationContainerBoundsChanged( notificationListPlaceholder.top.toFloat(), notificationListPlaceholder.bottom.toFloat(), - animate = shouldAnimate, + animate = shouldAnimate ) } @@ -528,7 +531,7 @@ object KeyguardRootViewBinder { Int.MAX_VALUE } else { view.getTop() - }, + } ) } } else { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt index 4cf3c4e7f6d0..c6efcfad8da7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt @@ -25,18 +25,20 @@ import androidx.constraintlayout.widget.ConstraintLayout import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Config -import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Type import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow -data class TransitionData(val config: Config, val start: Long = System.currentTimeMillis()) +data class TransitionData( + val config: Config, + val start: Long = System.currentTimeMillis(), +) class KeyguardBlueprintViewModel @Inject constructor( @Main private val handler: Handler, - private val keyguardBlueprintInteractor: KeyguardBlueprintInteractor, + keyguardBlueprintInteractor: KeyguardBlueprintInteractor, ) { val blueprint = keyguardBlueprintInteractor.blueprint val blueprintId = keyguardBlueprintInteractor.blueprintId @@ -74,9 +76,6 @@ constructor( } } - fun refreshBlueprint(type: Type = Type.NoTransition) = - keyguardBlueprintInteractor.refreshBlueprint(type) - fun updateTransitions(data: TransitionData?, mutate: MutableSet<Transition>.() -> Unit) { runningTransitions.mutate() @@ -96,7 +95,7 @@ constructor( Log.w( TAG, "runTransition: skipping ${transition::class.simpleName}: " + - "currentPriority=$currentPriority; config=$config", + "currentPriority=$currentPriority; config=$config" ) } apply() @@ -107,7 +106,7 @@ constructor( Log.i( TAG, "runTransition: running ${transition::class.simpleName}: " + - "currentPriority=$currentPriority; config=$config", + "currentPriority=$currentPriority; config=$config" ) } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt index 130868dc3c1c..1f339dddd4d1 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt @@ -33,6 +33,7 @@ import com.android.systemui.bluetooth.BroadcastDialogController import com.android.systemui.media.controls.data.repository.MediaFilterRepository import com.android.systemui.media.controls.domain.pipeline.MediaDataProcessor import com.android.systemui.media.controls.domain.pipeline.getNotificationActions +import com.android.systemui.media.controls.shared.MediaLogger import com.android.systemui.media.controls.shared.model.MediaControlModel import com.android.systemui.media.controls.shared.model.MediaData import com.android.systemui.media.controls.util.MediaSmartspaceLogger @@ -59,6 +60,7 @@ constructor( private val lockscreenUserManager: NotificationLockscreenUserManager, private val mediaOutputDialogManager: MediaOutputDialogManager, private val broadcastDialogController: BroadcastDialogController, + private val mediaLogger: MediaLogger, ) { val mediaControl: Flow<MediaControlModel?> = @@ -73,7 +75,7 @@ constructor( instanceId: InstanceId, delayMs: Long, eventId: Int, - location: Int + location: Int, ): Boolean { logSmartspaceUserEvent(eventId, location) val dismissed = @@ -81,7 +83,7 @@ constructor( if (!dismissed) { Log.w( TAG, - "Manager failed to dismiss media of instanceId=$instanceId, Token uid=${token?.uid}" + "Manager failed to dismiss media of instanceId=$instanceId, Token uid=${token?.uid}", ) } return dismissed @@ -120,20 +122,20 @@ constructor( expandable: Expandable, clickIntent: PendingIntent, eventId: Int, - location: Int + location: Int, ) { logSmartspaceUserEvent(eventId, location) - if (!launchOverLockscreen(clickIntent)) { + if (!launchOverLockscreen(expandable, clickIntent)) { activityStarter.postStartActivityDismissingKeyguard( clickIntent, - expandable.activityTransitionController(Cuj.CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER) + expandable.activityTransitionController(Cuj.CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER), ) } } fun startDeviceIntent(deviceIntent: PendingIntent) { if (deviceIntent.isActivity) { - if (!launchOverLockscreen(deviceIntent)) { + if (!launchOverLockscreen(expandable = null, deviceIntent)) { activityStarter.postStartActivityDismissingKeyguard(deviceIntent) } } else { @@ -141,20 +143,33 @@ constructor( } } - private fun launchOverLockscreen(pendingIntent: PendingIntent): Boolean { + private fun launchOverLockscreen( + expandable: Expandable?, + pendingIntent: PendingIntent, + ): Boolean { val showOverLockscreen = keyguardStateController.isShowing && activityIntentHelper.wouldPendingShowOverLockscreen( pendingIntent, - lockscreenUserManager.currentUserId + lockscreenUserManager.currentUserId, ) if (showOverLockscreen) { try { - val options = BroadcastOptions.makeBasic() - options.isInteractive = true - options.pendingIntentBackgroundActivityStartMode = - ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED - pendingIntent.send(options.toBundle()) + if (expandable != null) { + activityStarter.startPendingIntentMaybeDismissingKeyguard( + pendingIntent, + /* intentSentUiThreadCallback = */ null, + expandable.activityTransitionController( + Cuj.CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER + ), + ) + } else { + val options = BroadcastOptions.makeBasic() + options.isInteractive = true + options.pendingIntentBackgroundActivityStartMode = + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED + pendingIntent.send(options.toBundle()) + } } catch (e: PendingIntent.CanceledException) { Log.e(TAG, "pending intent of $instanceId was canceled") } @@ -166,7 +181,7 @@ constructor( fun startMediaOutputDialog( expandable: Expandable, packageName: String, - token: MediaSession.Token? = null + token: MediaSession.Token? = null, ) { mediaOutputDialogManager.createAndShowWithController( packageName, @@ -180,7 +195,7 @@ constructor( broadcastDialogController.createBroadcastDialogWithController( broadcastApp, packageName, - expandable.dialogTransitionController() + expandable.dialogTransitionController(), ) } @@ -188,10 +203,14 @@ constructor( repository.logSmartspaceCardUserEvent( eventId, MediaSmartspaceLogger.getSurface(location), - instanceId = instanceId + instanceId = instanceId, ) } + fun logMediaControlIsBound(artistName: CharSequence, songName: CharSequence) { + mediaLogger.logMediaControlIsBound(instanceId, artistName, songName) + } + private fun Expandable.dialogController(): DialogTransitionAnimator.Controller? { return dialogTransitionController( cuj = diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaLogger.kt index 7d20e170d8bc..88c47ba4d243 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaLogger.kt @@ -36,7 +36,7 @@ class MediaLogger @Inject constructor(@MediaLog private val buffer: LogBuffer) { bool1 = active str2 = reason }, - { "add media $str1, active: $bool1, reason: $str2" } + { "add media $str1, active: $bool1, reason: $str2" }, ) } @@ -48,7 +48,7 @@ class MediaLogger @Inject constructor(@MediaLog private val buffer: LogBuffer) { str1 = instanceId.toString() str2 = reason }, - { "removing media $str1, reason: $str2" } + { "removing media $str1, reason: $str2" }, ) } @@ -61,7 +61,7 @@ class MediaLogger @Inject constructor(@MediaLog private val buffer: LogBuffer) { bool1 = isActive str2 = reason }, - { "add recommendation $str1, active $bool1, reason: $str2" } + { "add recommendation $str1, active $bool1, reason: $str2" }, ) } @@ -74,7 +74,7 @@ class MediaLogger @Inject constructor(@MediaLog private val buffer: LogBuffer) { bool1 = immediately str2 = reason }, - { "removing recommendation $str1, immediate=$bool1, reason: $str2" } + { "removing recommendation $str1, immediate=$bool1, reason: $str2" }, ) } @@ -83,7 +83,7 @@ class MediaLogger @Inject constructor(@MediaLog private val buffer: LogBuffer) { TAG, LogLevel.DEBUG, { str1 = instanceId.toString() }, - { "adding media card $str1 to carousel" } + { "adding media card $str1 to carousel" }, ) } @@ -92,7 +92,7 @@ class MediaLogger @Inject constructor(@MediaLog private val buffer: LogBuffer) { TAG, LogLevel.DEBUG, { str1 = instanceId.toString() }, - { "removing media card $str1 from carousel" } + { "removing media card $str1 from carousel" }, ) } @@ -101,7 +101,7 @@ class MediaLogger @Inject constructor(@MediaLog private val buffer: LogBuffer) { TAG, LogLevel.DEBUG, { str1 = key }, - { "adding recommendation card $str1 to carousel" } + { "adding recommendation card $str1 to carousel" }, ) } @@ -110,7 +110,7 @@ class MediaLogger @Inject constructor(@MediaLog private val buffer: LogBuffer) { TAG, LogLevel.DEBUG, { str1 = key }, - { "removing recommendation card $str1 from carousel" } + { "removing recommendation card $str1 from carousel" }, ) } @@ -119,7 +119,24 @@ class MediaLogger @Inject constructor(@MediaLog private val buffer: LogBuffer) { TAG, LogLevel.DEBUG, { str1 = key }, - { "duplicate media notification $str1 posted" } + { "duplicate media notification $str1 posted" }, + ) + } + + fun logMediaControlIsBound( + instanceId: InstanceId, + artistName: CharSequence, + title: CharSequence, + ) { + buffer.log( + TAG, + LogLevel.DEBUG, + { + str1 = instanceId.toString() + str2 = artistName.toString() + str3 = title.toString() + }, + { "binding media control, instance id= $str1, artist= $str2, title= $str3" }, ) } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt index 6373feda9c9b..4055818dcb0b 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt @@ -80,16 +80,19 @@ object MediaControlViewBinder { mediaCard.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.STARTED) { launch { - viewModel.player.collectLatest { playerViewModel -> - playerViewModel?.let { - bindMediaCard( - viewHolder, - viewController, - it, - falsingManager, - backgroundDispatcher, - mainDispatcher, - ) + viewModel.player.collectLatest { player -> + player?.let { + if (viewModel.isNewPlayer(it)) { + bindMediaCard( + viewHolder, + viewController, + it, + falsingManager, + backgroundDispatcher, + mainDispatcher, + ) + viewModel.onMediaControlIsBound(it.artistName, it.titleName) + } } } } @@ -143,7 +146,7 @@ object MediaControlViewBinder { viewHolder, viewModel.outputSwitcher, viewController, - falsingManager + falsingManager, ) bindGutsViewModel(viewHolder, viewModel, viewController, falsingManager) bindActionButtons(viewHolder, viewModel, viewController, falsingManager) @@ -157,7 +160,7 @@ object MediaControlViewBinder { viewController, backgroundDispatcher, mainDispatcher, - isSongUpdated + isSongUpdated, ) if (viewModel.playTurbulenceNoise) { @@ -259,12 +262,12 @@ object MediaControlViewBinder { if (buttonView.id == R.id.actionPrev) { viewController.setUpPrevButtonInfo( buttonModel.isEnabled, - buttonModel.notVisibleValue + buttonModel.notVisibleValue, ) } else if (buttonView.id == R.id.actionNext) { viewController.setUpNextButtonInfo( buttonModel.isEnabled, - buttonModel.notVisibleValue + buttonModel.notVisibleValue, ) } val animHandler = (buttonView.tag ?: AnimationBindHandler()) as AnimationBindHandler @@ -295,7 +298,7 @@ object MediaControlViewBinder { viewController.collapsedLayout, visible, buttonModel.notVisibleValue, - buttonModel.showInCollapsed + buttonModel.showInCollapsed, ) } } @@ -350,7 +353,7 @@ object MediaControlViewBinder { createTouchRippleAnimation( button, viewController.colorSchemeTransition, - multiRippleView + multiRippleView, ) ) @@ -382,12 +385,12 @@ object MediaControlViewBinder { setVisibleAndAlpha( expandedSet, R.id.media_explicit_indicator, - viewModel.isExplicitVisible + viewModel.isExplicitVisible, ) setVisibleAndAlpha( collapsedSet, R.id.media_explicit_indicator, - viewModel.isExplicitVisible + viewModel.isExplicitVisible, ) // refreshState is required here to resize the text views (and prevent ellipsis) @@ -398,7 +401,7 @@ object MediaControlViewBinder { // something is incorrectly bound, but needs to be run if other elements were // updated while the enter animation was running viewController.refreshState() - } + }, ) } @@ -427,7 +430,7 @@ object MediaControlViewBinder { viewModel.backgroundCover!!, viewModel.colorScheme, width, - height + height, ) } else { ColorDrawable(Color.TRANSPARENT) @@ -493,7 +496,7 @@ object MediaControlViewBinder { transitionDrawable: TransitionDrawable, layer: Int, targetWidth: Int, - targetHeight: Int + targetHeight: Int, ) { val drawable = transitionDrawable.getDrawable(layer) ?: return val width = drawable.intrinsicWidth @@ -509,7 +512,7 @@ object MediaControlViewBinder { artworkIcon: android.graphics.drawable.Icon, mutableColorScheme: ColorScheme, width: Int, - height: Int + height: Int, ): LayerDrawable { val albumArt = MediaArtworkHelper.getScaledBackground(context, artworkIcon, width, height) return MediaArtworkHelper.setUpGradientColorOnDrawable( @@ -517,7 +520,7 @@ object MediaControlViewBinder { context.getDrawable(R.drawable.qs_media_scrim)?.mutate() as GradientDrawable, mutableColorScheme, MEDIA_PLAYER_SCRIM_START_ALPHA, - MEDIA_PLAYER_SCRIM_END_ALPHA + MEDIA_PLAYER_SCRIM_END_ALPHA, ) } @@ -544,7 +547,7 @@ object MediaControlViewBinder { private fun createTouchRippleAnimation( button: ImageButton, colorSchemeTransition: ColorSchemeTransition, - multiRippleView: MultiRippleView + multiRippleView: MultiRippleView, ): RippleAnimation { val maxSize = (multiRippleView.width * 2).toFloat() return RippleAnimation( @@ -562,7 +565,7 @@ object MediaControlViewBinder { baseRingFadeParams = null, sparkleRingFadeParams = null, centerFillFadeParams = null, - shouldDistort = false + shouldDistort = false, ) ) } @@ -596,7 +599,7 @@ object MediaControlViewBinder { set: ConstraintSet, resId: Int, visible: Boolean, - notVisibleValue: Int + notVisibleValue: Int, ) { set.setVisibility(resId, if (visible) ConstraintSet.VISIBLE else notVisibleValue) set.setAlpha(resId, if (visible) 1.0f else 0.0f) @@ -618,7 +621,7 @@ object MediaControlViewBinder { collapsedSet: ConstraintSet, visible: Boolean, notVisibleValue: Int, - showInCollapsed: Boolean + showInCollapsed: Boolean, ) { if (notVisibleValue == ConstraintSet.INVISIBLE) { // Since time views should appear instead of buttons. diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaActionViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaActionViewModel.kt index 82099e61009f..3f22d549698c 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaActionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaActionViewModel.kt @@ -32,4 +32,16 @@ data class MediaActionViewModel( val buttonId: Int? = null, val isEnabled: Boolean, val onClicked: (Int) -> Unit, -) +) { + fun contentEquals(other: MediaActionViewModel?): Boolean { + return other?.let { + contentDescription == other.contentDescription && + isVisibleWhenScrubbing == other.isVisibleWhenScrubbing && + notVisibleValue == other.notVisibleValue && + showInCollapsed == other.showInCollapsed && + rebindId == other.rebindId && + buttonId == other.buttonId && + isEnabled == other.isEnabled + } ?: false + } +} diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt index 64820e0d0ced..104d155ab74e 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt @@ -69,18 +69,30 @@ class MediaControlViewModel( mediaControl?.let { toViewModel(it) } } } - .distinctUntilChanged() + .distinctUntilChanged { old, new -> + (new == null && old == null) || new?.contentEquals(old) ?: false + } .flowOn(backgroundDispatcher) private var isPlaying = false private var isAnyButtonClicked = false private var location = -1 + private var playerViewModel: MediaPlayerViewModel? = null + + fun isNewPlayer(viewModel: MediaPlayerViewModel): Boolean { + val contentEquals = playerViewModel?.contentEquals(viewModel) ?: false + return (!contentEquals).also { playerViewModel = viewModel } + } + + fun onMediaControlIsBound(artistName: CharSequence, titleName: CharSequence) { + interactor.logMediaControlIsBound(artistName, titleName) + } private fun onDismissMediaData( token: Token?, uid: Int, packageName: String, - instanceId: InstanceId + instanceId: InstanceId, ) { logger.logLongPressDismiss(uid, packageName, instanceId) interactor.removeMediaControl( @@ -88,7 +100,7 @@ class MediaControlViewModel( instanceId, MEDIA_PLAYER_ANIMATION_DELAY, SMARTSPACE_CARD_DISMISS_EVENT, - location + location, ) } @@ -99,7 +111,7 @@ class MediaControlViewModel( applicationContext, backgroundDispatcher, model.artwork, - TAG + TAG, ) val scheme = wallpaperColors?.let { ColorScheme(it, true, Style.CONTENT) } @@ -107,7 +119,7 @@ class MediaControlViewModel( applicationContext, model.packageName, TAG, - Style.CONTENT + Style.CONTENT, ) ?: return null @@ -131,7 +143,7 @@ class MediaControlViewModel( R.string.controls_media_playing_item_description, model.songName, model.artistName, - model.appName + model.appName, ) } }, @@ -157,7 +169,7 @@ class MediaControlViewModel( expandable, clickIntent, SMARTSPACE_CARD_CLICK_EVENT, - location + location, ) } }, @@ -177,7 +189,7 @@ class MediaControlViewModel( } } }, - onLocationChanged = { location = it } + onLocationChanged = { location = it }, ) } @@ -191,7 +203,7 @@ class MediaControlViewModel( device?.name?.let { TextUtils.equals( it, - applicationContext.getString(R.string.broadcasting_description_is_broadcasting) + applicationContext.getString(R.string.broadcasting_description_is_broadcasting), ) } ?: false val useDisabledAlpha = @@ -236,19 +248,19 @@ class MediaControlViewModel( logger.logOpenBroadcastDialog( model.uid, model.packageName, - model.instanceId + model.instanceId, ) interactor.startBroadcastDialog( expandable, device?.name.toString(), - model.packageName + model.packageName, ) } else { logger.logOpenOutputSwitcher(model.uid, model.packageName, model.instanceId) interactor.startMediaOutputDialog( expandable, model.packageName, - model.token + model.token, ) } } else { @@ -257,10 +269,10 @@ class MediaControlViewModel( ?: interactor.startMediaOutputDialog( expandable, model.packageName, - model.token + model.token, ) } - } + }, ) } @@ -270,7 +282,7 @@ class MediaControlViewModel( if (model.isDismissible) { applicationContext.getString( R.string.controls_media_close_session, - model.appName + model.appName, ) } else { applicationContext.getString(R.string.controls_media_active_session) @@ -304,7 +316,7 @@ class MediaControlViewModel( model, mediaButton.getActionById(buttonId), buttonId, - isScrubbingTimeEnabled + isScrubbingTimeEnabled, ) } } @@ -319,7 +331,7 @@ class MediaControlViewModel( model: MediaControlModel, mediaAction: MediaAction?, buttonId: Int, - canShowScrubbingTimeViews: Boolean + canShowScrubbingTimeViews: Boolean, ): MediaActionViewModel { val showInCollapsed = SEMANTIC_ACTIONS_COMPACT.contains(buttonId) val hideWhenScrubbing = SEMANTIC_ACTIONS_HIDE_WHEN_SCRUBBING.contains(buttonId) @@ -353,7 +365,7 @@ class MediaControlViewModel( private fun toNotifActionViewModel( model: MediaControlModel, mediaAction: MediaAction, - index: Int + index: Int, ): MediaActionViewModel { return MediaActionViewModel( icon = mediaAction.icon, @@ -375,7 +387,7 @@ class MediaControlViewModel( uid: Int, packageName: String, instanceId: InstanceId, - action: Runnable + action: Runnable, ) { logger.logTapAction(id, uid, packageName, instanceId) interactor.logSmartspaceUserEvent(SMARTSPACE_CARD_CLICK_EVENT, location) @@ -424,7 +436,7 @@ class MediaControlViewModel( R.id.actionPrev, R.id.actionNext, R.id.action0, - R.id.action1 + R.id.action1, ) const val TURBULENCE_NOISE_PLAY_MS_DURATION = 7500L diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaOutputSwitcherViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaOutputSwitcherViewModel.kt index 9df9bccdf522..2a47a5af790a 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaOutputSwitcherViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaOutputSwitcherViewModel.kt @@ -29,4 +29,15 @@ data class MediaOutputSwitcherViewModel( val alpha: Float, val isVisible: Boolean, val onClicked: (Expandable) -> Unit, -) +) { + fun contentEquals(other: MediaOutputSwitcherViewModel?): Boolean { + return (other?.let { + isTapEnabled == other.isTapEnabled && + deviceString == other.deviceString && + isCurrentBroadcastApp == other.isCurrentBroadcastApp && + isIntentValid == other.isIntentValid && + alpha == other.alpha && + isVisible == other.isVisible + } ?: false) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaPlayerViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaPlayerViewModel.kt index 96e7fc79c8eb..f4b0d6e2c990 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaPlayerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaPlayerViewModel.kt @@ -43,4 +43,30 @@ data class MediaPlayerViewModel( val onSeek: () -> Unit, val onBindSeekbar: (SeekBarViewModel) -> Unit, val onLocationChanged: (Int) -> Unit, -) +) { + fun contentEquals(other: MediaPlayerViewModel?): Boolean { + return other?.let { + other.backgroundCover == backgroundCover && + appIcon == other.appIcon && + useGrayColorFilter == other.useGrayColorFilter && + artistName == other.artistName && + titleName == other.titleName && + isExplicitVisible == other.isExplicitVisible && + shouldAddGradient == other.shouldAddGradient && + canShowTime == other.canShowTime && + playTurbulenceNoise == other.playTurbulenceNoise && + useSemanticActions == other.useSemanticActions && + areActionsEqual(other.actionButtons) && + outputSwitcher.contentEquals(other.outputSwitcher) + } ?: false + } + + private fun areActionsEqual(other: List<MediaActionViewModel>): Boolean { + actionButtons.forEachIndexed { index, mediaActionViewModel -> + if (!mediaActionViewModel.contentEquals(other[index])) { + return false + } + } + return true + } +} diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java index 4251b81226b3..8351597f35de 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java @@ -34,6 +34,7 @@ import android.annotation.RequiresPermission; import android.app.Activity; import android.app.ActivityOptions.LaunchCookie; import android.app.AlertDialog; +import android.app.KeyguardManager; import android.app.StatusBarManager; import android.app.compat.CompatChanges; import android.content.Context; @@ -83,6 +84,7 @@ public class MediaProjectionPermissionActivity extends Activity { private final StatusBarManager mStatusBarManager; private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger; private final ScreenCaptureDisabledDialogDelegate mScreenCaptureDisabledDialogDelegate; + private final KeyguardManager mKeyguardManager; private String mPackageName; private int mUid; @@ -101,11 +103,13 @@ public class MediaProjectionPermissionActivity extends Activity { FeatureFlags featureFlags, Lazy<ScreenCaptureDevicePolicyResolver> screenCaptureDevicePolicyResolver, StatusBarManager statusBarManager, + KeyguardManager keyguardManager, MediaProjectionMetricsLogger mediaProjectionMetricsLogger, ScreenCaptureDisabledDialogDelegate screenCaptureDisabledDialogDelegate) { mFeatureFlags = featureFlags; mScreenCaptureDevicePolicyResolver = screenCaptureDevicePolicyResolver; mStatusBarManager = statusBarManager; + mKeyguardManager = keyguardManager; mMediaProjectionMetricsLogger = mediaProjectionMetricsLogger; mScreenCaptureDisabledDialogDelegate = screenCaptureDisabledDialogDelegate; } @@ -208,7 +212,14 @@ public class MediaProjectionPermissionActivity extends Activity { } setUpDialog(mDialog); - mDialog.show(); + + boolean shouldDismissKeyguard = + com.android.systemui.Flags.mediaProjectionDialogBehindLockscreen(); + if (shouldDismissKeyguard && mKeyguardManager.isDeviceLocked()) { + requestDeviceUnlock(); + } else { + mDialog.show(); + } if (savedInstanceState == null) { mMediaProjectionMetricsLogger.notifyPermissionRequestDisplayed(mUid); @@ -332,6 +343,16 @@ public class MediaProjectionPermissionActivity extends Activity { return false; } + private void requestDeviceUnlock() { + mKeyguardManager.requestDismissKeyguard(this, + new KeyguardManager.KeyguardDismissCallback() { + @Override + public void onDismissSucceeded() { + mDialog.show(); + } + }); + } + private void grantMediaProjectionPermission( int screenShareMode, boolean hasCastingCapabilities) { try { diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt index aa6c08eecd76..45aad825a70f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt @@ -140,6 +140,7 @@ fun Tile(tile: TileViewModel, iconOnly: Boolean, modifier: Modifier) { } }, onLongClick = { tile.onLongClick(expandable) }, + accessibilityUiState = uiState.accessibilityUiState, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java index 3f3ad13f9b12..4f47536f6b32 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java @@ -28,6 +28,7 @@ import android.content.Context; import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.graphics.PixelFormat; +import android.graphics.Rect; import android.graphics.Region; import android.os.Binder; import android.os.Build; @@ -987,6 +988,19 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW @Override public void onConfigChanged(Configuration newConfig) { + // If the shade window is not visible, the bounds will not update until it becomes visible. + // Touches that should invoke shade expansion but are not within those incorrect bounds + // (because the shape of the shade window remains portrait after flipping to landscape) will + // be dropped, causing the shade expansion to fail silently. Since the shade doesn't open, + // it doesn't become visible, and the bounds will never update. Therefore, we must detect + // the incorrect bounds here and force the update so that touches are routed correctly. + if (SceneContainerFlag.isEnabled() && mWindowRootView.getVisibility() == View.INVISIBLE) { + Rect bounds = newConfig.windowConfiguration.getBounds(); + if (mWindowRootView.getWidth() != bounds.width()) { + mLogger.logConfigChangeWidthAdjust(mWindowRootView.getWidth(), bounds.width()); + updateRootViewBounds(bounds); + } + } final boolean newScreenRotationAllowed = mKeyguardStateController .isKeyguardScreenRotationAllowed(); @@ -996,6 +1010,16 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW } } + private void updateRootViewBounds(Rect bounds) { + int originalMlpWidth = mLp.width; + int originalMlpHeight = mLp.height; + mLp.width = bounds.width(); + mLp.height = bounds.height(); + mWindowManager.updateViewLayout(mWindowRootView, mLp); + mLp.width = originalMlpWidth; + mLp.height = originalMlpHeight; + } + /** * When keyguard will be dismissed but didn't start animation yet. */ diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt index e7a397b0fa09..1693e62c89fb 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt @@ -31,18 +31,13 @@ class ShadeWindowLogger @Inject constructor(@ShadeWindowLog private val buffer: ConstantStringsLogger by ConstantStringsLoggerImpl(buffer, TAG) { fun logNewState(state: Any) { - buffer.log( - TAG, - DEBUG, - { str1 = state.toString() }, - { "Applying new state: $str1" } - ) + buffer.log(TAG, DEBUG, { str1 = state.toString() }, { "Applying new state: $str1" }) } private inline fun log( logLevel: LogLevel, initializer: LogMessage.() -> Unit, - noinline printer: LogMessage.() -> String + noinline printer: LogMessage.() -> String, ) { buffer.log(TAG, logLevel, initializer, printer) } @@ -52,7 +47,8 @@ class ShadeWindowLogger @Inject constructor(@ShadeWindowLog private val buffer: TAG, DEBUG, { bool1 = visible }, - { "Updating visibility, should be visible : $bool1" }) + { "Updating visibility, should be visible : $bool1" }, + ) } fun logIsExpanded( @@ -65,7 +61,7 @@ class ShadeWindowLogger @Inject constructor(@ShadeWindowLog private val buffer: headsUpNotificationShowing: Boolean, scrimsVisibilityNotTransparent: Boolean, backgroundBlurRadius: Boolean, - launchingActivityFromNotification: Boolean + launchingActivityFromNotification: Boolean, ) { buffer.log( TAG, @@ -82,11 +78,13 @@ class ShadeWindowLogger @Inject constructor(@ShadeWindowLog private val buffer: long2 = if (backgroundBlurRadius) 1 else 0 double1 = if (launchingActivityFromNotification) 1.0 else 0.0 }, - { "Setting isExpanded to $str1: forceWindowCollapsed $bool1, " + + { + "Setting isExpanded to $str1: forceWindowCollapsed $bool1, " + "isKeyguardShowingAndNotOccluded $bool2, panelVisible $bool3, " + "keyguardFadingAway $bool4, bouncerShowing $int1," + "headsUpNotificationShowing $int2, scrimsVisibilityNotTransparent $long1," + - "backgroundBlurRadius $long2, launchingActivityFromNotification $double1"} + "backgroundBlurRadius $long2, launchingActivityFromNotification $double1" + }, ) } @@ -95,7 +93,7 @@ class ShadeWindowLogger @Inject constructor(@ShadeWindowLog private val buffer: TAG, DEBUG, { bool1 = visible }, - { "Updating shade, should be visible and focusable: $bool1" } + { "Updating shade, should be visible and focusable: $bool1" }, ) } @@ -104,7 +102,19 @@ class ShadeWindowLogger @Inject constructor(@ShadeWindowLog private val buffer: TAG, DEBUG, { bool1 = focusable }, - { "Updating shade, should be focusable : $bool1" } + { "Updating shade, should be focusable : $bool1" }, + ) + } + + fun logConfigChangeWidthAdjust(originalWidth: Int, newWidth: Int) { + buffer.log( + TAG, + DEBUG, + { + int1 = originalWidth + int2 = newWidth + }, + { "Config changed. SceneWindowRootView width updating from $int1 to $int2." }, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt index c6c303ebb0d4..975b92e632b8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt @@ -22,7 +22,6 @@ import com.android.systemui.res.R import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewInitializedListener import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewUpdatedListener import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions -import com.android.systemui.statusbar.phone.PhoneStatusBarView import com.android.systemui.statusbar.phone.PhoneStatusBarViewController import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent @@ -60,7 +59,6 @@ interface StatusBarInitializer { interface OnStatusBarViewUpdatedListener { fun onStatusBarViewUpdated( - statusBarView: PhoneStatusBarView, statusBarViewController: PhoneStatusBarViewController, statusBarTransitions: PhoneStatusBarTransitions, ) @@ -88,7 +86,6 @@ constructor( (fragment as CollapsedStatusBarFragment).statusBarFragmentComponent ?: throw IllegalStateException() statusBarViewUpdatedListener?.onStatusBarViewUpdated( - statusBarFragmentComponent.phoneStatusBarView, statusBarFragmentComponent.phoneStatusBarViewController, statusBarFragmentComponent.phoneStatusBarTransitions, ) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarSimpleFragment.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarSimpleFragment.kt new file mode 100644 index 000000000000..214151383dc6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarSimpleFragment.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.core + +import com.android.systemui.Flags +import com.android.systemui.flags.FlagToken +import com.android.systemui.flags.RefactorFlagUtils + +/** Helper for reading and using the status bar simple fragment flag state */ +object StatusBarSimpleFragment { + /** Aconfig flag for removing the fragment */ + const val FLAG_NAME = Flags.FLAG_STATUS_BAR_SIMPLE_FRAGMENT + + /** A token used for dependency declaration */ + val token: FlagToken + get() = FlagToken(FLAG_NAME, isEnabled) + + /** Is the refactor enabled */ + @JvmStatic + inline val isEnabled + get() = Flags.statusBarSimpleFragment() + + /** + * Called to ensure code is only run when the flag is enabled. This protects users from the + * unintended behaviors caused by accidentally running new logic, while also crashing on an eng + * build to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun isUnexpectedlyInLegacyMode() = + RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME) + + /** + * Called to ensure code is only run when the flag is disabled. This will throw an exception if + * the flag is not enabled to ensure that the refactor author catches issues in testing. + * Caution!! Using this check incorrectly will cause crashes in nextfood builds! + */ + @JvmStatic + inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME) + + /** + * Called to ensure code is only run when the flag is disabled. This will throw an exception if + * the flag is enabled to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt index 526c64c15696..55943a527a8c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.dagger +import android.content.Context +import com.android.app.viewcapture.ViewCaptureAwareWindowManager import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.LogBuffer @@ -63,12 +65,18 @@ abstract class StatusBarModule { @Binds abstract fun statusBarInitializer(impl: StatusBarInitializerImpl): StatusBarInitializer - @Binds - abstract fun statusBarWindowController( - impl: StatusBarWindowControllerImpl - ): StatusBarWindowController - companion object { + + @Provides + @SysUISingleton + fun statusBarWindowController( + context: Context?, + viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager?, + factory: StatusBarWindowControllerImpl.Factory, + ): StatusBarWindowController { + return factory.create(context, viewCaptureAwareWindowManager) + } + @Provides @SysUISingleton @OngoingCallLog 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 50e92498b114..59533b343a57 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -372,7 +372,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private final Point mCurrentDisplaySize = new Point(); - protected PhoneStatusBarView mStatusBarView; private PhoneStatusBarViewController mPhoneStatusBarViewController; private PhoneStatusBarTransitions mStatusBarTransitions; private final AuthRippleController mAuthRippleController; @@ -1191,8 +1190,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { // Set up CollapsedStatusBarFragment and PhoneStatusBarView mStatusBarInitializer.setStatusBarViewUpdatedListener( - (statusBarView, statusBarViewController, statusBarTransitions) -> { - mStatusBarView = statusBarView; + (statusBarViewController, statusBarTransitions) -> { mPhoneStatusBarViewController = statusBarViewController; mStatusBarTransitions = statusBarTransitions; getNotificationShadeWindowViewController() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java index c0e36b2ab42a..f026b99af49c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java @@ -27,7 +27,6 @@ import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions; import com.android.systemui.statusbar.phone.PhoneStatusBarView; import com.android.systemui.statusbar.phone.PhoneStatusBarViewController; import com.android.systemui.statusbar.phone.StatusBarLocation; -import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer; import com.android.systemui.statusbar.policy.Clock; import com.android.systemui.statusbar.window.StatusBarWindowController; @@ -114,14 +113,6 @@ public interface StatusBarFragmentModule { /** */ @Provides @StatusBarFragmentScope - static StatusBarUserSwitcherContainer provideStatusBarUserSwitcherContainer( - @RootView PhoneStatusBarView view) { - return view.findViewById(R.id.user_switcher_container); - } - - /** */ - @Provides - @StatusBarFragmentScope static PhoneStatusBarViewController providePhoneStatusBarViewController( PhoneStatusBarViewController.Factory phoneStatusBarViewControllerFactory, @RootView PhoneStatusBarView phoneStatusBarView) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java index 1a0327cdd809..1ee7cf3490f4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java @@ -28,7 +28,6 @@ import static com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE; import static com.android.systemui.util.leak.RotationUtils.ROTATION_UPSIDE_DOWN; import android.content.Context; -import android.content.res.Resources; import android.graphics.Insets; import android.graphics.PixelFormat; import android.graphics.Rect; @@ -52,23 +51,23 @@ import com.android.app.viewcapture.ViewCaptureAwareWindowManager; import com.android.internal.policy.SystemBarUtils; import com.android.systemui.animation.ActivityTransitionAnimator; import com.android.systemui.animation.DelegateTransitionAnimatorController; -import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.fragments.FragmentHostManager; import com.android.systemui.fragments.FragmentService; import com.android.systemui.res.R; import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider; +import com.android.systemui.statusbar.window.StatusBarWindowModule.InternalWindowViewInflater; import com.android.systemui.unfold.UnfoldTransitionProgressProvider; import com.android.systemui.unfold.util.JankMonitorTransitionProgressListener; -import java.util.Optional; +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; -import javax.inject.Inject; +import java.util.Optional; /** * Encapsulates all logic for the status bar window state management. */ -@SysUISingleton public class StatusBarWindowControllerImpl implements StatusBarWindowController { private static final String TAG = "StatusBarWindowController"; private static final boolean DEBUG = false; @@ -90,21 +89,20 @@ public class StatusBarWindowControllerImpl implements StatusBarWindowController private final WindowManager.LayoutParams mLpChanged; private final Binder mInsetsSourceOwner = new Binder(); - @Inject + @AssistedInject public StatusBarWindowControllerImpl( - Context context, - @StatusBarWindowModule.InternalWindowView StatusBarWindowView statusBarWindowView, - ViewCaptureAwareWindowManager viewCaptureAwareWindowManager, + @Assisted Context context, + @InternalWindowViewInflater StatusBarWindowViewInflater statusBarWindowViewInflater, + @Assisted ViewCaptureAwareWindowManager viewCaptureAwareWindowManager, IWindowManager iWindowManager, StatusBarContentInsetsProvider contentInsetsProvider, FragmentService fragmentService, - @Main Resources resources, Optional<UnfoldTransitionProgressProvider> unfoldTransitionProgressProvider) { mContext = context; mWindowManager = viewCaptureAwareWindowManager; mIWindowManager = iWindowManager; mContentInsetsProvider = contentInsetsProvider; - mStatusBarWindowView = statusBarWindowView; + mStatusBarWindowView = statusBarWindowViewInflater.inflate(context); mFragmentService = fragmentService; mLaunchAnimationContainer = mStatusBarWindowView.findViewById( R.id.status_bar_launch_animation_container); @@ -354,4 +352,13 @@ public class StatusBarWindowControllerImpl implements StatusBarWindowController mLpChanged.forciblyShownTypes &= ~WindowInsets.Type.statusBars(); } } + + @AssistedFactory + public interface Factory { + /** Creates a new instance. */ + StatusBarWindowControllerImpl create( + Context context, + ViewCaptureAwareWindowManager viewCaptureAwareWindowManager); + } + } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowModule.kt index 1c7debcdf39d..ebfbac7be916 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowModule.kt @@ -1,47 +1,36 @@ package com.android.systemui.statusbar.window -import android.view.LayoutInflater -import com.android.systemui.res.R import com.android.systemui.dagger.SysUISingleton +import dagger.Binds import dagger.Module -import dagger.Provides import javax.inject.Qualifier /** Module providing dependencies related to the status bar window. */ @Module abstract class StatusBarWindowModule { + /** - * Provides a [StatusBarWindowView]. + * Binds a [StatusBarWindowViewInflater]. * - * Only [StatusBarWindowController] should inject the view. + * Only [StatusBarWindowControllerImpl] should inject it. */ - @Module - companion object { - @JvmStatic - @Provides - @SysUISingleton - @InternalWindowView - fun providesStatusBarWindowView(layoutInflater: LayoutInflater): StatusBarWindowView { - return layoutInflater.inflate( - R.layout.super_status_bar, - /* root= */null - ) as StatusBarWindowView? - ?: throw IllegalStateException( - "R.layout.super_status_bar could not be properly inflated" - ) - } - } + @Binds + @SysUISingleton + @InternalWindowViewInflater + abstract fun providesStatusBarWindowViewInflater( + inflaterImpl: StatusBarWindowViewInflaterImpl + ): StatusBarWindowViewInflater /** - * We want [StatusBarWindowView] to be provided to [StatusBarWindowController]'s constructor via - * dagger so that we can provide a fake window view when testing the controller. However, we wan - * want *only* the controller to be able to inject the window view. + * We want [StatusBarWindowViewInflater] to be provided to [StatusBarWindowControllerImpl]'s + * constructor via dagger so that we can provide a fake window view when testing the controller. + * However, we wan want *only* the controller to be able to inject the window view. * - * This protected qualifier annotation achieves this. [StatusBarWindowView] can only be injected - * if it's annotated with [InternalWindowView], and only classes inside this [statusbar.window] - * package can access the annotation. + * This protected qualifier annotation achieves this. [StatusBarWindowViewInflater] can only be + * injected if it's annotated with [InternalWindowViewInflater], and only classes inside this + * [statusbar.window] package can access the annotation. */ @Retention(AnnotationRetention.BINARY) @Qualifier - protected annotation class InternalWindowView + protected annotation class InternalWindowViewInflater } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowViewInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowViewInflater.kt new file mode 100644 index 000000000000..f030a4ac0d0e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowViewInflater.kt @@ -0,0 +1,42 @@ +/* + * 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.window + +import android.content.Context +import android.view.LayoutInflater +import com.android.systemui.res.R +import javax.inject.Inject + +/** + * Inflates a [StatusBarWindowView]. Exists so that it can be injected into + * [StatusBarWindowControllerImpl] and be swapped for a fake implementation in tests. + */ +interface StatusBarWindowViewInflater { + fun inflate(context: Context): StatusBarWindowView +} + +class StatusBarWindowViewInflaterImpl @Inject constructor() : StatusBarWindowViewInflater { + + override fun inflate(context: Context): StatusBarWindowView { + val layoutInflater = LayoutInflater.from(context) + return layoutInflater.inflate(R.layout.super_status_bar, /* root= */ null) + as StatusBarWindowView? + ?: throw IllegalStateException( + "R.layout.super_status_bar could not be properly inflated" + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java index 103449b6b0f7..ee8ce17cecd4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java @@ -26,6 +26,7 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import android.app.UiModeManager; +import android.content.res.Configuration; import android.graphics.Rect; import android.graphics.drawable.GradientDrawable; import android.platform.test.annotations.EnableFlags; @@ -78,6 +79,12 @@ public class MenuViewTest extends SysuiTestCase { mNightMode = mUiModeManager.getNightMode(); mUiModeManager.setNightMode(MODE_NIGHT_YES); + // Programmatically update the resource's configuration to night mode to reduce flakiness + Configuration nightConfig = new Configuration(mContext.getResources().getConfiguration()); + nightConfig.uiMode = Configuration.UI_MODE_NIGHT_YES; + mContext.getResources().updateConfiguration(nightConfig, + mContext.getResources().getDisplayMetrics(), null); + mSpyContext = spy(mContext); doNothing().when(mSpyContext).startActivity(any()); @@ -101,6 +108,8 @@ public class MenuViewTest extends SysuiTestCase { @Test public void insetsOnDarkTheme_menuOnLeft_matchInsets() { + // In dark theme, the inset is not 0 to avoid weird spacing issue between the menu and + // the edge of the screen. mMenuView.onConfigurationChanged(/* newConfig= */ null); final InstantInsetLayerDrawable insetLayerDrawable = (InstantInsetLayerDrawable) mMenuView.getBackground(); diff --git a/packages/SystemUI/tests/utils/src/android/hardware/input/FakeInputManager.kt b/packages/SystemUI/tests/utils/src/android/hardware/input/FakeInputManager.kt index ee36cadd8480..de4bbecaaf0e 100644 --- a/packages/SystemUI/tests/utils/src/android/hardware/input/FakeInputManager.kt +++ b/packages/SystemUI/tests/utils/src/android/hardware/input/FakeInputManager.kt @@ -84,7 +84,7 @@ class FakeInputManager { if (devices.containsKey(deviceId)) { return } - addPhysicalKeyboard(deviceId, enabled) + addPhysicalKeyboard(deviceId, enabled = enabled) } fun registerInputDeviceListener(listener: InputDeviceListener) { @@ -92,9 +92,15 @@ class FakeInputManager { inputDeviceListener = listener } - fun addPhysicalKeyboard(id: Int, enabled: Boolean = true) { + fun addPhysicalKeyboard( + id: Int, + vendorId: Int = 0, + productId: Int = 0, + isFullKeyboard: Boolean = true, + enabled: Boolean = true + ) { check(id > 0) { "Physical keyboard ids have to be > 0" } - addKeyboard(id, enabled) + addKeyboard(id, vendorId, productId, isFullKeyboard, enabled) } fun removeKeysFromKeyboard(deviceId: Int, vararg keyCodes: Int) { @@ -102,20 +108,38 @@ class FakeInputManager { supportedKeyCodesByDeviceId[deviceId]!!.removeAll(keyCodes.asList()) } - private fun addKeyboard(id: Int, enabled: Boolean = true) { - devices[id] = + private fun addKeyboard( + id: Int, + vendorId: Int = 0, + productId: Int = 0, + isFullKeyboard: Boolean = true, + enabled: Boolean = true + ) { + val keyboardType = + if (isFullKeyboard) InputDevice.KEYBOARD_TYPE_ALPHABETIC + else InputDevice.KEYBOARD_TYPE_NON_ALPHABETIC + // VendorId and productId are set to 0 if not specified, which is the same as the default + // values used in InputDevice.Builder + val builder = InputDevice.Builder() .setId(id) - .setKeyboardType(InputDevice.KEYBOARD_TYPE_ALPHABETIC) + .setVendorId(vendorId) + .setProductId(productId) + .setKeyboardType(keyboardType) .setSources(InputDevice.SOURCE_KEYBOARD) .setEnabled(enabled) .setKeyCharacterMap(keyCharacterMap) - .build() + devices[id] = builder.build() + inputDeviceListener?.onInputDeviceAdded(id) supportedKeyCodesByDeviceId[id] = allKeyCodes.toMutableSet() } - fun addDevice(id: Int, sources: Int) { - devices[id] = InputDevice.Builder().setId(id).setSources(sources).build() + fun addDevice(id: Int, sources: Int, isNotFound: Boolean = false) { + // there's not way of differentiate device connection vs registry in current implementation. + // If the device isNotFound, it means that we connect an unregistered device. + if (!isNotFound) { + devices[id] = InputDevice.Builder().setId(id).setSources(sources).build() + } inputDeviceListener?.onInputDeviceAdded(id) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractorKosmos.kt index 6e650a3c5391..77afa7989e83 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractorKosmos.kt @@ -21,6 +21,7 @@ import com.android.systemui.bluetooth.mockBroadcastDialogController import com.android.systemui.kosmos.Kosmos import com.android.systemui.media.controls.data.repository.mediaFilterRepository import com.android.systemui.media.controls.domain.pipeline.mediaDataProcessor +import com.android.systemui.media.controls.shared.mediaLogger import com.android.systemui.media.controls.util.mediaInstanceId import com.android.systemui.media.mediaOutputDialogManager import com.android.systemui.plugins.activityStarter @@ -39,5 +40,6 @@ val Kosmos.mediaControlInteractor by lockscreenUserManager = notificationLockscreenUserManager, mediaOutputDialogManager = mediaOutputDialogManager, broadcastDialogController = mockBroadcastDialogController, + mediaLogger = mediaLogger, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/factory/MediaControlInteractorFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/factory/MediaControlInteractorFactoryKosmos.kt index e490b7502894..9ea660fd3704 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/factory/MediaControlInteractorFactoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/factory/MediaControlInteractorFactoryKosmos.kt @@ -23,6 +23,7 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.media.controls.data.repository.mediaFilterRepository import com.android.systemui.media.controls.domain.pipeline.interactor.MediaControlInteractor import com.android.systemui.media.controls.domain.pipeline.mediaDataProcessor +import com.android.systemui.media.controls.shared.mediaLogger import com.android.systemui.media.mediaOutputDialogManager import com.android.systemui.plugins.activityStarter import com.android.systemui.statusbar.notificationLockscreenUserManager @@ -42,6 +43,7 @@ val Kosmos.mediaControlInteractorFactory by lockscreenUserManager = notificationLockscreenUserManager, mediaOutputDialogManager = mediaOutputDialogManager, broadcastDialogController = mockBroadcastDialogController, + mediaLogger = mediaLogger, ) } } diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java index 4cb2ce1bdfd7..5d251bdafd44 100644 --- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java +++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java @@ -195,23 +195,25 @@ public final class RavenwoodAwareTestRunner extends Runner implements Filterable try { performGlobalInitialization(); - mTestClass = new TestClass(testClass); - - Log.v(TAG, "RavenwoodAwareTestRunner starting for " + testClass.getCanonicalName()); - - onRunnerInitializing(); - /* * If the class has @DisabledOnRavenwood, then we'll delegate to * ClassSkippingTestRunner, which simply skips it. + * + * We need to do it before instantiating TestClass for b/367694651. */ if (isOnRavenwood() && !RavenwoodAwareTestRunnerHook.shouldRunClassOnRavenwood( - mTestClass.getJavaClass())) { - mRealRunner = new ClassSkippingTestRunner(mTestClass); + testClass)) { + mRealRunner = new ClassSkippingTestRunner(testClass); mDescription = mRealRunner.getDescription(); return; } + mTestClass = new TestClass(testClass); + + Log.v(TAG, "RavenwoodAwareTestRunner starting for " + testClass.getCanonicalName()); + + onRunnerInitializing(); + // Find the real runner. final Class<? extends Runner> realRunnerClass; final InnerRunner innerRunnerAnnotation = mTestClass.getAnnotation(InnerRunner.class); @@ -444,14 +446,11 @@ public final class RavenwoodAwareTestRunner extends Runner implements Filterable * filter. */ private static class ClassSkippingTestRunner extends Runner implements Filterable { - private final TestClass mTestClass; private final Description mDescription; private boolean mFilteredOut; - ClassSkippingTestRunner(TestClass testClass) { - mTestClass = testClass; - mDescription = Description.createTestDescription( - testClass.getJavaClass(), testClass.getJavaClass().getSimpleName()); + ClassSkippingTestRunner(Class<?> testClass) { + mDescription = Description.createTestDescription(testClass, testClass.getSimpleName()); mFilteredOut = false; } diff --git a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerCallbackTest.java b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerCallbackTest.java index 6d8fb983504b..09ed12d49cea 100644 --- a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerCallbackTest.java +++ b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerCallbackTest.java @@ -17,12 +17,14 @@ package com.android.ravenwoodtest.runnercallbacktests; import static org.junit.Assume.assumeTrue; +import android.platform.test.annotations.DisabledOnRavenwood; import android.platform.test.annotations.NoRavenizer; import android.platform.test.ravenwood.RavenwoodAwareTestRunner; import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.AfterClass; +import org.junit.Assert; import org.junit.Assume; import org.junit.BeforeClass; import org.junit.ClassRule; @@ -353,4 +355,34 @@ public class RavenwoodRunnerCallbackTest extends RavenwoodRunnerTestBase { public void test2() { } } + + /** + * The test class is unloadable, but has a @DisabledOnRavenwood. + */ + @RunWith(AndroidJUnit4.class) + @DisabledOnRavenwood + // CHECKSTYLE:OFF + @Expected(""" + testRunStarted: classes + testSuiteStarted: classes + testSuiteStarted: ClassUnloadbleTest(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ClassUnloadbleTest) + testIgnored: ClassUnloadbleTest(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ClassUnloadbleTest) + testSuiteFinished: ClassUnloadbleTest(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ClassUnloadbleTest) + testSuiteFinished: classes + testRunFinished: 0,0,0,1 + """) + // CHECKSTYLE:ON + public static class ClassUnloadbleTest { + static { + Assert.fail("Class unloadable!"); + } + + @Test + public void test1() { + } + + @Test + public void test2() { + } + } } diff --git a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java index 8c6f50e5c1bd..e29b6e403f2a 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java +++ b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java @@ -145,13 +145,25 @@ public class MetadataSyncAdapter { ArrayMap<String, ArraySet<String>> removedFunctionsDiffMap = getRemovedFunctionsDiffMap(staticPackageToFunctionMap, runtimePackageToFunctionMap); - Set<AppSearchSchema> appRuntimeMetadataSchemas = - getAllRuntimeMetadataSchemas(staticPackageToFunctionMap.keySet()); - appRuntimeMetadataSchemas.add( - AppFunctionRuntimeMetadata.createParentAppFunctionRuntimeSchema()); + if (!staticPackageToFunctionMap.keySet().equals(runtimePackageToFunctionMap.keySet())) { + // Drop removed packages from removedFunctionsDiffMap, as setSchema() deletes them + ArraySet<String> removedPackages = + getRemovedPackages( + staticPackageToFunctionMap.keySet(), removedFunctionsDiffMap.keySet()); + for (String packageName : removedPackages) { + removedFunctionsDiffMap.remove(packageName); + } + Set<AppSearchSchema> appRuntimeMetadataSchemas = + getAllRuntimeMetadataSchemas(staticPackageToFunctionMap.keySet()); + appRuntimeMetadataSchemas.add( + AppFunctionRuntimeMetadata.createParentAppFunctionRuntimeSchema()); + SetSchemaRequest addSetSchemaRequest = + buildSetSchemaRequestForRuntimeMetadataSchemas( + mPackageManager, appRuntimeMetadataSchemas); + Objects.requireNonNull( + runtimeMetadataSearchSession.setSchema(addSetSchemaRequest).get()); + } - // Operation order matters here. i.e. remove -> setSchema -> add. Otherwise we would - // encounter an error trying to delete a document with no existing schema. if (!removedFunctionsDiffMap.isEmpty()) { RemoveByDocumentIdRequest removeByDocumentIdRequest = buildRemoveRuntimeMetadataRequest(removedFunctionsDiffMap); @@ -164,12 +176,6 @@ public class MetadataSyncAdapter { } if (!addedFunctionsDiffMap.isEmpty()) { - // TODO(b/357551503): only set schema on package diff - SetSchemaRequest addSetSchemaRequest = - buildSetSchemaRequestForRuntimeMetadataSchemas( - mPackageManager, appRuntimeMetadataSchemas); - Objects.requireNonNull( - runtimeMetadataSearchSession.setSchema(addSetSchemaRequest).get()); PutDocumentsRequest putDocumentsRequest = buildPutRuntimeMetadataRequest(addedFunctionsDiffMap); AppSearchBatchResult<String, Void> putDocumentBatchResult = @@ -276,6 +282,30 @@ public class MetadataSyncAdapter { } /** + * This method returns a set of packages that are in the removed function packages but not in + * the all existing static packages. + * + * @param allExistingStaticPackages A set of all existing static metadata packages. + * @param removedFunctionPackages A set of all removed function packages. + * @return A set of packages that are in the removed function packages but not in the all + * existing static packages. + */ + @NonNull + private static ArraySet<String> getRemovedPackages( + @NonNull Set<String> allExistingStaticPackages, + @NonNull Set<String> removedFunctionPackages) { + ArraySet<String> removedPackages = new ArraySet<>(); + + for (String packageName : removedFunctionPackages) { + if (!allExistingStaticPackages.contains(packageName)) { + removedPackages.add(packageName); + } + } + + return removedPackages; + } + + /** * This method returns a map of package names to a set of function ids that are in the static * metadata but not in the runtime metadata. * diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index b109472a2a1e..2fa0e0d0d946 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -720,6 +720,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState void handleInlineSuggestionRequest(InlineSuggestionsRequest inlineSuggestionsRequest, ViewState viewState) { + if (sVerbose) { + Slog.v(TAG, "handleInlineSuggestionRequest(): inline suggestion request received"); + } synchronized (mLock) { if (!mWaitForInlineRequest || mPendingInlineSuggestionsRequest != null) { return; @@ -734,15 +737,27 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") void maybeRequestFillLocked() { if (mPendingFillRequest == null) { + if (sVerbose) { + Slog.v(TAG, "maybeRequestFillLocked(): cancelling calling fill request " + + "due to empty pending fill request"); + } return; } mFieldClassificationIdSnapshot = sIdCounterForPcc.get(); if (mWaitForInlineRequest) { if (mPendingInlineSuggestionsRequest == null) { + if (sVerbose) { + Slog.v(TAG, "maybeRequestFillLocked(): cancelling calling fill request " + + "due to waiting for inline request and pending inline request is " + + "currently empty"); + } return; } - + if (sVerbose) { + Slog.v(TAG, "maybeRequestFillLocked(): adding inline request to pending " + + "fill request"); + } mPendingFillRequest = new FillRequest(mPendingFillRequest.getId(), mPendingFillRequest.getFillContexts(), mPendingFillRequest.getHints(), @@ -750,8 +765,17 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mPendingFillRequest.getFlags(), mPendingInlineSuggestionsRequest, mPendingFillRequest.getDelayedFillIntentSender()); + } else { + if (sVerbose) { + Slog.v(TAG, "maybeRequestFillLocked(): not adding inline request to pending " + + "fill request"); + } } + mLastFillRequest = mPendingFillRequest; + if (sVerbose) { + Slog.v(TAG, "maybeRequestFillLocked(): sending fill request"); + } if (shouldRequestSecondaryProvider(mPendingFillRequest.getFlags()) && mSecondaryProviderHandler != null) { Slog.v(TAG, "Requesting fill response to secondary provider."); diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java index 6657c1c1ba89..59dea099c2a1 100644 --- a/services/core/java/com/android/server/BatteryService.java +++ b/services/core/java/com/android/server/BatteryService.java @@ -160,6 +160,7 @@ public final class BatteryService extends SystemService { private int mLastChargeCounter; private int mLastBatteryCycleCount; private int mLastChargingState; + private int mLastBatteryCapacityLevel; /** * The last seen charging policy. This requires the * {@link android.Manifest.permission#BATTERY_STATS} permission and should therefore not be @@ -609,7 +610,8 @@ public final class BatteryService extends SystemService { || mHealthInfo.batteryChargeCounterUah != mLastChargeCounter || mInvalidCharger != mLastInvalidCharger || mHealthInfo.batteryCycleCount != mLastBatteryCycleCount - || mHealthInfo.chargingState != mLastChargingState)) { + || mHealthInfo.chargingState != mLastChargingState + || mHealthInfo.batteryCapacityLevel != mLastBatteryCapacityLevel)) { if (mPlugType != mLastPlugType) { if (mLastPlugType == BATTERY_PLUGGED_NONE) { @@ -829,6 +831,7 @@ public final class BatteryService extends SystemService { mLastInvalidCharger = mInvalidCharger; mLastBatteryCycleCount = mHealthInfo.batteryCycleCount; mLastChargingState = mHealthInfo.chargingState; + mLastBatteryCapacityLevel = mHealthInfo.batteryCapacityLevel; } } @@ -862,6 +865,7 @@ public final class BatteryService extends SystemService { intent.putExtra(BatteryManager.EXTRA_CHARGE_COUNTER, mHealthInfo.batteryChargeCounterUah); intent.putExtra(BatteryManager.EXTRA_CYCLE_COUNT, mHealthInfo.batteryCycleCount); intent.putExtra(BatteryManager.EXTRA_CHARGING_STATUS, mHealthInfo.chargingState); + intent.putExtra(BatteryManager.EXTRA_CAPACITY_LEVEL, mHealthInfo.batteryCapacityLevel); if (DEBUG) { Slog.d(TAG, "Sending ACTION_BATTERY_CHANGED. scale:" + BATTERY_SCALE + ", info:" + mHealthInfo.toString()); @@ -964,6 +968,7 @@ public final class BatteryService extends SystemService { event.putLong(BatteryManager.EXTRA_EVENT_TIMESTAMP, now); event.putInt(BatteryManager.EXTRA_CYCLE_COUNT, mHealthInfo.batteryCycleCount); event.putInt(BatteryManager.EXTRA_CHARGING_STATUS, mHealthInfo.chargingState); + event.putInt(BatteryManager.EXTRA_CAPACITY_LEVEL, mHealthInfo.batteryCapacityLevel); boolean queueWasEmpty = mBatteryLevelsEventQueue.isEmpty(); mBatteryLevelsEventQueue.add(event); @@ -1401,6 +1406,7 @@ public final class BatteryService extends SystemService { pw.println(" technology: " + mHealthInfo.batteryTechnology); pw.println(" Charging state: " + mHealthInfo.chargingState); pw.println(" Charging policy: " + mHealthInfo.chargingPolicy); + pw.println(" Capacity level: " + mHealthInfo.batteryCapacityLevel); } else { Shell shell = new Shell(); shell.exec(mBinderService, null, fd, null, args, null, new ResultReceiver(null)); diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING index 68d0ad265a46..a459ea944008 100644 --- a/services/core/java/com/android/server/TEST_MAPPING +++ b/services/core/java/com/android/server/TEST_MAPPING @@ -173,6 +173,15 @@ "include-filter": "com.android.server.wm.BackgroundActivityStart*" } ] + }, + { + "name": "CtsOsTestCases", + "file_patterns": ["StorageManagerService\\.java"], + "options": [ + { + "include-filter": "android.os.storage.cts.StorageStatsManagerTest" + } + ] } ] } diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java index 1c13ad5b3ceb..f32031dec43c 100644 --- a/services/core/java/com/android/server/UiModeManagerService.java +++ b/services/core/java/com/android/server/UiModeManagerService.java @@ -32,11 +32,11 @@ import static android.app.UiModeManager.PROJECTION_TYPE_AUTOMOTIVE; import static android.app.UiModeManager.PROJECTION_TYPE_NONE; import static android.os.UserHandle.USER_SYSTEM; import static android.os.UserHandle.getCallingUserId; -import static android.os.UserManager.isVisibleBackgroundUsersEnabled; import static android.provider.Settings.Secure.CONTRAST_LEVEL; import static android.util.TimeUtils.isTimeBetween; import static com.android.internal.util.FunctionalUtils.ignoreRemoteException; +import static com.android.server.pm.UserManagerService.enforceCurrentUserIfVisibleBackgroundEnabled; import android.annotation.IntRange; import android.annotation.NonNull; @@ -100,7 +100,6 @@ import com.android.internal.app.DisableCarModeActivity; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.internal.notification.SystemNotificationChannels; import com.android.internal.util.DumpUtils; -import com.android.server.pm.UserManagerService; import com.android.server.twilight.TwilightListener; import com.android.server.twilight.TwilightManager; import com.android.server.twilight.TwilightState; @@ -850,7 +849,7 @@ final class UiModeManagerService extends SystemService { } final int user = UserHandle.getCallingUserId(); - enforceValidCallingUser(user); + enforceCurrentUserIfVisibleBackgroundEnabled(user); final long ident = Binder.clearCallingIdentity(); try { @@ -914,7 +913,7 @@ final class UiModeManagerService extends SystemService { @AttentionModeThemeOverlayType int attentionModeThemeOverlayType) { setAttentionModeThemeOverlay_enforcePermission(); - enforceValidCallingUser(UserHandle.getCallingUserId()); + enforceCurrentUserIfVisibleBackgroundEnabled(UserHandle.getCallingUserId()); synchronized (mLock) { if (mAttentionModeThemeOverlay != attentionModeThemeOverlayType) { @@ -1005,7 +1004,7 @@ final class UiModeManagerService extends SystemService { return false; } final int user = Binder.getCallingUserHandle().getIdentifier(); - enforceValidCallingUser(user); + enforceCurrentUserIfVisibleBackgroundEnabled(user); if (user != mCurrentUser && getContext().checkCallingOrSelfPermission( android.Manifest.permission.INTERACT_ACROSS_USERS) @@ -1064,7 +1063,7 @@ final class UiModeManagerService extends SystemService { return; } final int user = UserHandle.getCallingUserId(); - enforceValidCallingUser(user); + enforceCurrentUserIfVisibleBackgroundEnabled(user); final long ident = Binder.clearCallingIdentity(); try { @@ -1094,7 +1093,7 @@ final class UiModeManagerService extends SystemService { return; } final int user = UserHandle.getCallingUserId(); - enforceValidCallingUser(user); + enforceCurrentUserIfVisibleBackgroundEnabled(user); final long ident = Binder.clearCallingIdentity(); try { @@ -1116,7 +1115,7 @@ final class UiModeManagerService extends SystemService { assertLegit(callingPackage); assertSingleProjectionType(projectionType); enforceProjectionTypePermissions(projectionType); - enforceValidCallingUser(getCallingUserId()); + enforceCurrentUserIfVisibleBackgroundEnabled(getCallingUserId()); synchronized (mLock) { if (mProjectionHolders == null) { @@ -1162,7 +1161,7 @@ final class UiModeManagerService extends SystemService { assertLegit(callingPackage); assertSingleProjectionType(projectionType); enforceProjectionTypePermissions(projectionType); - enforceValidCallingUser(getCallingUserId()); + enforceCurrentUserIfVisibleBackgroundEnabled(getCallingUserId()); return releaseProjectionUnchecked(projectionType, callingPackage); } @@ -1204,7 +1203,7 @@ final class UiModeManagerService extends SystemService { return; } - enforceValidCallingUser(getCallingUserId()); + enforceCurrentUserIfVisibleBackgroundEnabled(getCallingUserId()); synchronized (mLock) { if (mProjectionListeners == null) { @@ -1253,32 +1252,6 @@ final class UiModeManagerService extends SystemService { } }; - // This method validates whether calling user is valid in visible background users - // feature. Valid user is the current user or the system or in the same profile group as - // the current user. - private void enforceValidCallingUser(int userId) { - if (!isVisibleBackgroundUsersEnabled()) { - return; - } - if (LOG) { - Slog.d(TAG, "enforceValidCallingUser: userId=" + userId - + " isSystemUser=" + (userId == USER_SYSTEM) + " current user=" + mCurrentUser - + " callingPid=" + Binder.getCallingPid() - + " callingUid=" + mInjector.getCallingUid()); - } - long ident = Binder.clearCallingIdentity(); - try { - if (userId != USER_SYSTEM && userId != mCurrentUser - && !UserManagerService.getInstance().isSameProfileGroup(userId, mCurrentUser)) { - throw new SecurityException( - "Calling user is not valid for level-1 compatibility in MUMD. " - + "callingUserId=" + userId + " currentUserId=" + mCurrentUser); - } - } finally { - Binder.restoreCallingIdentity(ident); - } - } - private void enforceProjectionTypePermissions(@UiModeManager.ProjectionType int p) { if ((p & PROJECTION_TYPE_AUTOMOTIVE) != 0) { getContext().enforceCallingPermission( diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 54a741060bbe..871c32086a7f 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -714,12 +714,14 @@ public class ActivityManagerService extends IActivityManager.Stub /** * Map userId to its companion app uids. */ + @GuardedBy("mCompanionAppUidsMap") private final Map<Integer, Set<Integer>> mCompanionAppUidsMap = new ArrayMap<>(); /** * The profile owner UIDs. */ - private ArraySet<Integer> mProfileOwnerUids = null; + @GuardedBy("mProfileOwnerUids") + private final ArraySet<Integer> mProfileOwnerUids = new ArraySet<>(); final UserController mUserController; @VisibleForTesting @@ -17535,32 +17537,35 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public void setProfileOwnerUid(ArraySet<Integer> profileOwnerUids) { - synchronized (ActivityManagerService.this) { - mProfileOwnerUids = profileOwnerUids; + synchronized (mProfileOwnerUids) { + mProfileOwnerUids.clear(); + mProfileOwnerUids.addAll(profileOwnerUids); } } @Override public boolean isProfileOwner(int uid) { - synchronized (ActivityManagerService.this) { - return mProfileOwnerUids != null && mProfileOwnerUids.indexOf(uid) >= 0; + synchronized (mProfileOwnerUids) { + return mProfileOwnerUids.indexOf(uid) >= 0; } } @Override public void setCompanionAppUids(int userId, Set<Integer> companionAppUids) { - synchronized (ActivityManagerService.this) { + synchronized (mCompanionAppUidsMap) { mCompanionAppUidsMap.put(userId, companionAppUids); } } @Override public boolean isAssociatedCompanionApp(int userId, int uid) { - final Set<Integer> allUids = mCompanionAppUidsMap.get(userId); - if (allUids == null) { - return false; + synchronized (mCompanionAppUidsMap) { + final Set<Integer> allUids = mCompanionAppUidsMap.get(userId); + if (allUids == null) { + return false; + } + return allUids.contains(uid); } - return allUids.contains(uid); } @Override diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index b186eaacab74..262c76e4a4d7 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -156,6 +156,9 @@ import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Objects; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; @@ -223,18 +226,9 @@ class UserController implements Handler.Callback { private static final int USER_SWITCH_CALLBACKS_TIMEOUT_MS = 5 * 1000; /** - * Amount of time waited for - * {@link ActivityTaskManagerInternal.ScreenObserver#onKeyguardStateChanged} callbacks to be - * called after calling {@link WindowManagerService#lockDeviceNow}. - * Otherwise, we should throw a {@link RuntimeException} and never dismiss the - * {@link UserSwitchingDialog}. - */ - static final int SHOW_KEYGUARD_TIMEOUT_MS = 20 * 1000; - - /** * Amount of time waited for {@link WindowManagerService#dismissKeyguard} callbacks to be * called after dismissing the keyguard. - * Otherwise, we should move on to dismiss the dialog {@link #dismissUserSwitchDialog}} + * Otherwise, we should move on to dismiss the dialog {@link #dismissUserSwitchDialog()} * and report user switch is complete {@link #REPORT_USER_SWITCH_COMPLETE_MSG}. */ private static final int DISMISS_KEYGUARD_TIMEOUT_MS = 2 * 1000; @@ -1986,10 +1980,18 @@ class UserController implements Handler.Callback { // it should be moved outside, but for now it's not as there are many calls to // external components here afterwards updateProfileRelatedCaches(); + dispatchOnBeforeUserSwitching(userId); mInjector.getWindowManager().setCurrentUser(userId); mInjector.reportCurWakefulnessUsageEvent(); + // Once the internal notion of the active user has switched, we lock the device + // with the option to show the user switcher on the keyguard. if (userSwitchUiEnabled) { mInjector.getWindowManager().setSwitchingUser(true); + // Only lock if the user has a secure keyguard PIN/Pattern/Pwd + if (mInjector.getKeyguardManager().isDeviceSecure(userId)) { + // Make sure the device is locked before moving on with the user switch + mInjector.lockDeviceNowAndWaitForKeyguardShown(); + } } } else { @@ -2284,6 +2286,25 @@ class UserController implements Handler.Callback { mUserSwitchObservers.finishBroadcast(); } + private void dispatchOnBeforeUserSwitching(@UserIdInt int newUserId) { + final TimingsTraceAndSlog t = new TimingsTraceAndSlog(); + t.traceBegin("dispatchOnBeforeUserSwitching-" + newUserId); + final int observerCount = mUserSwitchObservers.beginBroadcast(); + for (int i = 0; i < observerCount; i++) { + final String name = "#" + i + " " + mUserSwitchObservers.getBroadcastCookie(i); + t.traceBegin("onBeforeUserSwitching-" + name); + try { + mUserSwitchObservers.getBroadcastItem(i).onBeforeUserSwitching(newUserId); + } catch (RemoteException e) { + // Ignore + } finally { + t.traceEnd(); + } + } + mUserSwitchObservers.finishBroadcast(); + t.traceEnd(); + } + /** Called on handler thread */ @VisibleForTesting void dispatchUserSwitchComplete(@UserIdInt int oldUserId, @UserIdInt int newUserId) { @@ -2499,17 +2520,6 @@ class UserController implements Handler.Callback { final int observerCount = mUserSwitchObservers.beginBroadcast(); if (observerCount > 0) { - for (int i = 0; i < observerCount; i++) { - final String name = "#" + i + " " + mUserSwitchObservers.getBroadcastCookie(i); - t.traceBegin("onBeforeUserSwitching-" + name); - try { - mUserSwitchObservers.getBroadcastItem(i).onBeforeUserSwitching(newUserId); - } catch (RemoteException e) { - // Ignore - } finally { - t.traceEnd(); - } - } final ArraySet<String> curWaitingUserSwitchCallbacks = new ArraySet<>(); synchronized (mLock) { uss.switching = true; @@ -2606,54 +2616,32 @@ class UserController implements Handler.Callback { @VisibleForTesting void completeUserSwitch(int oldUserId, int newUserId) { - final Runnable sendUserSwitchCompleteMessage = () -> { - mHandler.removeMessages(REPORT_USER_SWITCH_COMPLETE_MSG); - mHandler.sendMessage(mHandler.obtainMessage( - REPORT_USER_SWITCH_COMPLETE_MSG, oldUserId, newUserId)); - }; - if (isUserSwitchUiEnabled()) { - if (mInjector.getKeyguardManager().isDeviceSecure(newUserId)) { - this.showKeyguard(() -> dismissUserSwitchDialog(sendUserSwitchCompleteMessage)); - } else { - this.dismissKeyguard(() -> dismissUserSwitchDialog(sendUserSwitchCompleteMessage)); - } - } else { - sendUserSwitchCompleteMessage.run(); - } - } - - protected void showKeyguard(Runnable runnable) { - runWithTimeout(mInjector::showKeyguard, SHOW_KEYGUARD_TIMEOUT_MS, runnable, () -> { - throw new RuntimeException( - "Keyguard is not shown in " + SHOW_KEYGUARD_TIMEOUT_MS + " ms."); - }, "showKeyguard"); - } - - protected void dismissKeyguard(Runnable runnable) { - runWithTimeout(mInjector::dismissKeyguard, DISMISS_KEYGUARD_TIMEOUT_MS, runnable, runnable, - "dismissKeyguard"); + final boolean isUserSwitchUiEnabled = isUserSwitchUiEnabled(); + // serialize each conditional step + await( + // STEP 1 - If there is no challenge set, dismiss the keyguard right away + isUserSwitchUiEnabled && !mInjector.getKeyguardManager().isDeviceSecure(newUserId), + mInjector::dismissKeyguard, + () -> await( + // STEP 2 - If user switch ui was enabled, dismiss user switch dialog + isUserSwitchUiEnabled, + this::dismissUserSwitchDialog, + () -> { + // STEP 3 - Send REPORT_USER_SWITCH_COMPLETE_MSG to broadcast + // ACTION_USER_SWITCHED & call UserSwitchObservers.onUserSwitchComplete + mHandler.removeMessages(REPORT_USER_SWITCH_COMPLETE_MSG); + mHandler.sendMessage(mHandler.obtainMessage( + REPORT_USER_SWITCH_COMPLETE_MSG, oldUserId, newUserId)); + } + )); } - private void runWithTimeout(Consumer<Runnable> task, int timeoutMs, Runnable onSuccess, - Runnable onTimeout, String traceMsg) { - final AtomicInteger state = new AtomicInteger(0); // state = 0 (RUNNING) - - asyncTraceBegin(traceMsg, 0); - - mHandler.postDelayed(() -> { - if (state.compareAndSet(0, 1)) { // state = 1 (TIMEOUT) - asyncTraceEnd(traceMsg, 0); - Slogf.w(TAG, "Timeout: %s did not finish in %d ms", traceMsg, timeoutMs); - onTimeout.run(); - } - }, timeoutMs); - - task.accept(() -> { - if (state.compareAndSet(0, 2)) { // state = 2 (SUCCESS) - asyncTraceEnd(traceMsg, 0); - onSuccess.run(); - } - }); + private void await(boolean condition, Consumer<Runnable> conditionalStep, Runnable nextStep) { + if (condition) { + conditionalStep.accept(nextStep); + } else { + nextStep.run(); + } } private void moveUserToForeground(UserState uss, int newUserId) { @@ -4100,45 +4088,29 @@ class UserController implements Handler.Callback { return IStorageManager.Stub.asInterface(ServiceManager.getService("mount")); } - protected void showKeyguard(Runnable runnable) { - if (getWindowManager().isKeyguardLocked()) { - runnable.run(); - return; - } - getActivityTaskManagerInternal().registerScreenObserver( - new ActivityTaskManagerInternal.ScreenObserver() { - @Override - public void onAwakeStateChanged(boolean isAwake) { - - } - - @Override - public void onKeyguardStateChanged(boolean isShowing) { - if (isShowing) { - getActivityTaskManagerInternal().unregisterScreenObserver(this); - runnable.run(); - } - } - } - ); - getWindowManager().lockDeviceNow(); - } - protected void dismissKeyguard(Runnable runnable) { + final AtomicBoolean isFirst = new AtomicBoolean(true); + final Runnable runOnce = () -> { + if (isFirst.getAndSet(false)) { + runnable.run(); + } + }; + + mHandler.postDelayed(runOnce, DISMISS_KEYGUARD_TIMEOUT_MS); getWindowManager().dismissKeyguard(new IKeyguardDismissCallback.Stub() { @Override public void onDismissError() throws RemoteException { - runnable.run(); + mHandler.post(runOnce); } @Override public void onDismissSucceeded() throws RemoteException { - runnable.run(); + mHandler.post(runOnce); } @Override public void onDismissCancelled() throws RemoteException { - runnable.run(); + mHandler.post(runOnce); } }, /* message= */ null); } @@ -4164,5 +4136,43 @@ class UserController implements Handler.Callback { void onSystemUserVisibilityChanged(boolean visible) { getUserManagerInternal().onSystemUserVisibilityChanged(visible); } + + void lockDeviceNowAndWaitForKeyguardShown() { + if (getWindowManager().isKeyguardLocked()) { + return; + } + + final TimingsTraceAndSlog t = new TimingsTraceAndSlog(); + t.traceBegin("lockDeviceNowAndWaitForKeyguardShown"); + + final CountDownLatch latch = new CountDownLatch(1); + ActivityTaskManagerInternal.ScreenObserver screenObserver = + new ActivityTaskManagerInternal.ScreenObserver() { + @Override + public void onAwakeStateChanged(boolean isAwake) { + + } + + @Override + public void onKeyguardStateChanged(boolean isShowing) { + if (isShowing) { + latch.countDown(); + } + } + }; + + getActivityTaskManagerInternal().registerScreenObserver(screenObserver); + getWindowManager().lockDeviceNow(); + try { + if (!latch.await(20, TimeUnit.SECONDS)) { + throw new RuntimeException("Keyguard is not shown in 20 seconds"); + } + } catch (InterruptedException e) { + throw new RuntimeException(e); + } finally { + getActivityTaskManagerInternal().unregisterScreenObserver(screenObserver); + t.traceEnd(); + } + } } } diff --git a/services/core/java/com/android/server/cpu/CpuMonitorService.java b/services/core/java/com/android/server/cpu/CpuMonitorService.java index 88ff7e4103f9..2cadbc58d0f2 100644 --- a/services/core/java/com/android/server/cpu/CpuMonitorService.java +++ b/services/core/java/com/android/server/cpu/CpuMonitorService.java @@ -216,7 +216,7 @@ public final class CpuMonitorService extends SystemService { @Override public void onBootPhase(int phase) { - if (phase != PHASE_BOOT_COMPLETED) { + if (phase != PHASE_BOOT_COMPLETED || mHandler == null) { return; } Slogf.i(TAG, "Stopping periodic cpuset reading on boot complete"); diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index ac75ef7b4656..8e41d18f0953 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -1688,7 +1688,11 @@ public class HdmiControlService extends SystemService { private void sendCecCommandWithRetries(HdmiCecMessage command, @Nullable SendMessageCallback callback) { assertRunOnServiceThread(); - HdmiCecLocalDevice localDevice = getAllCecLocalDevices().get(0); + List<HdmiCecLocalDevice> devices = getAllCecLocalDevices(); + if (devices.isEmpty()) { + return; + } + HdmiCecLocalDevice localDevice = devices.get(0); if (localDevice != null) { sendCecCommandWithoutRetries(command, new SendMessageCallback() { @Override diff --git a/services/core/java/com/android/server/input/debug/TouchpadDebugView.java b/services/core/java/com/android/server/input/debug/TouchpadDebugView.java index 5ff8568f81b2..0e940d281b09 100644 --- a/services/core/java/com/android/server/input/debug/TouchpadDebugView.java +++ b/services/core/java/com/android/server/input/debug/TouchpadDebugView.java @@ -180,6 +180,10 @@ public class TouchpadDebugView extends LinearLayout { @Override public boolean onTouchEvent(MotionEvent event) { + if (event.getClassification() == MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE) { + return false; + } + float deltaX; float deltaY; switch (event.getAction()) { diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java index 4b2c12abe5bb..63bd9ab815b2 100644 --- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java +++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java @@ -389,7 +389,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements // Reload gnss config for no SIM case mGnssConfiguration.reloadGpsProperties(); } - if (Flags.enableNiSuplMessageInjectionByCarrierConfig()) { + if (Flags.enableNiSuplMessageInjectionByCarrierConfigBugfix()) { updateNiSuplMessageListenerRegistration( mGnssConfiguration.isNiSuplMessageInjectionEnabled()); } @@ -538,7 +538,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements intentFilter.addAction(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED); mContext.registerReceiver(mIntentReceiver, intentFilter, null, mHandler); - if (!Flags.enableNiSuplMessageInjectionByCarrierConfig()) { + if (!Flags.enableNiSuplMessageInjectionByCarrierConfigBugfix()) { updateNiSuplMessageListenerRegistration( mGnssConfiguration.isNiSuplMessageInjectionEnabled()); } @@ -1672,7 +1672,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements if (dumpAll) { mNetworkTimeHelper.dump(pw); pw.println("mSupportsPsds=" + mSupportsPsds); - if (Flags.enableNiSuplMessageInjectionByCarrierConfig()) { + if (Flags.enableNiSuplMessageInjectionByCarrierConfigBugfix()) { pw.println("mNiSuplMessageListenerRegistered=" + mNiSuplMessageListenerRegistered); } diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java index 158d444bcff2..1e25f1cf1d5e 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java @@ -140,7 +140,7 @@ class LockSettingsStorage { try { db.delete(TABLE, COLUMN_KEY + "=? AND " + COLUMN_USERID + "=?", new String[] {key, Integer.toString(userId)}); - db.insert(TABLE, null, cv); + db.insertOrThrow(TABLE, null, cv); db.setTransactionSuccessful(); mCache.putKeyValue(key, value, userId); } finally { diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java index 621c090d37b8..48d24f2e14dd 100644 --- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java +++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java @@ -179,7 +179,8 @@ public final class MediaProjectionManagerService extends SystemService /** * In order to record the keyguard, the MediaProjection package must be either: * - a holder of RECORD_SENSITIVE_CONTENT permission, or - * - be one of the bugreport whitelisted packages + * - be one of the bugreport allowlisted packages, or + * - hold the OP_PROJECT_MEDIA AppOp. */ private boolean canCaptureKeyguard() { if (!android.companion.virtualdevice.flags.Flags.mediaProjectionKeyguardRestrictions()) { @@ -194,6 +195,14 @@ public final class MediaProjectionManagerService extends SystemService == PackageManager.PERMISSION_GRANTED) { return true; } + boolean operationActive = mAppOps.isOperationActive(AppOpsManager.OP_PROJECT_MEDIA, + mProjectionGrant.uid, + mProjectionGrant.packageName); + if (operationActive) { + // Some tools use media projection by granting the OP_PROJECT_MEDIA app + // op via a shell command. Those tools can be granted keyguard capture + return true; + } return SystemConfig.getInstance().getBugreportWhitelistedPackages() .contains(mProjectionGrant.packageName); } diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING index c75622cb9bbc..21a6df203015 100644 --- a/services/core/java/com/android/server/pm/TEST_MAPPING +++ b/services/core/java/com/android/server/pm/TEST_MAPPING @@ -208,6 +208,22 @@ "name": "CtsUpdateOwnershipEnforcementTestCases" }, { + "name": "CtsPackageInstallerCUJDeviceAdminTestCases", + "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": "CtsPackageInstallerCUJInstallationTestCases", "file_patterns": [ "core/java/.*Install.*", @@ -224,6 +240,22 @@ ] }, { + "name": "CtsPackageInstallerCUJMultiUsersTestCases", + "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.*", diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java index 68026ea9094a..e3d71e4998be 100644 --- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java +++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java @@ -24,8 +24,11 @@ import android.annotation.AnyThread; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.WorkerThread; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; @@ -78,6 +81,8 @@ import java.util.function.Consumer; public final class RollbackPackageHealthObserver implements PackageHealthObserver { private static final String TAG = "RollbackPackageHealthObserver"; private static final String NAME = "rollback-observer"; + private static final String ACTION_NAME = RollbackPackageHealthObserver.class.getName(); + private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT | ApplicationInfo.FLAG_SYSTEM; @@ -596,12 +601,40 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve } }; - final LocalIntentReceiver rollbackReceiver = new LocalIntentReceiver(result -> { - mHandler.post(() -> onResult.accept(result)); - }); + if (Flags.refactorCrashrecovery()) { + // Define a BroadcastReceiver to handle the result + BroadcastReceiver rollbackReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent result) { + mHandler.post(() -> onResult.accept(result)); + } + }; + + // Register the BroadcastReceiver + mContext.registerReceiver(rollbackReceiver, + new IntentFilter(ACTION_NAME), + Context.RECEIVER_NOT_EXPORTED); + + Intent intentReceiver = new Intent(ACTION_NAME); + intentReceiver.putExtra("rollbackId", rollback.getRollbackId()); + intentReceiver.setPackage(mContext.getPackageName()); - rollbackManager.commitRollback(rollback.getRollbackId(), - Collections.singletonList(failedPackage), rollbackReceiver.getIntentSender()); + PendingIntent rollbackPendingIntent = PendingIntent.getBroadcast(mContext, + rollback.getRollbackId(), + intentReceiver, + PendingIntent.FLAG_MUTABLE); + + rollbackManager.commitRollback(rollback.getRollbackId(), + Collections.singletonList(failedPackage), + rollbackPendingIntent.getIntentSender()); + } else { + final LocalIntentReceiver rollbackReceiver = new LocalIntentReceiver(result -> { + mHandler.post(() -> onResult.accept(result)); + }); + + rollbackManager.commitRollback(rollback.getRollbackId(), + Collections.singletonList(failedPackage), rollbackReceiver.getIntentSender()); + } } /** diff --git a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java index 96a25dac21e3..1e82b8999834 100644 --- a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java +++ b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java @@ -322,9 +322,16 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver { if (SubscriptionManager.isValidSubscriptionId(subId)) { // Get only configs as needed to save memory. - final PersistableBundle carrierConfig = - CarrierConfigManager.getCarrierConfigSubset(mContext, subId, - VcnManager.VCN_RELATED_CARRIER_CONFIG_KEYS); + PersistableBundle carrierConfig = new PersistableBundle(); + try { + carrierConfig = + mCarrierConfigManager.getConfigForSubId( + subId, VcnManager.VCN_RELATED_CARRIER_CONFIG_KEYS); + + } catch (RuntimeException exception) { + Slog.w(TAG, "CarrierConfigLoader is not available."); + } + if (mDeps.isConfigForIdentifiedCarrier(carrierConfig)) { mReadySubIdsBySlotId.put(slotId, subId); diff --git a/services/core/java/com/android/server/wallpaper/WallpaperData.java b/services/core/java/com/android/server/wallpaper/WallpaperData.java index a698429ff09e..15f86e9c08ff 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperData.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperData.java @@ -78,7 +78,7 @@ class WallpaperData { /** * The component name of the currently set live wallpaper. */ - ComponentName wallpaperComponent; + private ComponentName mWallpaperComponent; // TODO(b/347235611) Remove this field /** @@ -195,7 +195,7 @@ class WallpaperData { */ WallpaperData(WallpaperData source) { this.userId = source.userId; - this.wallpaperComponent = source.wallpaperComponent; + this.mWallpaperComponent = source.mWallpaperComponent; this.mWhich = source.mWhich; this.wallpaperId = source.wallpaperId; this.cropHint.set(source.cropHint); @@ -230,6 +230,14 @@ class WallpaperData { return result; } + ComponentName getComponent() { + return mWallpaperComponent; + } + + void setComponent(ComponentName componentName) { + this.mWallpaperComponent = componentName; + } + @Override public String toString() { StringBuilder out = new StringBuilder(defaultString(this)); diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java index b15facb2945c..e3e83b3e1fd7 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java @@ -189,13 +189,13 @@ public class WallpaperDataParser { String comp = parser.getAttributeValue(null, "component"); if (removeNextWallpaperComponent()) { - wallpaperToParse.wallpaperComponent = comp != null + wallpaperToParse.setComponent(comp != null ? ComponentName.unflattenFromString(comp) - : null; - if (wallpaperToParse.wallpaperComponent == null - || "android".equals(wallpaperToParse.wallpaperComponent + : null); + if (wallpaperToParse.getComponent() == null + || "android".equals(wallpaperToParse.getComponent() .getPackageName())) { - wallpaperToParse.wallpaperComponent = mImageWallpaper; + wallpaperToParse.setComponent(mImageWallpaper); } } else { wallpaperToParse.nextWallpaperComponent = comp != null @@ -219,7 +219,7 @@ public class WallpaperDataParser { Slog.v(TAG, "primaryColors:" + wallpaper.primaryColors); Slog.v(TAG, "mName:" + wallpaper.name); if (removeNextWallpaperComponent()) { - Slog.v(TAG, "mWallpaperComponent:" + wallpaper.wallpaperComponent); + Slog.v(TAG, "mWallpaperComponent:" + wallpaper.getComponent()); } else { Slog.v(TAG, "mNextWallpaperComponent:" + wallpaper.nextWallpaperComponent); @@ -340,7 +340,7 @@ public class WallpaperDataParser { getAttributeInt(parser, "totalCropTop", 0), getAttributeInt(parser, "totalCropRight", 0), getAttributeInt(parser, "totalCropBottom", 0)); - ComponentName componentName = removeNextWallpaperComponent() ? wallpaper.wallpaperComponent + ComponentName componentName = removeNextWallpaperComponent() ? wallpaper.getComponent() : wallpaper.nextWallpaperComponent; if (multiCrop() && mImageWallpaper.equals(componentName)) { wallpaper.mCropHints = new SparseArray<>(); @@ -480,7 +480,7 @@ public class WallpaperDataParser { out.startTag(null, tag); out.attributeInt(null, "id", wallpaper.wallpaperId); - if (multiCrop() && mImageWallpaper.equals(wallpaper.wallpaperComponent)) { + if (multiCrop() && mImageWallpaper.equals(wallpaper.getComponent())) { if (wallpaper.mCropHints == null) { Slog.e(TAG, "cropHints should not be null when saved"); wallpaper.mCropHints = new SparseArray<>(); @@ -580,10 +580,10 @@ public class WallpaperDataParser { } out.attribute(null, "name", wallpaper.name); - if (wallpaper.wallpaperComponent != null - && !wallpaper.wallpaperComponent.equals(mImageWallpaper)) { + if (wallpaper.getComponent() != null + && !wallpaper.getComponent().equals(mImageWallpaper)) { out.attribute(null, "component", - wallpaper.wallpaperComponent.flattenToShortString()); + wallpaper.getComponent().flattenToShortString()); } if (wallpaper.allowBackup) { diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 6cc37ddda9bc..4754ffb5cf6e 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -276,7 +276,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub final boolean isMigration = moved && lockWallpaperChanged; final boolean isRestore = moved && !isMigration; final boolean isAppliedToLock = (wallpaper.mWhich & FLAG_LOCK) != 0; - final boolean needsUpdate = wallpaper.wallpaperComponent == null + final boolean needsUpdate = wallpaper.getComponent() == null || event != CLOSE_WRITE // includes the MOVED_TO case || wallpaper.imageWallpaperPending; @@ -527,7 +527,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub * @return true unless the wallpaper changed during the color computation */ private boolean extractColors(WallpaperData wallpaper) { - if (offloadColorExtraction()) return !mImageWallpaper.equals(wallpaper.wallpaperComponent); + if (offloadColorExtraction()) return !mImageWallpaper.equals(wallpaper.getComponent()); String cropFile = null; boolean defaultImageWallpaper = false; int wallpaperId; @@ -550,8 +550,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub synchronized (mLock) { // Not having a wallpaperComponent means it's a lock screen wallpaper. - final boolean imageWallpaper = mImageWallpaper.equals(wallpaper.wallpaperComponent) - || wallpaper.wallpaperComponent == null; + final boolean imageWallpaper = mImageWallpaper.equals(wallpaper.getComponent()) + || wallpaper.getComponent() == null; if (imageWallpaper && wallpaper.getCropFile().exists()) { cropFile = wallpaper.getCropFile().getAbsolutePath(); } else if (imageWallpaper && !wallpaper.cropExists() && !wallpaper.sourceExists()) { @@ -824,13 +824,13 @@ public class WallpaperManagerService extends IWallpaperManager.Stub return; } TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG); - t.traceBegin("WPMS.connectLocked-" + wallpaper.wallpaperComponent); + t.traceBegin("WPMS.connectLocked-" + wallpaper.getComponent()); if (DEBUG) Slog.v(TAG, "Adding window token: " + mToken); mWindowManagerInternal.addWindowToken(mToken, TYPE_WALLPAPER, mDisplayId, null /* options */); mWindowManagerInternal.setWallpaperShowWhenLocked( mToken, (wallpaper.mWhich & FLAG_LOCK) != 0); - if (multiCrop() && mImageWallpaper.equals(wallpaper.wallpaperComponent)) { + if (multiCrop() && mImageWallpaper.equals(wallpaper.getComponent())) { mWindowManagerInternal.setWallpaperCropHints(mToken, mWallpaperCropper.getRelativeCropHints(wallpaper)); } else { @@ -906,7 +906,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } if (!mWallpaper.wallpaperUpdating && mWallpaper.userId == mCurrentUserId) { - Slog.w(TAG, "Wallpaper reconnect timed out for " + mWallpaper.wallpaperComponent + Slog.w(TAG, "Wallpaper reconnect timed out for " + mWallpaper.getComponent() + ", reverting to built-in wallpaper!"); clearWallpaperLocked(mWallpaper.mWhich, mWallpaper.userId, false, null); } @@ -1035,9 +1035,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub public void onServiceDisconnected(ComponentName name) { synchronized (mLock) { Slog.w(TAG, "Wallpaper service gone: " + name); - if (!Objects.equals(name, mWallpaper.wallpaperComponent)) { + if (!Objects.equals(name, mWallpaper.getComponent())) { Slog.e(TAG, "Does not match expected wallpaper component " - + mWallpaper.wallpaperComponent); + + mWallpaper.getComponent()); } mService = null; forEachDisplayConnector(connector -> connector.mEngine = null); @@ -1065,7 +1065,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub fgHandler.postDelayed(mResetRunnable, WALLPAPER_RECONNECT_TIMEOUT_MS); if (DEBUG_LIVE) { Slog.i(TAG, - "Started wallpaper reconnect timeout for " + mWallpaper.wallpaperComponent); + "Started wallpaper reconnect timeout for " + mWallpaper.getComponent()); } } @@ -1081,7 +1081,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub return; } - final ComponentName wpService = mWallpaper.wallpaperComponent; + final ComponentName wpService = mWallpaper.getComponent(); // The broadcast of package update could be delayed after service disconnected. Try // to re-bind the service for 10 seconds. mWallpaper.mBindSource = BindSource.CONNECTION_TRY_TO_REBIND; @@ -1110,7 +1110,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub // The wallpaper disappeared. If this isn't a system-default one, track // crashes and fall back to default if it continues to misbehave. if (this == mWallpaper.connection) { - final ComponentName wpService = mWallpaper.wallpaperComponent; + final ComponentName wpService = mWallpaper.getComponent(); if (!mWallpaper.wallpaperUpdating && mWallpaper.userId == mCurrentUserId && !Objects.equals(mDefaultWallpaperComponent, wpService) @@ -1188,7 +1188,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub synchronized (mLock) { // Do not broadcast changes on ImageWallpaper since it's handled // internally by this class. - boolean isImageWallpaper = mImageWallpaper.equals(mWallpaper.wallpaperComponent); + boolean isImageWallpaper = mImageWallpaper.equals(mWallpaper.getComponent()); if (isImageWallpaper && (!offloadColorExtraction() || primaryColors == null)) { return; } @@ -1303,7 +1303,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (mNewWallpaper.mWhich == FLAG_SYSTEM) { // New wp is system only, so old system+lock is now lock only final boolean originalIsStatic = mImageWallpaper.equals( - mOriginalSystem.wallpaperComponent); + mOriginalSystem.getComponent()); if (originalIsStatic) { // Static wp: image file rename has already been tried via // migrateStaticSystemToLockWallpaperLocked() and added to the lock wp map @@ -1314,8 +1314,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (DEBUG) { Slog.v(TAG, "static system+lock to system success"); } - lockWp.wallpaperComponent = - mOriginalSystem.wallpaperComponent; + lockWp.setComponent(mOriginalSystem.getComponent()); lockWp.connection = mOriginalSystem.connection; lockWp.connection.mWallpaper = lockWp; mOriginalSystem.mWhich = FLAG_LOCK; @@ -1376,7 +1375,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub return; } for (WallpaperData wallpaper: getWallpapers()) { - final ComponentName wpService = wallpaper.wallpaperComponent; + final ComponentName wpService = wallpaper.getComponent(); if (wpService != null && wpService.getPackageName().equals(packageName)) { if (DEBUG_LIVE) { Slog.i(TAG, "Wallpaper " + wpService + " update has finished"); @@ -1402,8 +1401,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub return; } for (WallpaperData wallpaper: getWallpapers()) { - if (wallpaper.wallpaperComponent != null - && wallpaper.wallpaperComponent.getPackageName().equals(packageName)) { + if (wallpaper.getComponent() != null + && wallpaper.getComponent().getPackageName().equals(packageName)) { doPackagesChangedLocked(true, wallpaper); } } @@ -1417,10 +1416,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub return; } for (WallpaperData wallpaper: getWallpapers()) { - if (wallpaper.wallpaperComponent != null - && wallpaper.wallpaperComponent.getPackageName().equals(packageName)) { + if (wallpaper.getComponent() != null + && wallpaper.getComponent().getPackageName().equals(packageName)) { if (DEBUG_LIVE) { - Slog.i(TAG, "Wallpaper service " + wallpaper.wallpaperComponent + Slog.i(TAG, "Wallpaper service " + wallpaper.getComponent() + " is updating"); } wallpaper.wallpaperUpdating = true; @@ -1462,15 +1461,15 @@ public class WallpaperManagerService extends IWallpaperManager.Stub boolean doPackagesChangedLocked(boolean doit, WallpaperData wallpaper) { boolean changed = false; - if (wallpaper.wallpaperComponent != null) { - int change = isPackageDisappearing(wallpaper.wallpaperComponent + if (wallpaper.getComponent() != null) { + int change = isPackageDisappearing(wallpaper.getComponent() .getPackageName()); if (change == PACKAGE_PERMANENT_CHANGE || change == PACKAGE_TEMPORARY_CHANGE) { changed = true; if (doit) { Slog.w(TAG, "Wallpaper uninstalled, removing: " - + wallpaper.wallpaperComponent); + + wallpaper.getComponent()); clearWallpaperLocked(wallpaper.mWhich, wallpaper.userId, false, null); } } @@ -1485,15 +1484,15 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } } - if (wallpaper.wallpaperComponent != null - && isPackageModified(wallpaper.wallpaperComponent.getPackageName())) { + if (wallpaper.getComponent() != null + && isPackageModified(wallpaper.getComponent().getPackageName())) { try { - mContext.getPackageManager().getServiceInfo(wallpaper.wallpaperComponent, + mContext.getPackageManager().getServiceInfo(wallpaper.getComponent(), PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE); } catch (NameNotFoundException e) { Slog.w(TAG, "Wallpaper component gone, removing: " - + wallpaper.wallpaperComponent); + + wallpaper.getComponent()); clearWallpaperLocked(wallpaper.mWhich, wallpaper.userId, false, null); } } @@ -1636,8 +1635,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub // sure we have something to render boolean isImageComponent; if (removeNextWallpaperComponent()) { - isImageComponent = wallpaper.wallpaperComponent == null - || mImageWallpaper.equals(wallpaper.wallpaperComponent); + isImageComponent = wallpaper.getComponent() == null + || mImageWallpaper.equals(wallpaper.getComponent()); } else { isImageComponent = mImageWallpaper.equals(wallpaper.nextWallpaperComponent); } @@ -1892,10 +1891,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub final ComponentName cname; if (removeNextWallpaperComponent()) { - cname = wallpaper.wallpaperComponent; + cname = wallpaper.getComponent(); } else { - cname = (wallpaper.wallpaperComponent != null) - ? wallpaper.wallpaperComponent : wallpaper.nextWallpaperComponent; + cname = (wallpaper.getComponent() != null) + ? wallpaper.getComponent() : wallpaper.nextWallpaperComponent; } if (!bindWallpaperComponentLocked(cname, true, false, wallpaper, reply)) { // We failed to bind the desired wallpaper, but that might @@ -1927,7 +1926,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub // We might end up persisting the current wallpaper data // while locked, so pretend like the component was actually // bound into place - wallpaper.wallpaperComponent = wallpaper.nextWallpaperComponent; + wallpaper.setComponent(wallpaper.nextWallpaperComponent); } final WallpaperData fallback = new WallpaperData(wallpaper.userId, wallpaper.mWhich); @@ -2004,7 +2003,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub // lock only case: set the system wallpaper component to both screens if (which == FLAG_LOCK) { - component = wallpaper.wallpaperComponent; + component = wallpaper.getComponent(); finalWhich = FLAG_LOCK | FLAG_SYSTEM; } else { component = null; @@ -2310,7 +2309,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub checkPermission(READ_WALLPAPER_INTERNAL); WallpaperData wallpaper = (which == FLAG_LOCK) ? mLockWallpaperMap.get(userId) : mWallpaperMap.get(userId); - if (wallpaper == null || !mImageWallpaper.equals(wallpaper.wallpaperComponent)) { + if (wallpaper == null || !mImageWallpaper.equals(wallpaper.getComponent())) { return null; } SparseArray<Rect> relativeSuggestedCrops = @@ -2760,7 +2759,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub WallpaperData wallpaperData = (which == FLAG_LOCK ? mLockWallpaperMap : mWallpaperMap) .get(mCurrentUserId); if (wallpaperData == null) return false; - return mImageWallpaper.equals(wallpaperData.wallpaperComponent); + return mImageWallpaper.equals(wallpaperData.getComponent()); } } @@ -2996,7 +2995,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub final WallpaperData originalSystemWallpaper = mWallpaperMap.get(userId); final boolean systemIsStatic = originalSystemWallpaper != null && mImageWallpaper.equals( - originalSystemWallpaper.wallpaperComponent); + originalSystemWallpaper.getComponent()); final boolean systemIsBoth = mLockWallpaperMap.get(userId) == null; /* If we're setting system but not lock, and lock is currently sharing the system @@ -3190,7 +3189,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub throw new IllegalStateException("Wallpaper not yet initialized for user " + userId); } final boolean systemIsStatic = mImageWallpaper.equals( - originalSystemWallpaper.wallpaperComponent); + originalSystemWallpaper.getComponent()); final boolean systemIsBoth = mLockWallpaperMap.get(userId) == null; if (which == FLAG_SYSTEM && systemIsBoth && systemIsStatic) { @@ -3212,7 +3211,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub liveSync = new WallpaperDestinationChangeHandler( newWallpaper); boolean same = changingToSame(name, newWallpaper.connection, - newWallpaper.wallpaperComponent); + newWallpaper.getComponent()); /* * If we have a shared system+lock wallpaper, and we reapply the same wallpaper @@ -3243,7 +3242,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } boolean lockBitmapCleared = false; - if (!mImageWallpaper.equals(newWallpaper.wallpaperComponent)) { + if (!mImageWallpaper.equals(newWallpaper.getComponent())) { clearWallpaperBitmaps(newWallpaper); lockBitmapCleared = newWallpaper.mWhich == FLAG_LOCK; } @@ -3324,7 +3323,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } // Has the component changed? if (!force && changingToSame(componentName, wallpaper.connection, - wallpaper.wallpaperComponent)) { + wallpaper.getComponent())) { try { if (DEBUG_LIVE) { Slog.v(TAG, "Changing to the same component, ignoring"); @@ -3461,7 +3460,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub return false; } maybeDetachLastWallpapers(wallpaper); - wallpaper.wallpaperComponent = componentName; + wallpaper.setComponent(componentName); wallpaper.connection = newConn; newConn.mReply = reply; updateCurrentWallpapers(wallpaper); @@ -3586,7 +3585,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } private void clearWallpaperComponentLocked(WallpaperData wallpaper) { - wallpaper.wallpaperComponent = null; + wallpaper.setComponent(null); detachWallpaperLocked(wallpaper); } @@ -3831,7 +3830,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub wallpaper.wallpaperId = makeWallpaperIdLocked(); // always bump id at restore wallpaper.allowBackup = true; // by definition if it was restored ComponentName componentName = - removeNextWallpaperComponent() ? wallpaper.wallpaperComponent + removeNextWallpaperComponent() ? wallpaper.getComponent() : wallpaper.nextWallpaperComponent; if (componentName != null && !componentName.equals(mImageWallpaper)) { wallpaper.mBindSource = BindSource.RESTORE_SETTINGS_LIVE_SUCCESS; @@ -3907,7 +3906,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (multiCrop()) pw.print(" mCropHints="); pw.println(wallpaper.mCropHints); pw.print(" mName="); pw.println(wallpaper.name); pw.print(" mAllowBackup="); pw.println(wallpaper.allowBackup); - pw.print(" mWallpaperComponent="); pw.println(wallpaper.wallpaperComponent); + pw.print(" mWallpaperComponent="); pw.println(wallpaper.getComponent()); pw.print(" mWallpaperDimAmount="); pw.println(wallpaper.mWallpaperDimAmount); pw.print(" isColorExtracted="); pw.println(wallpaper.mIsColorExtractedFromDim); pw.println(" mUidToDimAmount:"); diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 3d5b2732e948..6009b4a70e3e 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -6218,7 +6218,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { public void onProcessRemoved(String name, int uid) { synchronized (mGlobalLockWithoutBoost) { final WindowProcessController proc = mProcessNames.remove(name, uid); - if (proc != null && !mStartingProcessActivities.isEmpty()) { + if (proc != null && !proc.mHasEverAttached + && !mStartingProcessActivities.isEmpty()) { // Use a copy in case finishIfPossible changes the list indirectly. final ArrayList<ActivityRecord> activities = new ArrayList<>(mStartingProcessActivities); diff --git a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java index cc6904f9b3af..156d8a065b67 100644 --- a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java +++ b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java @@ -103,7 +103,7 @@ public final class DesktopModeBoundsCalculator { final TaskDisplayArea displayArea = task.getDisplayArea(); final Rect screenBounds = displayArea.getBounds(); final Size idealSize = calculateIdealSize(screenBounds, DESKTOP_MODE_INITIAL_BOUNDS_SCALE); - if (!DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(activity.mWmService.mContext)) { + if (!DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()) { return centerInScreen(idealSize, screenBounds); } if (activity.mAppCompatController.getAppCompatAspectRatioOverrides() diff --git a/services/core/java/com/android/server/wm/DesktopModeHelper.java b/services/core/java/com/android/server/wm/DesktopModeHelper.java index 61fbb96882ec..da7631723185 100644 --- a/services/core/java/com/android/server/wm/DesktopModeHelper.java +++ b/services/core/java/com/android/server/wm/DesktopModeHelper.java @@ -35,8 +35,8 @@ public final class DesktopModeHelper { "persist.wm.debug.desktop_mode_enforce_device_restrictions", true); /** Whether desktop mode is enabled. */ - static boolean isDesktopModeEnabled(@NonNull Context context) { - return DesktopModeFlags.DESKTOP_WINDOWING_MODE.isEnabled(context); + static boolean isDesktopModeEnabled() { + return DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODE.isEnabled(); } /** @@ -60,7 +60,7 @@ public final class DesktopModeHelper { * Return {@code true} if desktop mode can be entered on the current device. */ static boolean canEnterDesktopMode(@NonNull Context context) { - return isDesktopModeEnabled(context) + return isDesktopModeEnabled() && (!shouldEnforceDeviceRestrictions() || isDesktopModeSupported(context)); } } diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 8f5612c61e1c..84072e26761a 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -1841,6 +1841,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> } boolean attachApplication(WindowProcessController app) throws RemoteException { + app.mHasEverAttached = true; final ArrayList<ActivityRecord> activities = mService.mStartingProcessActivities; RemoteException remoteException = null; boolean hasActivityStarted = false; diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 9d4652957487..7c3f0f22608e 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -35,6 +35,7 @@ import static android.view.SurfaceControl.Transaction; import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.window.TaskFragmentAnimationParams.DEFAULT_ANIMATION_BACKGROUND_COLOR; +import static android.window.flags.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS; @@ -115,7 +116,6 @@ import com.android.server.wm.SurfaceAnimator.Animatable; import com.android.server.wm.SurfaceAnimator.AnimationType; import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; import com.android.server.wm.utils.AlwaysTruePredicate; -import com.android.window.flags.Flags; import java.io.PrintWriter; import java.lang.ref.WeakReference; @@ -457,7 +457,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< source.setFrame(provider.getArbitraryRectangle()) .updateSideHint(getBounds()) .setBoundingRects(provider.getBoundingRects()); - if (Flags.enableCaptionCompatInsetForceConsumption()) { + if (ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION.isEnabled()) { source.setFlags(provider.getFlags()); } mLocalInsetsSources.put(id, source); diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java index 30d6f0a46bae..32fe303b9e90 100644 --- a/services/core/java/com/android/server/wm/WindowProcessController.java +++ b/services/core/java/com/android/server/wm/WindowProcessController.java @@ -204,6 +204,9 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio // Set to true when process was launched with a wrapper attached private volatile boolean mUsingWrapper; + /** Whether this process has ever completed ActivityThread#handleBindApplication. */ + boolean mHasEverAttached; + /** Non-null if this process may have a window. */ @Nullable Session mWindowSession; diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 8c4448e7915f..155e73c53819 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -1808,9 +1808,30 @@ void NativeInputManager::loadAdditionalMouseResources( ATRACE_CALL(); JNIEnv* env = jniEnv(); - for (int32_t iconId = static_cast<int32_t>(PointerIconStyle::TYPE_CONTEXT_MENU); - iconId <= static_cast<int32_t>(PointerIconStyle::TYPE_HANDWRITING); ++iconId) { - const PointerIconStyle pointerIconStyle = static_cast<PointerIconStyle>(iconId); + constexpr static std::array ADDITIONAL_STYLES{PointerIconStyle::TYPE_CONTEXT_MENU, + PointerIconStyle::TYPE_HAND, + PointerIconStyle::TYPE_HELP, + PointerIconStyle::TYPE_WAIT, + PointerIconStyle::TYPE_CELL, + PointerIconStyle::TYPE_CROSSHAIR, + PointerIconStyle::TYPE_TEXT, + PointerIconStyle::TYPE_VERTICAL_TEXT, + PointerIconStyle::TYPE_ALIAS, + PointerIconStyle::TYPE_COPY, + PointerIconStyle::TYPE_NO_DROP, + PointerIconStyle::TYPE_ALL_SCROLL, + PointerIconStyle::TYPE_HORIZONTAL_DOUBLE_ARROW, + PointerIconStyle::TYPE_VERTICAL_DOUBLE_ARROW, + PointerIconStyle::TYPE_TOP_RIGHT_DOUBLE_ARROW, + PointerIconStyle::TYPE_TOP_LEFT_DOUBLE_ARROW, + PointerIconStyle::TYPE_ZOOM_IN, + PointerIconStyle::TYPE_ZOOM_OUT, + PointerIconStyle::TYPE_GRAB, + PointerIconStyle::TYPE_GRABBING, + PointerIconStyle::TYPE_HANDWRITING, + PointerIconStyle::TYPE_SPOT_HOVER}; + + for (const auto pointerIconStyle : ADDITIONAL_STYLES) { PointerIcon pointerIcon = loadPointerIcon(env, displayId, pointerIconStyle); (*outResources)[pointerIconStyle] = toSpriteIcon(pointerIcon); if (!pointerIcon.bitmapFrames.empty()) { diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index ab459df1cdf6..9fdf088c3d1d 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -107,7 +107,7 @@ import com.android.internal.os.BinderInternal; import com.android.internal.os.RuntimeInit; import com.android.internal.policy.AttributeCache; import com.android.internal.protolog.ProtoLog; -import com.android.internal.protolog.ProtoLogConfigurationService; +import com.android.internal.protolog.ProtoLogConfigurationServiceImpl; import com.android.internal.protolog.ProtoLogGroup; import com.android.internal.util.ConcurrentUtils; import com.android.internal.util.EmergencyAffordanceManager; @@ -1097,7 +1097,7 @@ public final class SystemServer implements Dumpable { if (android.tracing.Flags.clientSideProtoLogging()) { t.traceBegin("StartProtoLogConfigurationService"); ServiceManager.addService( - Context.PROTOLOG_CONFIGURATION_SERVICE, new ProtoLogConfigurationService()); + Context.PROTOLOG_CONFIGURATION_SERVICE, new ProtoLogConfigurationServiceImpl()); t.traceEnd(); } diff --git a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java index 8b653378664e..32135f1cb7fa 100644 --- a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java @@ -22,9 +22,10 @@ import static android.permission.flags.Flags.FLAG_SENSITIVE_NOTIFICATION_APP_PRO import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doCallRealMethod; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.never; @@ -36,6 +37,7 @@ import android.content.pm.PackageManagerInternal; import android.media.projection.MediaProjectionInfo; import android.media.projection.MediaProjectionManager; import android.os.Process; +import android.os.RemoteException; import android.platform.test.annotations.RequiresFlagsDisabled; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; @@ -137,9 +139,17 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { mSensitiveContentProtectionManagerService.mNotificationListener = spy(mSensitiveContentProtectionManagerService.mNotificationListener); - doCallRealMethod() - .when(mSensitiveContentProtectionManagerService.mNotificationListener) - .onListenerConnected(); + + // Unexpected NLS interactions when registered cause test flakes. For purposes of this test, + // the test will control any NLS calls. + try { + doNothing().when(mSensitiveContentProtectionManagerService.mNotificationListener) + .registerAsSystemService(any(), any(), anyInt()); + doNothing().when(mSensitiveContentProtectionManagerService.mNotificationListener) + .unregisterAsSystemService(); + } catch (RemoteException e) { + // Intra-process call, should never happen. + } // Setup RankingMap and two possilbe rankings when(mSensitiveRanking.hasSensitiveContent()).thenReturn(true); diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java index 15ae4634b573..0b762df86df9 100644 --- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java @@ -290,7 +290,7 @@ public class WallpaperManagerServiceTests { final WallpaperData fallbackData = mService.mFallbackWallpaper; assertEquals("Fallback wallpaper component should be ImageWallpaper.", - sImageWallpaperComponentName, fallbackData.wallpaperComponent); + sImageWallpaperComponentName, fallbackData.getComponent()); verifyLastWallpaperData(USER_SYSTEM, sDefaultWallpaperComponent); verifyDisplayData(); @@ -580,7 +580,7 @@ public class WallpaperManagerServiceTests { final WallpaperData lastData = mService.mLastWallpaper; assertNotNull("Last wallpaper must not be null", lastData); assertEquals("Last wallpaper component must be equals.", expectedComponent, - lastData.wallpaperComponent); + lastData.getComponent()); assertEquals("The user id in last wallpaper should be the last switched user", lastUserId, lastData.userId); assertNotNull("Must exist user data connection on last wallpaper data", diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java index a25621a8975f..390eb937fe25 100644 --- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java @@ -61,7 +61,6 @@ import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.doNothing; @@ -95,7 +94,6 @@ import android.os.Looper; import android.os.Message; import android.os.PowerManagerInternal; import android.os.RemoteException; -import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; import android.os.storage.IStorageManager; @@ -214,10 +212,7 @@ public class UserControllerTest { doNothing().when(mInjector).activityManagerOnUserStopped(anyInt()); doNothing().when(mInjector).clearBroadcastQueueForUser(anyInt()); doNothing().when(mInjector).taskSupervisorRemoveUser(anyInt()); - doAnswer(invocation -> { - ((Runnable) invocation.getArgument(0)).run(); - return null; - }).when(mInjector).showKeyguard(any()); + doNothing().when(mInjector).lockDeviceNowAndWaitForKeyguardShown(); mockIsUsersOnSecondaryDisplaysEnabled(false); // All UserController params are set to default. @@ -432,6 +427,7 @@ public class UserControllerTest { mUserController.registerUserSwitchObserver(observer, "mock"); // Start user -- this will update state of mUserController mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND); + verify(observer, times(1)).onBeforeUserSwitching(eq(TEST_USER_ID)); Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG); assertNotNull(reportMsg); UserState userState = (UserState) reportMsg.obj; @@ -440,7 +436,6 @@ public class UserControllerTest { // Call dispatchUserSwitch and verify that observer was called only once mInjector.mHandler.clearAllRecordedMessages(); mUserController.dispatchUserSwitch(userState, oldUserId, newUserId); - verify(observer, times(1)).onBeforeUserSwitching(eq(TEST_USER_ID)); verify(observer, times(1)).onUserSwitching(eq(TEST_USER_ID), any()); Set<Integer> expectedCodes = Collections.singleton(CONTINUE_USER_SWITCH_MSG); Set<Integer> actualCodes = mInjector.mHandler.getMessageCodes(); @@ -463,6 +458,7 @@ public class UserControllerTest { mUserController.registerUserSwitchObserver(observer, "mock"); // Start user -- this will update state of mUserController mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND); + verify(observer, times(1)).onBeforeUserSwitching(eq(TEST_USER_ID)); Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG); assertNotNull(reportMsg); UserState userState = (UserState) reportMsg.obj; @@ -471,7 +467,6 @@ public class UserControllerTest { // Call dispatchUserSwitch and verify that observer was called only once mInjector.mHandler.clearAllRecordedMessages(); mUserController.dispatchUserSwitch(userState, oldUserId, newUserId); - verify(observer, times(1)).onBeforeUserSwitching(eq(TEST_USER_ID)); verify(observer, times(1)).onUserSwitching(eq(TEST_USER_ID), any()); // Verify that CONTINUE_USER_SWITCH_MSG is not sent (triggers timeout) Set<Integer> actualCodes = mInjector.mHandler.getMessageCodes(); @@ -554,6 +549,7 @@ public class UserControllerTest { expectedCodes.add(REPORT_USER_SWITCH_COMPLETE_MSG); if (backgroundUserStopping) { expectedCodes.add(CLEAR_USER_JOURNEY_SESSION_MSG); + expectedCodes.add(0); // this is for directly posting in stopping. } if (expectScheduleBackgroundUserStopping) { expectedCodes.add(SCHEDULED_STOP_BACKGROUND_USER_MSG); @@ -1579,13 +1575,21 @@ public class UserControllerTest { // mock the device to be secure in order to expect the keyguard to be shown when(mInjector.mKeyguardManagerMock.isDeviceSecure(anyInt())).thenReturn(true); - // call real showKeyguard method for this test - doCallRealMethod().when(mInjector).showKeyguard(any()); + // call real lockDeviceNowAndWaitForKeyguardShown method for this test + doCallRealMethod().when(mInjector).lockDeviceNowAndWaitForKeyguardShown(); - mUserController.completeUserSwitch(TEST_USER_ID1, TEST_USER_ID2); + // call startUser on a thread because we're expecting it to be blocked + Thread threadStartUser = new Thread(()-> { + mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND); + }); + threadStartUser.start(); - // make sure the switch is stalled by checking the UserSwitchingDialog is not dismissed yet - verify(mInjector, never()).dismissUserSwitchingDialog(any()); + // make sure the switch is stalled... + Thread.sleep(2000); + // by checking REPORT_USER_SWITCH_MSG is not sent yet + assertNull(mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG)); + // and the thread is still alive + assertTrue(threadStartUser.isAlive()); // mock send the keyguard shown event ArgumentCaptor<ActivityTaskManagerInternal.ScreenObserver> captor = ArgumentCaptor.forClass( @@ -1593,42 +1597,12 @@ public class UserControllerTest { verify(mInjector.mActivityTaskManagerInternal).registerScreenObserver(captor.capture()); captor.getValue().onKeyguardStateChanged(true); - // verify the switch now moves on by checking the UserSwitchingDialog is dismissed - verify(mInjector, atLeastOnce()).dismissUserSwitchingDialog(any()); - - // verify that SHOW_KEYGUARD_TIMEOUT is ignored and does not crash the system - try { - mInjector.mHandler.processPostDelayedCallbacksWithin( - UserController.SHOW_KEYGUARD_TIMEOUT_MS); - } catch (RuntimeException e) { - throw new AssertionError( - "SHOW_KEYGUARD_TIMEOUT is not ignored and crashed the system", e); - } - } - - @Test - public void testRuntimeExceptionIsThrownIfTheKeyguardIsNotShown() throws Exception { - // enable user switch ui, because keyguard is only shown then - mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true, - /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false, - /* backgroundUserScheduledStopTimeSecs= */ -1); - - // mock the device to be secure in order to expect the keyguard to be shown - when(mInjector.mKeyguardManagerMock.isDeviceSecure(anyInt())).thenReturn(true); - - // suppress showKeyguard method for this test - doNothing().when(mInjector).showKeyguard(any()); - - mUserController.completeUserSwitch(TEST_USER_ID1, TEST_USER_ID2); - - // verify that the system has crashed - assertThrows("Should have thrown RuntimeException", RuntimeException.class, () -> { - mInjector.mHandler.processPostDelayedCallbacksWithin( - UserController.SHOW_KEYGUARD_TIMEOUT_MS); - }); - - // make sure the UserSwitchingDialog is not dismissed - verify(mInjector, never()).dismissUserSwitchingDialog(any()); + // verify the switch now moves on... + Thread.sleep(1000); + // by checking REPORT_USER_SWITCH_MSG is sent + assertNotNull(mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG)); + // and the thread is finished + assertFalse(threadStartUser.isAlive()); } private void setUpAndStartUserInBackground(int userId) throws Exception { @@ -1989,9 +1963,7 @@ public class UserControllerTest { Set<Integer> getMessageCodes() { Set<Integer> result = new LinkedHashSet<>(); for (Message msg : mMessages) { - if (msg.what != 0) { // ignore mHandle.post and mHandler.postDelayed messages - result.add(msg.what); - } + result.add(msg.what); } return result; } @@ -2015,28 +1987,14 @@ public class UserControllerTest { @Override public boolean sendMessageAtTime(Message msg, long uptimeMillis) { - final Runnable cb = msg.getCallback(); - if (cb != null && uptimeMillis <= SystemClock.uptimeMillis()) { - // run mHandler.post calls immediately - cb.run(); - return true; - } Message copy = new Message(); copy.copyFrom(msg); - copy.setCallback(cb); mMessages.add(copy); - return super.sendMessageAtTime(msg, uptimeMillis); - } - - public void processPostDelayedCallbacksWithin(long millis) { - final long whenMax = SystemClock.uptimeMillis() + millis; - for (Message msg : mMessages) { - final Runnable cb = msg.getCallback(); - if (cb != null && msg.getWhen() <= whenMax) { - msg.setCallback(null); - cb.run(); - } + if (msg.getCallback() != null) { + msg.getCallback().run(); + msg.setCallback(null); } + return super.sendMessageAtTime(msg, uptimeMillis); } } } diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java index 689b241f0faa..abc9ce3fdc36 100644 --- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java @@ -50,11 +50,12 @@ import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import static org.testng.Assert.assertThrows; +import android.annotation.SuppressLint; import android.app.ActivityManagerInternal; import android.app.ActivityOptions.LaunchCookie; +import android.app.AppOpsManager; import android.app.KeyguardManager; import android.content.Context; -import android.content.ContextWrapper; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.ApplicationInfoFlags; @@ -72,6 +73,7 @@ import android.os.test.TestLooper; import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; import android.platform.test.flag.junit.SetFlagsRule; +import android.testing.TestableContext; import android.view.ContentRecordingSession; import android.view.ContentRecordingSession.RecordContent; @@ -99,13 +101,14 @@ import java.util.concurrent.TimeUnit; /** * Tests for the {@link MediaProjectionManagerService} class. - * + * <p> * Build/Install/Run: * atest FrameworksServicesTests:MediaProjectionManagerServiceTest */ @SmallTest @Presubmit @RunWith(AndroidJUnit4.class) +@SuppressLint({"UseCheckPermission", "VisibleForTests", "MissingPermission"}) public class MediaProjectionManagerServiceTest { private static final int UID = 10; private static final String PACKAGE_NAME = "test.package"; @@ -151,7 +154,10 @@ public class MediaProjectionManagerServiceTest { } }; - private Context mContext; + @Rule + public final TestableContext mContext = spy( + new TestableContext(InstrumentationRegistry.getInstrumentation().getContext())); + private MediaProjectionManagerService mService; private OffsettableClock mClock; private ContentRecordingSession mWaitingDisplaySession = @@ -169,6 +175,8 @@ public class MediaProjectionManagerServiceTest { @Mock private KeyguardManager mKeyguardManager; @Mock + AppOpsManager mAppOpsManager; + @Mock private IMediaProjectionWatcherCallback mWatcherCallback; @Mock private MediaProjectionMetricsLogger mMediaProjectionMetricsLogger; @@ -185,10 +193,9 @@ public class MediaProjectionManagerServiceTest { LocalServices.removeServiceForTest(WindowManagerInternal.class); LocalServices.addService(WindowManagerInternal.class, mWindowManagerInternal); - mContext = spy(new ContextWrapper( - InstrumentationRegistry.getInstrumentation().getTargetContext())); - doReturn(mPackageManager).when(mContext).getPackageManager(); - doReturn(mKeyguardManager).when(mContext).getSystemService(eq(Context.KEYGUARD_SERVICE)); + mContext.addMockSystemService(AppOpsManager.class, mAppOpsManager); + mContext.addMockSystemService(KeyguardManager.class, mKeyguardManager); + mContext.setMockPackageManager(mPackageManager); mClock = new OffsettableClock.Stopped(); mWaitingDisplaySession.setWaitingForConsent(true); @@ -291,6 +298,27 @@ public class MediaProjectionManagerServiceTest { assertThat(mService.getActiveProjectionInfo()).isNotNull(); } + @SuppressLint("MissingPermission") + @EnableFlags(android.companion.virtualdevice.flags + .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS) + @Test + public void testCreateProjection_keyguardLocked_AppOpMediaProjection() + throws NameNotFoundException { + MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions(); + doReturn(true).when(mAppOpsManager).isOperationActive(eq(AppOpsManager.OP_PROJECT_MEDIA), + eq(projection.uid), eq(projection.packageName)); + doReturn(true).when(mKeyguardManager).isKeyguardLocked(); + + doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager).checkPermission( + RECORD_SENSITIVE_CONTENT, projection.packageName); + + projection.start(mIMediaProjectionCallback); + projection.notifyVirtualDisplayCreated(10); + + // The projection was started because it was allowed to capture the keyguard. + assertThat(mService.getActiveProjectionInfo()).isNotNull(); + } + @Test public void testCreateProjection_attemptReuse_noPriorProjectionGrant() throws NameNotFoundException { diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java index 957b5e04fef6..ae0c6e551246 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java @@ -331,6 +331,7 @@ public class RootWindowContainerTests extends WindowTestsBase { final WindowProcessController proc = mSystemServicesTestRule.addProcess( activity.packageName, activity.processName, 6789 /* pid */, activity.info.applicationInfo.uid); + assertFalse(proc.mHasEverAttached); try { mRootWindowContainer.attachApplication(proc); verify(mSupervisor).realStartActivityLocked(eq(topActivity), eq(proc), @@ -338,6 +339,15 @@ public class RootWindowContainerTests extends WindowTestsBase { } catch (RemoteException e) { e.rethrowAsRuntimeException(); } + + // Verify that onProcessRemoved won't clear the launching activities if an attached process + // is died. Because in real case, it should be handled from WindowProcessController's + // and ActivityRecord's handleAppDied to decide whether to remove the activities. + assertTrue(proc.mHasEverAttached); + assertTrue(mAtm.mStartingProcessActivities.isEmpty()); + mAtm.mStartingProcessActivities.add(activity); + mAtm.mInternal.onProcessRemoved(proc.mName, proc.mUid); + assertFalse(mAtm.mStartingProcessActivities.isEmpty()); } /** diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 41223db750c0..2ef057350033 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -5038,7 +5038,7 @@ public class CarrierConfigManager { * {@code true} - Enable NI SUPL message injection. */ @FlaggedApi(android.location.flags.Flags - .FLAG_ENABLE_NI_SUPL_MESSAGE_INJECTION_BY_CARRIER_CONFIG) + .FLAG_ENABLE_NI_SUPL_MESSAGE_INJECTION_BY_CARRIER_CONFIG_BUGFIX) public static final String KEY_ENABLE_NI_SUPL_MESSAGE_INJECTION_BOOL = KEY_PREFIX + "enable_ni_supl_message_injection_bool"; @@ -5059,7 +5059,7 @@ public class CarrierConfigManager { defaults.putInt(KEY_ES_SUPL_CONTROL_PLANE_SUPPORT_INT, SUPL_EMERGENCY_MODE_TYPE_CP_ONLY); defaults.putStringArray(KEY_ES_SUPL_DATA_PLANE_ONLY_ROAMING_PLMN_STRING_ARRAY, null); - if (android.location.flags.Flags.enableNiSuplMessageInjectionByCarrierConfig()) { + if (android.location.flags.Flags.enableNiSuplMessageInjectionByCarrierConfigBugfix()) { defaults.putBoolean(KEY_ENABLE_NI_SUPL_MESSAGE_INJECTION_BOOL, false); } return defaults; diff --git a/telephony/java/android/telephony/satellite/ISatelliteModemStateCallback.aidl b/telephony/java/android/telephony/satellite/ISatelliteModemStateCallback.aidl index 66a20ae28f39..50e3a0e4a79d 100644 --- a/telephony/java/android/telephony/satellite/ISatelliteModemStateCallback.aidl +++ b/telephony/java/android/telephony/satellite/ISatelliteModemStateCallback.aidl @@ -34,4 +34,12 @@ oneway interface ISatelliteModemStateCallback { * @param isEmergency True means satellite enabled for emergency mode, false otherwise. */ void onEmergencyModeChanged(in boolean isEmergency); + + /** + * Indicates that the satellite registration failed with following failure code + * + * @param causeCode the primary failure cause code of the procedure. + * For LTE (EMM), cause codes are TS 24.301 Sec 9.9.3.9 + */ + void onRegistrationFailure(in int causeCode); } diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java index 90dae3be058c..4eefaaca71f4 100644 --- a/telephony/java/android/telephony/satellite/SatelliteManager.java +++ b/telephony/java/android/telephony/satellite/SatelliteManager.java @@ -19,6 +19,7 @@ package android.telephony.satellite; import android.Manifest; import android.annotation.CallbackExecutor; import android.annotation.FlaggedApi; +import android.annotation.Hide; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -1579,6 +1580,13 @@ public final class SatelliteManager { executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onEmergencyModeChanged(isEmergency))); } + + @Hide + @Override + public void onRegistrationFailure(int causeCode) { + executor.execute(() -> Binder.withCleanCallingIdentity(() -> + callback.onRegistrationFailure(causeCode))); + } }; sSatelliteModemStateCallbackMap.put(callback, internalCallback); return telephony.registerForSatelliteModemStateChanged(internalCallback); diff --git a/telephony/java/android/telephony/satellite/SatelliteModemStateCallback.java b/telephony/java/android/telephony/satellite/SatelliteModemStateCallback.java index 423a7859dd6b..13af4694389b 100644 --- a/telephony/java/android/telephony/satellite/SatelliteModemStateCallback.java +++ b/telephony/java/android/telephony/satellite/SatelliteModemStateCallback.java @@ -45,4 +45,13 @@ public interface SatelliteModemStateCallback { */ @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN) default void onEmergencyModeChanged(boolean isEmergency) {}; + + /** + * Indicates that the satellite registration failed with following failure code + * + * @param causeCode the primary failure cause code of the procedure. + * For LTE (EMM), cause codes are TS 24.301 Sec 9.9.3.9 + * @hide + */ + default void onRegistrationFailure(int causeCode) {}; } diff --git a/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java index eac2e9282714..b3a998ebca0d 100644 --- a/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java +++ b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java @@ -16,6 +16,7 @@ package com.android.server.input.debug; +import static android.view.InputDevice.SOURCE_MOUSE; import static android.view.InputDevice.SOURCE_TOUCHSCREEN; import static org.junit.Assert.assertEquals; @@ -92,7 +93,7 @@ public class TouchpadDebugViewTest { InputDevice inputDevice = new InputDevice.Builder() .setId(TOUCHPAD_DEVICE_ID) - .setSources(InputDevice.SOURCE_TOUCHPAD | InputDevice.SOURCE_MOUSE) + .setSources(InputDevice.SOURCE_TOUCHPAD | SOURCE_MOUSE) .setName("Test Device " + TOUCHPAD_DEVICE_ID) .build(); @@ -354,4 +355,43 @@ public class TouchpadDebugViewTest { mTouchpadDebugView.updateGestureInfo(gestureType, TOUCHPAD_DEVICE_ID); assertEquals(child.getText().toString(), TouchpadDebugView.getGestureText(gestureType)); } -} + + @Test + public void testTwoFingerDrag() { + float offsetX = ViewConfiguration.get(mTestableContext).getScaledTouchSlop() + 10; + float offsetY = ViewConfiguration.get(mTestableContext).getScaledTouchSlop() + 10; + + // Simulate ACTION_DOWN event (gesture starts). + MotionEvent actionDown = new MotionEventBuilder(MotionEvent.ACTION_DOWN, SOURCE_MOUSE) + .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER) + .x(40f) + .y(40f) + ) + .classification(MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE) + .build(); + mTouchpadDebugView.dispatchTouchEvent(actionDown); + + // Simulate ACTION_MOVE event (dragging with two fingers, processed as one pointer). + MotionEvent actionMove = new MotionEventBuilder(MotionEvent.ACTION_MOVE, SOURCE_MOUSE) + .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER) + .x(40f + offsetX) + .y(40f + offsetY) + ) + .classification(MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE) + .build(); + mTouchpadDebugView.dispatchTouchEvent(actionMove); + + // Simulate ACTION_UP event (gesture ends). + MotionEvent actionUp = new MotionEventBuilder(MotionEvent.ACTION_UP, SOURCE_MOUSE) + .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER) + .x(40f + offsetX) + .y(40f + offsetY) + ) + .classification(MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE) + .build(); + mTouchpadDebugView.dispatchTouchEvent(actionUp); + + // Verify that no updateViewLayout is called (as expected for a two-finger drag gesture). + verify(mWindowManager, times(0)).updateViewLayout(any(), any()); + } +}
\ No newline at end of file diff --git a/tests/Tracing/Android.bp b/tests/Tracing/Android.bp index 5a7f12f56655..90998e67ae31 100644 --- a/tests/Tracing/Android.bp +++ b/tests/Tracing/Android.bp @@ -15,7 +15,7 @@ android_test { }, // Include some source files directly to be able to access package members srcs: ["src/**/*.java"], - libs: ["android.test.runner"], + libs: ["android.test.runner.stubs.system"], static_libs: [ "junit", "androidx.test.rules", diff --git a/tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java b/tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java index e841d9ea0880..cfb2645dee0d 100644 --- a/tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java +++ b/tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java @@ -16,6 +16,8 @@ package com.android.internal.protolog; +import static android.tools.traces.Utils.executeShellCommand; + import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; @@ -42,12 +44,13 @@ import android.util.proto.ProtoInputStream; import androidx.test.platform.app.InstrumentationRegistry; -import com.android.internal.protolog.ProtoLogConfigurationService.ViewerConfigFileTracer; +import com.android.internal.protolog.ProtoLogConfigurationServiceImpl.ViewerConfigFileTracer; import com.android.internal.protolog.common.IProtoLogGroup; import com.android.internal.protolog.common.LogDataType; import com.android.internal.protolog.common.LogLevel; import com.google.common.truth.Truth; +import com.google.protobuf.InvalidProtocolBufferException; import org.junit.After; import org.junit.Before; @@ -57,12 +60,14 @@ import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.mockito.Mockito; +import perfetto.protos.PerfettoConfig.TracingServiceState; import perfetto.protos.Protolog; import perfetto.protos.ProtologCommon; import java.io.File; import java.io.IOException; import java.util.List; +import java.util.Optional; import java.util.Random; import java.util.concurrent.atomic.AtomicInteger; @@ -166,7 +171,8 @@ public class PerfettoProtoLogImplTest { return new ProtoInputStream(sViewerConfigBuilder.build().toByteArray()); }); }; - sProtoLogConfigurationService = new ProtoLogConfigurationService(dataSourceBuilder, tracer); + sProtoLogConfigurationService = + new ProtoLogConfigurationServiceImpl(dataSourceBuilder, tracer); if (android.tracing.Flags.clientSideProtoLogging()) { sProtoLog = new PerfettoProtoLogImpl( @@ -177,6 +183,8 @@ public class PerfettoProtoLogImplTest { viewerConfigInputStreamProvider, sReader, () -> sCacheUpdater.run(), TestProtoLogGroup.values(), dataSourceBuilder, sProtoLogConfigurationService); } + + waitDataSourceIsAvailable(); } @Before @@ -862,6 +870,54 @@ public class PerfettoProtoLogImplTest { .isEqualTo("This message should also be logged 567"); } + private static void waitDataSourceIsAvailable() { + final int timeoutMs = 10000; + final int busyWaitIntervalMs = 100; + + int elapsedMs = 0; + + while (!isDataSourceAvailable()) { + SystemClock.sleep(busyWaitIntervalMs); + elapsedMs += busyWaitIntervalMs; + if (elapsedMs >= timeoutMs) { + throw new RuntimeException("Data source didn't become available." + + " Waited for: " + timeoutMs + " ms"); + } + } + } + + private static boolean isDataSourceAvailable() { + byte[] proto = executeShellCommand("perfetto --query-raw"); + + try { + TracingServiceState state = TracingServiceState.parseFrom(proto); + + Optional<Integer> producerId = Optional.empty(); + + for (TracingServiceState.Producer producer : state.getProducersList()) { + if (producer.getPid() == android.os.Process.myPid()) { + producerId = Optional.of(producer.getId()); + break; + } + } + + if (producerId.isEmpty()) { + return false; + } + + for (TracingServiceState.DataSource ds : state.getDataSourcesList()) { + if (ds.getDsDescriptor().getName().equals(TEST_PROTOLOG_DATASOURCE_NAME) + && ds.getProducerId() == producerId.get()) { + return true; + } + } + } catch (InvalidProtocolBufferException e) { + throw new RuntimeException(e); + } + + return false; + } + private enum TestProtoLogGroup implements IProtoLogGroup { TEST_GROUP(true, true, false, "TEST_TAG"); diff --git a/tests/Tracing/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java b/tests/Tracing/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java index e1bdd777dc5f..a3d03a8278ed 100644 --- a/tests/Tracing/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java +++ b/tests/Tracing/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java @@ -150,11 +150,11 @@ public class ProtoLogConfigurationServiceTest { @Test public void canRegisterClientWithGroupsOnly() throws RemoteException { - final ProtoLogConfigurationService service = new ProtoLogConfigurationService(); + final ProtoLogConfigurationService service = new ProtoLogConfigurationServiceImpl(); - final ProtoLogConfigurationService.RegisterClientArgs args = - new ProtoLogConfigurationService.RegisterClientArgs() - .setGroups(new ProtoLogConfigurationService.RegisterClientArgs + final ProtoLogConfigurationServiceImpl.RegisterClientArgs args = + new ProtoLogConfigurationServiceImpl.RegisterClientArgs() + .setGroups(new ProtoLogConfigurationServiceImpl.RegisterClientArgs .GroupConfig(TEST_GROUP, true)); service.registerClient(mMockClient, args); @@ -165,11 +165,11 @@ public class ProtoLogConfigurationServiceTest { @Test public void willDumpViewerConfigOnlyOnceOnTraceStop() throws RemoteException, InvalidProtocolBufferException { - final ProtoLogConfigurationService service = new ProtoLogConfigurationService(); + final ProtoLogConfigurationService service = new ProtoLogConfigurationServiceImpl(); - final ProtoLogConfigurationService.RegisterClientArgs args = - new ProtoLogConfigurationService.RegisterClientArgs() - .setGroups(new ProtoLogConfigurationService.RegisterClientArgs + final ProtoLogConfigurationServiceImpl.RegisterClientArgs args = + new ProtoLogConfigurationServiceImpl.RegisterClientArgs() + .setGroups(new ProtoLogConfigurationServiceImpl.RegisterClientArgs .GroupConfig(TEST_GROUP, true)) .setViewerConfigFile(mViewerConfigFile.getAbsolutePath()); service.registerClient(mMockClient, args); @@ -200,13 +200,13 @@ public class ProtoLogConfigurationServiceTest { @Test public void willDumpViewerConfigOnLastClientDisconnected() throws RemoteException, FileNotFoundException { - final ProtoLogConfigurationService.ViewerConfigFileTracer tracer = - Mockito.mock(ProtoLogConfigurationService.ViewerConfigFileTracer.class); - final ProtoLogConfigurationService service = new ProtoLogConfigurationService(tracer); + final ProtoLogConfigurationServiceImpl.ViewerConfigFileTracer tracer = + Mockito.mock(ProtoLogConfigurationServiceImpl.ViewerConfigFileTracer.class); + final ProtoLogConfigurationService service = new ProtoLogConfigurationServiceImpl(tracer); - final ProtoLogConfigurationService.RegisterClientArgs args = - new ProtoLogConfigurationService.RegisterClientArgs() - .setGroups(new ProtoLogConfigurationService.RegisterClientArgs + final ProtoLogConfigurationServiceImpl.RegisterClientArgs args = + new ProtoLogConfigurationServiceImpl.RegisterClientArgs() + .setGroups(new ProtoLogConfigurationServiceImpl.RegisterClientArgs .GroupConfig(TEST_GROUP, true)) .setViewerConfigFile(mViewerConfigFile.getAbsolutePath()); service.registerClient(mMockClient, args); @@ -225,10 +225,10 @@ public class ProtoLogConfigurationServiceTest { @Test public void sendEnableLoggingToLogcatToClient() throws RemoteException { - final var service = new ProtoLogConfigurationService(); + final var service = new ProtoLogConfigurationServiceImpl(); - final var args = new ProtoLogConfigurationService.RegisterClientArgs() - .setGroups(new ProtoLogConfigurationService.RegisterClientArgs + final var args = new ProtoLogConfigurationServiceImpl.RegisterClientArgs() + .setGroups(new ProtoLogConfigurationServiceImpl.RegisterClientArgs .GroupConfig(TEST_GROUP, false)); service.registerClient(mMockClient, args); @@ -242,11 +242,11 @@ public class ProtoLogConfigurationServiceTest { @Test public void sendDisableLoggingToLogcatToClient() throws RemoteException { - final ProtoLogConfigurationService service = new ProtoLogConfigurationService(); + final ProtoLogConfigurationService service = new ProtoLogConfigurationServiceImpl(); - final ProtoLogConfigurationService.RegisterClientArgs args = - new ProtoLogConfigurationService.RegisterClientArgs() - .setGroups(new ProtoLogConfigurationService.RegisterClientArgs + final ProtoLogConfigurationServiceImpl.RegisterClientArgs args = + new ProtoLogConfigurationServiceImpl.RegisterClientArgs() + .setGroups(new ProtoLogConfigurationServiceImpl.RegisterClientArgs .GroupConfig(TEST_GROUP, true)); service.registerClient(mMockClient, args); @@ -260,11 +260,11 @@ public class ProtoLogConfigurationServiceTest { @Test public void doNotSendLoggingToLogcatToClientWithoutRegisteredGroup() throws RemoteException { - final ProtoLogConfigurationService service = new ProtoLogConfigurationService(); + final ProtoLogConfigurationService service = new ProtoLogConfigurationServiceImpl(); - final ProtoLogConfigurationService.RegisterClientArgs args = - new ProtoLogConfigurationService.RegisterClientArgs() - .setGroups(new ProtoLogConfigurationService.RegisterClientArgs + final ProtoLogConfigurationServiceImpl.RegisterClientArgs args = + new ProtoLogConfigurationServiceImpl.RegisterClientArgs() + .setGroups(new ProtoLogConfigurationServiceImpl.RegisterClientArgs .GroupConfig(TEST_GROUP, false)); service.registerClient(mMockClient, args); @@ -277,15 +277,15 @@ public class ProtoLogConfigurationServiceTest { @Test public void handlesToggleToLogcatBeforeClientIsRegistered() throws RemoteException { - final ProtoLogConfigurationService service = new ProtoLogConfigurationService(); + final ProtoLogConfigurationService service = new ProtoLogConfigurationServiceImpl(); Truth.assertThat(service.getGroups()).asList().doesNotContain(TEST_GROUP); service.enableProtoLogToLogcat(TEST_GROUP); Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isTrue(); - final ProtoLogConfigurationService.RegisterClientArgs args = - new ProtoLogConfigurationService.RegisterClientArgs() - .setGroups(new ProtoLogConfigurationService.RegisterClientArgs + final ProtoLogConfigurationServiceImpl.RegisterClientArgs args = + new ProtoLogConfigurationServiceImpl.RegisterClientArgs() + .setGroups(new ProtoLogConfigurationServiceImpl.RegisterClientArgs .GroupConfig(TEST_GROUP, false)); service.registerClient(mMockClient, args); diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/AndroidGlobalIssueRegistry.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/AndroidGlobalIssueRegistry.kt index 290e7be9f6c4..94674348df08 100644 --- a/tools/lint/global/checks/src/main/java/com/google/android/lint/AndroidGlobalIssueRegistry.kt +++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/AndroidGlobalIssueRegistry.kt @@ -22,6 +22,7 @@ import com.android.tools.lint.detector.api.CURRENT_API import com.google.android.lint.aidl.EnforcePermissionDetector import com.google.android.lint.aidl.PermissionAnnotationDetector import com.google.android.lint.aidl.SimpleManualPermissionEnforcementDetector +import com.google.android.lint.aidl.SimpleRequiresNoPermissionDetector import com.google.auto.service.AutoService @AutoService(IssueRegistry::class) @@ -34,6 +35,7 @@ class AndroidGlobalIssueRegistry : IssueRegistry() { EnforcePermissionDetector.ISSUE_MISUSING_ENFORCE_PERMISSION, PermissionAnnotationDetector.ISSUE_MISSING_PERMISSION_ANNOTATION, SimpleManualPermissionEnforcementDetector.ISSUE_SIMPLE_MANUAL_PERMISSION_ENFORCEMENT, + SimpleRequiresNoPermissionDetector.ISSUE_SIMPLE_REQUIRES_NO_PERMISSION, ) override val api: Int diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/SimpleRequiresNoPermissionDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/SimpleRequiresNoPermissionDetector.kt new file mode 100644 index 000000000000..1a13c0280ec6 --- /dev/null +++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/SimpleRequiresNoPermissionDetector.kt @@ -0,0 +1,118 @@ +/* + * 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.google.android.lint.aidl + +import com.android.tools.lint.detector.api.Category +import com.android.tools.lint.detector.api.Implementation +import com.android.tools.lint.detector.api.Issue +import com.android.tools.lint.detector.api.JavaContext +import com.android.tools.lint.detector.api.Scope +import com.android.tools.lint.detector.api.Severity +import org.jetbrains.uast.UastCallKind +import org.jetbrains.uast.UBlockExpression +import org.jetbrains.uast.UCallExpression +import org.jetbrains.uast.UElement +import org.jetbrains.uast.UMethod +import org.jetbrains.uast.visitor.AbstractUastVisitor + +/** + * Ensures all AIDL implementations hosted by system_server which don't call other methods are + * annotated with @RequiresNoPermission. AIDL Interfaces part of `exemptAidlInterfaces` are skipped + * during this search to ensure the detector targets only new AIDL Interfaces. + */ +class SimpleRequiresNoPermissionDetector : AidlImplementationDetector() { + override fun visitAidlMethod( + context: JavaContext, + node: UMethod, + interfaceName: String, + body: UBlockExpression + ) { + if (!isSystemServicePath(context)) return + if (context.evaluator.isAbstract(node)) return + + val fullyQualifiedInterfaceName = + getContainingAidlInterfaceQualified(context, node) ?: return + if (exemptAidlInterfaces.contains(fullyQualifiedInterfaceName)) return + + if (node.hasAnnotation(ANNOTATION_REQUIRES_NO_PERMISSION)) return + + if (!isCallingMethod(node)) { + context.report( + ISSUE_SIMPLE_REQUIRES_NO_PERMISSION, + node, + context.getLocation(node), + """ + Method ${node.name} doesn't perform any permission checks, meaning it should \ + be annotated with @RequiresNoPermission. + """.trimMargin() + ) + } + } + + private fun isCallingMethod(node: UMethod): Boolean { + val uCallExpressionVisitor = UCallExpressionVisitor() + node.accept(uCallExpressionVisitor) + + return uCallExpressionVisitor.isCallingMethod + } + + /** + * Visits the body of a `UMethod` and determines if it encounters a `UCallExpression` which is + * a `UastCallKind.METHOD_CALL`. `isCallingMethod` will hold the result of the search procedure. + */ + private class UCallExpressionVisitor : AbstractUastVisitor() { + var isCallingMethod = false + + override fun visitElement(node: UElement): Boolean { + // Stop the search early when a method call has been found. + return isCallingMethod + } + + override fun visitCallExpression(node: UCallExpression): Boolean { + if (node.kind != UastCallKind.METHOD_CALL) return false + + isCallingMethod = true + return true + } + } + + companion object { + + private val EXPLANATION = """ + Method implementations of AIDL Interfaces hosted by the `system_server` which do not + call any other methods should be annotated with @RequiresNoPermission. That is because + not calling any other methods implies that the method does not perform any permission + checking. + + Please migrate to an @RequiresNoPermission annotation. + """.trimIndent() + + @JvmField + val ISSUE_SIMPLE_REQUIRES_NO_PERMISSION = Issue.create( + id = "SimpleRequiresNoPermission", + briefDescription = "System Service APIs not calling other methods should use @RNP", + explanation = EXPLANATION, + category = Category.SECURITY, + priority = 5, + severity = Severity.ERROR, + implementation = Implementation( + SimpleRequiresNoPermissionDetector::class.java, + Scope.JAVA_FILE_SCOPE + ), + ) + } +} diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/PermissionAnnotationDetectorTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/PermissionAnnotationDetectorTest.kt index 92d0829911bf..824be9309dbc 100644 --- a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/PermissionAnnotationDetectorTest.kt +++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/PermissionAnnotationDetectorTest.kt @@ -17,7 +17,6 @@ package com.google.android.lint.aidl import com.android.tools.lint.checks.infrastructure.LintDetectorTest -import com.android.tools.lint.checks.infrastructure.TestFile import com.android.tools.lint.checks.infrastructure.TestLintTask import com.android.tools.lint.detector.api.Detector import com.android.tools.lint.detector.api.Issue @@ -64,7 +63,7 @@ class PermissionAnnotationDetectorTest : LintDetectorTest() { """ package com.android.server; public class Bar extends IBar.Stub { - public void testMethod() { } + public void testMethod(int parameter1, int parameter2) { } } """ ) @@ -75,8 +74,8 @@ class PermissionAnnotationDetectorTest : LintDetectorTest() { .expect( """ src/frameworks/base/services/java/com/android/server/Bar.java:3: Error: The method testMethod is not permission-annotated. [MissingPermissionAnnotation] - public void testMethod() { } - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + public void testMethod(int parameter1, int parameter2) { } + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1 errors, 0 warnings """ ) @@ -90,7 +89,7 @@ class PermissionAnnotationDetectorTest : LintDetectorTest() { """ package com.android.server; public class Bar extends IBar.Stub { - public void testMethod() { } + public void testMethod(int parameter1, int parameter2) { } } """ ) @@ -132,7 +131,7 @@ class PermissionAnnotationDetectorTest : LintDetectorTest() { """ package com.android.server; public abstract class Bar extends IBar.Stub { - public abstract void testMethod(); + public abstract void testMethod(int parameter1, int parameter2); } """ ) @@ -177,50 +176,6 @@ class PermissionAnnotationDetectorTest : LintDetectorTest() { .expectClean() } - /* Stubs */ - - // A service with permission annotation on the method. - private val interfaceIFoo: TestFile = java( - """ - public interface IFoo extends android.os.IInterface { - public static abstract class Stub extends android.os.Binder implements IFoo { - } - @Override - @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE) - public void testMethod(); - @Override - @android.annotation.RequiresNoPermission - public void testMethodNoPermission(); - @Override - @android.annotation.PermissionManuallyEnforced - public void testMethodManual(); - } - """ - ).indented() - - // A service with no permission annotation. - private val interfaceIBar: TestFile = java( - """ - public interface IBar extends android.os.IInterface { - public static abstract class Stub extends android.os.Binder implements IBar { - } - public void testMethod(); - } - """ - ).indented() - - // A service whose AIDL Interface is exempted. - private val interfaceIExempted: TestFile = java( - """ - package android.accessibilityservice; - public interface IBrailleDisplayConnection extends android.os.IInterface { - public static abstract class Stub extends android.os.Binder implements IBrailleDisplayConnection { - } - public void testMethod(); - } - """ - ).indented() - private val stubs = arrayOf(interfaceIFoo, interfaceIBar, interfaceIExempted) private fun createVisitedPath(filename: String) = diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/SimpleRequiresNoPermissionDetectorTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/SimpleRequiresNoPermissionDetectorTest.kt new file mode 100644 index 000000000000..a33b48c7eaa0 --- /dev/null +++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/SimpleRequiresNoPermissionDetectorTest.kt @@ -0,0 +1,244 @@ +/* + * 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.google.android.lint.aidl + +import com.android.tools.lint.checks.infrastructure.LintDetectorTest +import com.android.tools.lint.checks.infrastructure.TestLintTask +import com.android.tools.lint.detector.api.Detector +import com.android.tools.lint.detector.api.Issue + +class SimpleRequiresNoPermissionDetectorTest : LintDetectorTest() { + override fun getDetector(): Detector = SimpleRequiresNoPermissionDetector() + override fun getIssues(): List<Issue> = listOf( + SimpleRequiresNoPermissionDetector + .ISSUE_SIMPLE_REQUIRES_NO_PERMISSION + ) + + override fun lint(): TestLintTask = super.lint().allowMissingSdk() + + fun testRequiresNoPermissionUsedCorrectly_shouldNotWarn() { + lint() + .files( + java( + createVisitedPath("Foo.java"), + """ + package com.android.server; + public class Foo extends IFoo.Stub { + private int memberInt; + + @Override + @android.annotation.RequiresNoPermission + public void testMethodNoPermission(int parameter1, int parameter2) { + if (parameter1 < parameter2) { + memberInt = parameter1; + } else { + memberInt = parameter2; + } + } + } + """ + ) + .indented(), + *stubs + ) + .run() + .expectClean() + } + + fun testMissingRequiresNoPermission_shouldWarn() { + lint() + .files( + java( + createVisitedPath("Bar.java"), + """ + package com.android.server; + public class Bar extends IBar.Stub { + private int memberInt; + + @Override + public void testMethod(int parameter1, int parameter2) { + if (parameter1 < parameter2) { + memberInt = parameter1; + } else { + memberInt = parameter2; + } + } + } + """ + ) + .indented(), + *stubs + ) + .run() + .expect( + """ + src/frameworks/base/services/java/com/android/server/Bar.java:5: Error: Method testMethod doesn't perform any permission checks, meaning it should be annotated with @RequiresNoPermission. [SimpleRequiresNoPermission] + @Override + ^ + 1 errors, 0 warnings + """ + ) + } + + fun testMethodOnlyPerformsConstructorCall_shouldWarn() { + lint() + .files( + java( + createVisitedPath("Bar.java"), + """ + package com.android.server; + public class Bar extends IBar.Stub { + private IntPair memberIntPair; + + @Override + public void testMethod(int parameter1, int parameter2) { + memberIntPair = new IntPair(parameter1, parameter2); + } + + private static class IntPair { + public int first; + public int second; + + public IntPair(int first, int second) { + this.first = first; + this.second = second; + } + } + } + """ + ) + .indented(), + *stubs + ) + .run() + .expect( + """ + src/frameworks/base/services/java/com/android/server/Bar.java:5: Error: Method testMethod doesn't perform any permission checks, meaning it should be annotated with @RequiresNoPermission. [SimpleRequiresNoPermission] + @Override + ^ + 1 errors, 0 warnings + """ + ) + } + + fun testMissingRequiresNoPermissionInIgnoredDirectory_shouldNotWarn() { + lint() + .files( + java( + ignoredPath, + """ + package com.android.server; + public class Bar extends IBar.Stub { + @Override + public void testMethod(int parameter1, int parameter2) {} + } + """ + ) + .indented(), + *stubs + ) + .run() + .expectClean() + } + + fun testMissingRequiresNoPermissionAbstractMethod_shouldNotWarn() { + lint() + .files( + java( + createVisitedPath("Bar.java"), + """ + package com.android.server; + public abstract class Bar extends IBar.Stub { + private int memberInt; + + @Override + public abstract void testMethodNoPermission(int parameter1, int parameter2); + } + """ + ) + .indented(), + *stubs + ) + .run() + .expectClean() + } + + // If this test fails, consider the following steps: + // 1. Pick the first entry (interface) from `exemptAidlInterfaces`. + // 2. Change `interfaceIExempted` to use that interface. + // 3. Change this test's class to extend the interface's Stub. + fun testMissingRequiresNoPermissionAidlInterfaceExempted_shouldNotWarn() { + lint() + .files( + java( + createVisitedPath("Bar.java"), + """ + package com.android.server; + public class Bar extends android.accessibilityservice.IBrailleDisplayConnection.Stub { + public void testMethod(int parameter1, int parameter2) {} + } + """ + ) + .indented(), + *stubs + ) + .run() + .expectClean() + } + + fun testMethodMakesAnotherMethodCall_shouldNotWarn() { + lint() + .files( + java( + createVisitedPath("Bar.java"), + """ + package com.android.server; + public class Bar extends IBar.Stub { + private int memberInt; + + @Override + public void testMethod(int parameter1, int parameter2) { + if (!hasPermission()) return; + + if (parameter1 < parameter2) { + memberInt = parameter1; + } else { + memberInt = parameter2; + } + } + + private bool hasPermission() { + // Perform a permission check. + return true; + } + } + """ + ) + .indented(), + *stubs + ) + .run() + .expectClean() + } + + private val stubs = arrayOf(interfaceIFoo, interfaceIBar, interfaceIExempted) + + private fun createVisitedPath(filename: String) = + "src/frameworks/base/services/java/com/android/server/$filename" + + private val ignoredPath = "src/test/pkg/TestClass.java" +} diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/Stubs.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/Stubs.kt index 2ec8fddbb4e9..18a8f186b624 100644 --- a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/Stubs.kt +++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/Stubs.kt @@ -85,4 +85,46 @@ val manifestStub: TestFile = java( } } """.trimIndent() -)
\ No newline at end of file +) + +// A service with permission annotation on the method. +val interfaceIFoo: TestFile = java( + """ + public interface IFoo extends android.os.IInterface { + public static abstract class Stub extends android.os.Binder implements IFoo { + } + @Override + @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE) + public void testMethod(); + @Override + @android.annotation.RequiresNoPermission + public void testMethodNoPermission(int parameter1, int parameter2); + @Override + @android.annotation.PermissionManuallyEnforced + public void testMethodManual(); + } + """ +).indented() + +// A service with no permission annotation. +val interfaceIBar: TestFile = java( + """ + public interface IBar extends android.os.IInterface { + public static abstract class Stub extends android.os.Binder implements IBar { + } + public void testMethod(int parameter1, int parameter2); + } + """ +).indented() + +// A service whose AIDL Interface is exempted. +val interfaceIExempted: TestFile = java( + """ + package android.accessibilityservice; + public interface IBrailleDisplayConnection extends android.os.IInterface { + public static abstract class Stub extends android.os.Binder implements IBrailleDisplayConnection { + } + public void testMethod(); + } + """ +).indented() diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java index f68ae2c7e249..fc4a909e761f 100644 --- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java +++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java @@ -173,6 +173,10 @@ public class SharedConnectivityManager { } } } + + Executor getExecutor() { + return mExecutor; + } } private ISharedConnectivityService mService; @@ -188,7 +192,7 @@ public class SharedConnectivityManager { private final String mServicePackageName; private final String mIntentAction; private ServiceConnection mServiceConnection; - private UserManager mUserManager; + private final UserManager mUserManager; /** * Creates a new instance of {@link SharedConnectivityManager}. @@ -316,15 +320,19 @@ public class SharedConnectivityManager { private void registerCallbackInternal(SharedConnectivityClientCallback callback, SharedConnectivityCallbackProxy proxy) { - try { - mService.registerCallback(proxy); - synchronized (mProxyDataLock) { - mProxyMap.put(callback, proxy); - } - } catch (RemoteException e) { - Log.e(TAG, "Exception in registerCallback", e); - callback.onRegisterCallbackFailed(e); - } + proxy.getExecutor().execute( + () -> { + try { + mService.registerCallback(proxy); + synchronized (mProxyDataLock) { + mProxyMap.put(callback, proxy); + } + } catch (RemoteException e) { + Log.e(TAG, "Exception in registerCallback", e); + callback.onRegisterCallbackFailed(e); + } + } + ); } /** |