diff options
803 files changed, 15272 insertions, 7635 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 7d9e95bb12ee..59a7cbc16587 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -21,6 +21,7 @@ aconfig_declarations_group { // !!! KEEP THIS LIST ALPHABETICAL !!! "aconfig_mediacodec_flags_java_lib", "android.adaptiveauth.flags-aconfig-java", + "android.app.appfunctions.flags-aconfig-java", "android.app.contextualsearch.flags-aconfig-java", "android.app.flags-aconfig-java", "android.app.ondeviceintelligence-aconfig-java", @@ -172,6 +173,7 @@ cc_aconfig_library { // Window aconfig_declarations { name: "com.android.window.flags.window-aconfig", + exportable: true, package: "com.android.window.flags", container: "system", srcs: ["core/java/android/window/flags/*.aconfig"], @@ -1382,6 +1384,21 @@ java_aconfig_library { defaults: ["framework-minus-apex-aconfig-java-defaults"], } +// AppFunctions +aconfig_declarations { + name: "android.app.appfunctions.flags-aconfig", + exportable: true, + package: "android.app.appfunctions.flags", + container: "system", + srcs: ["core/java/android/app/appfunctions/flags/flags.aconfig"], +} + +java_aconfig_library { + name: "android.app.appfunctions.flags-aconfig-java", + aconfig_declarations: "android.app.appfunctions.flags-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} + // Adaptive Auth aconfig_declarations { name: "android.adaptiveauth.flags-aconfig", diff --git a/INPUT_OWNERS b/INPUT_OWNERS index 06ead06fc13a..9b1016e7b7e9 100644 --- a/INPUT_OWNERS +++ b/INPUT_OWNERS @@ -1,4 +1,5 @@ # Bug component: 136048 +# Please assign bugs to android-framework-input-triage@. arpitks@google.com asmitapoddar@google.com hcutts@google.com diff --git a/Ravenwood.bp b/Ravenwood.bp index 258796942ca9..5f32ba026b50 100644 --- a/Ravenwood.bp +++ b/Ravenwood.bp @@ -43,29 +43,14 @@ genrule_defaults { ], out: [ "ravenwood.jar", - - // Following files are created just as FYI. - "hoststubgen_framework-minus-apex_keep_all.txt", - "hoststubgen_framework-minus-apex_dump.txt", - "hoststubgen_framework-minus-apex.log", - "hoststubgen_framework-minus-apex_stats.csv", - "hoststubgen_framework-minus-apex_apis.csv", ], } framework_minus_apex_cmd = "$(location hoststubgen) " + "@$(location :ravenwood-standard-options) " + - "--debug-log $(location hoststubgen_framework-minus-apex.log) " + - "--stats-file $(location hoststubgen_framework-minus-apex_stats.csv) " + - "--supported-api-list-file $(location hoststubgen_framework-minus-apex_apis.csv) " + - "--out-impl-jar $(location ravenwood.jar) " + - - "--gen-keep-all-file $(location hoststubgen_framework-minus-apex_keep_all.txt) " + - "--gen-input-dump-file $(location hoststubgen_framework-minus-apex_dump.txt) " + - "--in-jar $(location :framework-minus-apex-for-hoststubgen) " + "--policy-override-file $(location :ravenwood-framework-policies) " + "--annotation-allowed-classes-file $(location :ravenwood-annotation-allowed-classes) " @@ -133,13 +118,26 @@ java_genrule { // Build framework-minus-apex.ravenwood-base without sharding. // We extract the various dump files from this one, rather than the sharded ones, because // some dumps use the output from other classes (e.g. base classes) which may not be in the -// same shard. +// same shard. Also some of the dump files ("apis") may be slow even when sharded, because +// the output contains the information from all the input classes, rather than the output classes. // Not using sharding is fine for this module because it's only used for collecting the // dump / stats files, which don't have to happen regularly. java_genrule { name: "framework-minus-apex.ravenwood-base_all", defaults: ["framework-minus-apex.ravenwood-base_defaults"], - cmd: framework_minus_apex_cmd, + cmd: framework_minus_apex_cmd + + "--stats-file $(location hoststubgen_framework-minus-apex_stats.csv) " + + "--supported-api-list-file $(location hoststubgen_framework-minus-apex_apis.csv) " + + + "--gen-keep-all-file $(location hoststubgen_framework-minus-apex_keep_all.txt) " + + "--gen-input-dump-file $(location hoststubgen_framework-minus-apex_dump.txt) ", + + out: [ + "hoststubgen_framework-minus-apex_keep_all.txt", + "hoststubgen_framework-minus-apex_dump.txt", + "hoststubgen_framework-minus-apex_stats.csv", + "hoststubgen_framework-minus-apex_apis.csv", + ], } // Marge all the sharded jars diff --git a/TEST_MAPPING b/TEST_MAPPING index 49384cde5803..5db077220b20 100644 --- a/TEST_MAPPING +++ b/TEST_MAPPING @@ -137,13 +137,14 @@ "name": "CtsStrictJavaPackagesTestCases" } ], - "postsubmit-ravenwood": [ + "ravenwood-presubmit": [ { "name": "CtsUtilTestCasesRavenwood", - "host": true, - "file_patterns": [ - "[Rr]avenwood" - ] + "host": true + }, + { + "name": "RavenwoodBivalentTest", + "host": true } ], "postsubmit-managedprofile-stress": [ diff --git a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientSocketPerfTest.java b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientSocketPerfTest.java index d7b1c9a2d3a2..f20b1706129b 100644 --- a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientSocketPerfTest.java +++ b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientSocketPerfTest.java @@ -43,7 +43,6 @@ import java.util.concurrent.atomic.AtomicLong; import javax.crypto.Cipher; import javax.crypto.NoSuchPaddingException; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -143,7 +142,7 @@ public final class ClientSocketPerfTest { // Always use the same server for consistency across the benchmarks. server = config.serverFactory().newServer( - ChannelType.CHANNEL, config.messageSize(), config.protocol().getProtocols(), + config.messageSize(), config.protocol().getProtocols(), ciphers(config)); server.setMessageProcessor(new ServerEndpoint.MessageProcessor() { @@ -197,7 +196,6 @@ public final class ClientSocketPerfTest { */ @Test @Parameters(method = "getParams") - @Ignore("b/351034205") public void time(Config config) throws Exception { reset(); setup(config); diff --git a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/EndpointFactory.java b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/EndpointFactory.java index 0655f45726ba..ba2acb8a5205 100644 --- a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/EndpointFactory.java +++ b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/EndpointFactory.java @@ -43,10 +43,10 @@ public enum EndpointFactory { factories.clientFactory, channelType, port, protocols, ciphers); } - public ServerEndpoint newServer(ChannelType channelType, int messageSize, + public ServerEndpoint newServer(int messageSize, String[] protocols, String[] ciphers) throws IOException { return new ServerEndpoint(factories.serverFactory, factories.serverSocketFactory, - channelType, messageSize, protocols, ciphers); + messageSize, protocols, ciphers); } private static final class Factories { diff --git a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerEndpoint.java b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerEndpoint.java index 3631c3f29287..1e4f12460936 100644 --- a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerEndpoint.java +++ b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerEndpoint.java @@ -34,8 +34,6 @@ import javax.net.ssl.SSLServerSocketFactory; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; -import org.conscrypt.ChannelType; - /** * A simple socket-based test server. */ @@ -63,7 +61,6 @@ final class ServerEndpoint { } private final ServerSocket serverSocket; - private final ChannelType channelType; private final SSLSocketFactory socketFactory; private final int messageSize; private final String[] protocols; @@ -78,11 +75,10 @@ final class ServerEndpoint { private volatile Future<?> processFuture; ServerEndpoint(SSLSocketFactory socketFactory, SSLServerSocketFactory serverSocketFactory, - ChannelType channelType, int messageSize, String[] protocols, + int messageSize, String[] protocols, String[] cipherSuites) throws IOException { - this.serverSocket = channelType.newServerSocket(serverSocketFactory); + this.serverSocket = serverSocketFactory.createServerSocket(); this.socketFactory = socketFactory; - this.channelType = channelType; this.messageSize = messageSize; this.protocols = protocols; this.cipherSuites = cipherSuites; @@ -134,7 +130,7 @@ final class ServerEndpoint { if (stopping) { return; } - socket = channelType.accept(serverSocket, socketFactory); + socket = (SSLSocket) serverSocket.accept(); socket.setEnabledProtocols(protocols); socket.setEnabledCipherSuites(cipherSuites); diff --git a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerSocketPerfTest.java b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerSocketPerfTest.java index 8916a3c55a9a..af3c405eab82 100644 --- a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerSocketPerfTest.java +++ b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerSocketPerfTest.java @@ -43,7 +43,6 @@ import androidx.test.filters.LargeTest; import junitparams.JUnitParamsRunner; import junitparams.Parameters; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -131,8 +130,7 @@ public final class ServerSocketPerfTest { final ChannelType channelType = config.channelType(); - server = config.serverFactory().newServer( - channelType, config.messageSize(), + server = config.serverFactory().newServer(config.messageSize(), new String[] {"TLSv1.3", "TLSv1.2"}, ciphers(config)); server.setMessageProcessor(new MessageProcessor() { @Override @@ -202,7 +200,6 @@ public final class ServerSocketPerfTest { @Test @Parameters(method = "getParams") - @Ignore("b/351034205") public void throughput(Config config) throws Exception { setup(config); BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); diff --git a/apct-tests/perftests/core/src/android/text/VariableFontPerfTest.java b/apct-tests/perftests/core/src/android/text/VariableFontPerfTest.java index c34936f930f9..fbe67a477f5d 100644 --- a/apct-tests/perftests/core/src/android/text/VariableFontPerfTest.java +++ b/apct-tests/perftests/core/src/android/text/VariableFontPerfTest.java @@ -19,7 +19,6 @@ package android.text; import android.graphics.Paint; import android.graphics.RecordingCanvas; import android.graphics.RenderNode; -import android.graphics.Typeface; import android.perftests.utils.BenchmarkState; import android.perftests.utils.PerfStatusReporter; @@ -121,34 +120,13 @@ public class VariableFontPerfTest { public void testSetFontVariationSettings() { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); final Paint paint = new Paint(PAINT); + final Random random = new Random(0); while (state.keepRunning()) { state.pauseTiming(); - paint.setTypeface(null); - paint.setFontVariationSettings(null); - Typeface.clearTypefaceCachesForTestingPurpose(); - state.resumeTiming(); - - paint.setFontVariationSettings("'wght' 450"); - } - Typeface.clearTypefaceCachesForTestingPurpose(); - } - - @Test - public void testSetFontVariationSettings_Cached() { - final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); - final Paint paint = new Paint(PAINT); - Typeface.clearTypefaceCachesForTestingPurpose(); - - while (state.keepRunning()) { - state.pauseTiming(); - paint.setTypeface(null); - paint.setFontVariationSettings(null); + int weight = random.nextInt(1000); state.resumeTiming(); - paint.setFontVariationSettings("'wght' 450"); + paint.setFontVariationSettings("'wght' " + weight); } - - Typeface.clearTypefaceCachesForTestingPurpose(); } - } diff --git a/apct-tests/perftests/multiuser/Android.bp b/apct-tests/perftests/multiuser/Android.bp index 856dba3f804c..9eea712b33dd 100644 --- a/apct-tests/perftests/multiuser/Android.bp +++ b/apct-tests/perftests/multiuser/Android.bp @@ -45,3 +45,8 @@ filegroup { "trace_configs/trace_config_multi_user.textproto", ], } + +prebuilt_etc { + name: "trace_config_multi_user.textproto", + src: ":multi_user_trace_config", +} diff --git a/apct-tests/perftests/settingsprovider/src/android/provider/SettingsProviderPerfTest.java b/apct-tests/perftests/settingsprovider/src/android/provider/SettingsProviderPerfTest.java index c00c8d550885..06cd94263847 100644 --- a/apct-tests/perftests/settingsprovider/src/android/provider/SettingsProviderPerfTest.java +++ b/apct-tests/perftests/settingsprovider/src/android/provider/SettingsProviderPerfTest.java @@ -36,7 +36,7 @@ import java.util.List; @RunWith(AndroidJUnit4.class) public final class SettingsProviderPerfTest { - private static final String NAMESPACE = "test@namespace"; + private static final String NAMESPACE = "testing"; private static final String SETTING_NAME1 = "test:setting1"; private static final String SETTING_NAME2 = "test-setting2"; private static final String UNSET_SETTING = "test_unset_setting"; diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java index 18ee6f2c7992..0f3b1c366fb0 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java @@ -319,7 +319,7 @@ public class AlarmManagerService extends SystemService { */ int mSystemUiUid; - static boolean isTimeTickAlarm(Alarm a) { + private static boolean isTimeTickAlarm(Alarm a) { return a.uid == Process.SYSTEM_UID && TIME_TICK_TAG.equals(a.listenerTag); } @@ -357,6 +357,7 @@ public class AlarmManagerService extends SystemService { } // TODO(b/172085676): Move inside alarm store. + @GuardedBy("mLock") private final SparseArray<AlarmManager.AlarmClockInfo> mNextAlarmClockForUser = new SparseArray<>(); private final SparseArray<AlarmManager.AlarmClockInfo> mTmpSparseAlarmClockArray = @@ -2616,6 +2617,13 @@ public class AlarmManagerService extends SystemService { mInFlightListeners.add(callback); } } + + /** @see AlarmManagerInternal#getNextAlarmTriggerTimeForUser(int) */ + @Override + public long getNextAlarmTriggerTimeForUser(@UserIdInt int userId) { + final AlarmManager.AlarmClockInfo nextAlarm = getNextAlarmClockImpl(userId); + return nextAlarm != null ? nextAlarm.getTriggerTime() : 0; + } } boolean hasUseExactAlarmInternal(String packageName, int uid) { @@ -3947,6 +3955,9 @@ public class AlarmManagerService extends SystemService { if (!RemovedAlarm.isLoggable(reason)) { continue; } + if (isTimeTickAlarm(removed)) { + Slog.wtf(TAG, "Removed TIME_TICK alarm"); + } RingBuffer<RemovedAlarm> bufferForUid = mRemovalHistory.get(removed.uid); if (bufferForUid == null) { bufferForUid = new RingBuffer<>(RemovedAlarm.class, REMOVAL_HISTORY_SIZE_PER_UID); @@ -4441,6 +4452,11 @@ public class AlarmManagerService extends SystemService { public void run() { ArrayList<Alarm> triggerList = new ArrayList<Alarm>(); + synchronized (mLock) { + mLastTimeChangeClockTime = mInjector.getCurrentTimeMillis(); + mLastTimeChangeRealtime = mInjector.getElapsedRealtimeMillis(); + } + while (true) { int result = mInjector.waitForAlarm(); final long nowRTC = mInjector.getCurrentTimeMillis(); @@ -4464,10 +4480,9 @@ public class AlarmManagerService extends SystemService { expectedClockTime = lastTimeChangeClockTime + (nowELAPSED - mLastTimeChangeRealtime); } - if (lastTimeChangeClockTime == 0 || nowRTC < (expectedClockTime - 1000) + if (nowRTC < (expectedClockTime - 1000) || nowRTC > (expectedClockTime + 1000)) { - // The change is by at least +/- 1000 ms (or this is the first change), - // let's do it! + // The change is by at least +/- 1000 ms, let's do it! if (DEBUG_BATCH) { Slog.v(TAG, "Time changed notification from kernel; rebatching"); } diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/LazyAlarmStore.java b/apex/jobscheduler/service/java/com/android/server/alarm/LazyAlarmStore.java index 0073335a1332..020b510269f8 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/LazyAlarmStore.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/LazyAlarmStore.java @@ -17,11 +17,9 @@ package com.android.server.alarm; import static com.android.server.alarm.AlarmManagerService.dumpAlarmList; -import static com.android.server.alarm.AlarmManagerService.isTimeTickAlarm; import android.app.AlarmManager; import android.util.IndentingPrintWriter; -import android.util.Slog; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; @@ -88,11 +86,6 @@ public class LazyAlarmStore implements AlarmStore { if (removed.alarmClock != null && mOnAlarmClockRemoved != null) { mOnAlarmClockRemoved.run(); } - if (isTimeTickAlarm(removed)) { - // This code path is not invoked when delivering alarms, only when removing - // alarms due to the caller cancelling it or getting uninstalled, etc. - Slog.wtf(TAG, "Removed TIME_TICK alarm"); - } removedAlarms.add(removed); } } diff --git a/core/TEST_MAPPING b/core/TEST_MAPPING index fd571c95f568..b78659cd611d 100644 --- a/core/TEST_MAPPING +++ b/core/TEST_MAPPING @@ -1,18 +1,7 @@ { "presubmit": [ { - "name": "FrameworksCoreTests", - "options": [ - { - "include-filter": "android.view.inputmethod" - }, - { - "include-filter": "com.android.internal.inputmethod" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ], + "name": "FrameworksCoreTests_inputmethod", "file_patterns": [ "core/java/com/android/internal/inputmethod/.*", "core/java/android/view/inputmethod/.*", diff --git a/core/api/current.txt b/core/api/current.txt index ea039a7103a3..7f2b00485417 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -6844,9 +6844,6 @@ package android.app { method public android.app.Notification.MessagingStyle.Message setData(String, android.net.Uri); } - @FlaggedApi("android.app.api_rich_ongoing") public abstract static class Notification.RichOngoingStyle extends android.app.Notification.Style { - } - public abstract static class Notification.Style { ctor @Deprecated public Notification.Style(); method public android.app.Notification build(); @@ -10684,6 +10681,7 @@ package android.content { field public static final String ACTIVITY_SERVICE = "activity"; field public static final String ALARM_SERVICE = "alarm"; field public static final String APPWIDGET_SERVICE = "appwidget"; + field @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public static final String APP_FUNCTION_SERVICE = "app_function"; field public static final String APP_OPS_SERVICE = "appops"; field public static final String APP_SEARCH_SERVICE = "app_search"; field public static final String AUDIO_SERVICE = "audio"; diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 90de7abf845c..4fb35c3d5f5c 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -7312,7 +7312,7 @@ public class Activity extends ContextThemeWrapper @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private void finish(int finishTask) { if (DEBUG_FINISH_ACTIVITY) { - Log.d("Instrumentation", "finishActivity: finishTask=" + finishTask, new Throwable()); + Log.d(Instrumentation.TAG, "finishActivity: finishTask=" + finishTask, new Throwable()); } if (mParent == null) { int resultCode; diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 74e95839b2f5..be70de20c2e2 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -16,6 +16,7 @@ package android.app; +import static android.app.Instrumentation.DEBUG_FINISH_ACTIVITY; import static android.app.WindowConfiguration.activityTypeToString; import static android.app.WindowConfiguration.windowingModeToString; import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; @@ -80,6 +81,7 @@ import android.os.WorkSource; import android.text.TextUtils; import android.util.ArrayMap; import android.util.DisplayMetrics; +import android.util.Log; import android.util.Singleton; import android.util.Size; import android.view.WindowInsetsController.Appearance; @@ -6011,6 +6013,10 @@ public class ActivityManager { * Finishes all activities in this task and removes it from the recent tasks list. */ public void finishAndRemoveTask() { + if (DEBUG_FINISH_ACTIVITY) { + Log.d(Instrumentation.TAG, "AppTask#finishAndRemoveTask: task=" + + getTaskInfo(), new Throwable()); + } try { mAppTaskImpl.finishAndRemoveTask(); } catch (RemoteException e) { diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index 89efa9b77a60..d31881265064 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -961,6 +961,17 @@ public abstract class ActivityManagerInternal { @Nullable VoiceInteractionManagerProvider provider); /** + * Get whether or not the previous user's packages will be killed before the user is + * stopped during a user switch. + * + * <p> The primary use case of this method is for {@link com.android.server.SystemService} + * classes to call this API in their + * {@link com.android.server.SystemService#onUserSwitching} method implementation to prevent + * restarting any of the previous user's processes that will be killed during the user switch. + */ + public abstract boolean isEarlyPackageKillEnabledForUserSwitch(int fromUserId, int toUserId); + + /** * Sets whether the current foreground user (and its profiles) should be stopped after switched * out. */ diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl index 55ce90d00011..c876921379c3 100644 --- a/core/java/android/app/IActivityTaskManager.aidl +++ b/core/java/android/app/IActivityTaskManager.aidl @@ -226,7 +226,7 @@ interface IActivityTaskManager { in IBinder activityToken, int flags); boolean isAssistDataAllowed(); boolean requestAssistDataForTask(in IAssistDataReceiver receiver, int taskId, - in String callingPackageName, String callingAttributionTag); + in String callingPackageName, String callingAttributionTag, boolean fetchStructure); /** * Notify the system that the keyguard is going away. diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java index be270463e576..45852c7d338a 100644 --- a/core/java/android/app/Instrumentation.java +++ b/core/java/android/app/Instrumentation.java @@ -98,7 +98,7 @@ public class Instrumentation { */ public static final String REPORT_KEY_STREAMRESULT = "stream"; - private static final String TAG = "Instrumentation"; + static final String TAG = "Instrumentation"; private static final long CONNECT_TIMEOUT_MILLIS = 60_000; diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index ef09dc45d423..db979a5dd30b 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -10979,18 +10979,6 @@ public class Notification implements Parcelable } /** - * An object that can apply a rich ongoing notification style to a {@link Notification.Builder} - * object. - */ - @FlaggedApi(Flags.FLAG_API_RICH_ONGOING) - public abstract static class RichOngoingStyle extends Notification.Style { - /** - * @hide - */ - public RichOngoingStyle() {} - } - - /** * Notification style for custom views that are decorated by the system * * <p>Instead of providing a notification that is completely custom, a developer can set this diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java index fd4d8e90adf9..0cc210b7db41 100644 --- a/core/java/android/app/ResourcesManager.java +++ b/core/java/android/app/ResourcesManager.java @@ -1836,9 +1836,10 @@ public class ResourcesManager { // have shared library asset paths appended if there are any. if (r.getImpl() != null) { final ResourcesImpl oldImpl = r.getImpl(); + final AssetManager oldAssets = oldImpl.getAssets(); // ResourcesImpl constructor will help to append shared library asset paths. - if (oldImpl.getAssets().isUpToDate()) { - final ResourcesImpl newImpl = new ResourcesImpl(oldImpl.getAssets(), + if (oldAssets != AssetManager.getSystem() && oldAssets.isUpToDate()) { + final ResourcesImpl newImpl = new ResourcesImpl(oldAssets, oldImpl.getMetrics(), oldImpl.getConfiguration(), oldImpl.getDisplayAdjustments()); r.setImpl(newImpl); diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index e73f4718732f..114a2c4d5649 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -16,6 +16,8 @@ package android.app; +import static android.app.appfunctions.flags.Flags.enableAppFunctionManager; + import android.accounts.AccountManager; import android.accounts.IAccountManager; import android.adservices.AdServicesFrameworkInitializer; @@ -28,6 +30,8 @@ import android.app.admin.DevicePolicyManager; import android.app.admin.IDevicePolicyManager; import android.app.ambientcontext.AmbientContextManager; import android.app.ambientcontext.IAmbientContextManager; +import android.app.appfunctions.AppFunctionManager; +import android.app.appfunctions.IAppFunctionManager; import android.app.appsearch.AppSearchManagerFrameworkInitializer; import android.app.blob.BlobStoreManagerFrameworkInitializer; import android.app.contentsuggestions.ContentSuggestionsManager; @@ -925,6 +929,21 @@ public final class SystemServiceRegistry { return new CompanionDeviceManager(service, ctx.getOuterContext()); }}); + if (enableAppFunctionManager()) { + registerService(Context.APP_FUNCTION_SERVICE, AppFunctionManager.class, + new CachedServiceFetcher<>() { + @Override + public AppFunctionManager createService(ContextImpl ctx) + throws ServiceNotFoundException { + IAppFunctionManager service; + //TODO(b/357551503): If the feature not present avoid look up every time + service = IAppFunctionManager.Stub.asInterface( + ServiceManager.getServiceOrThrow(Context.APP_FUNCTION_SERVICE)); + return new AppFunctionManager(service, ctx.getOuterContext()); + } + }); + } + registerService(Context.VIRTUAL_DEVICE_SERVICE, VirtualDeviceManager.class, new CachedServiceFetcher<VirtualDeviceManager>() { @Override diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING index 0deb842aff61..b7f672c9766b 100644 --- a/core/java/android/app/TEST_MAPPING +++ b/core/java/android/app/TEST_MAPPING @@ -153,18 +153,7 @@ "file_patterns": ["(/|^)ContextImpl.java"] }, { - "name": "FrameworksCoreTests", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - }, - { - "include-filter": "android.content.ContextTest" - } - ], + "name": "FrameworksCoreTests_context", "file_patterns": ["(/|^)ContextImpl.java"] }, { @@ -177,35 +166,13 @@ ] }, { - "name": "FrameworksCoreTests", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - }, - { - "include-filter": "android.app.KeyguardManagerTest" - } - ], + "name": "FrameworksCoreTests_keyguard_manager", "file_patterns": [ "(/|^)KeyguardManager.java" ] }, { - "name": "FrameworksCoreTests", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - }, - { - "include-filter": "android.app.PropertyInvalidatedCacheTests" - } - ], + "name": "FrameworksCoreTests_property_invalidated_cache", "file_patterns": [ "(/|^)PropertyInvalidatedCache.java" ] diff --git a/core/java/android/app/appfunctions/AppFunctionManager.java b/core/java/android/app/appfunctions/AppFunctionManager.java new file mode 100644 index 000000000000..a01e373c5e83 --- /dev/null +++ b/core/java/android/app/appfunctions/AppFunctionManager.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.appfunctions; + +import android.annotation.SystemService; +import android.content.Context; + +/** + * 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. + * + * @hide + */ +@SystemService(Context.APP_FUNCTION_SERVICE) +public final class AppFunctionManager { + private final IAppFunctionManager mService; + private final Context mContext; + + /** + * TODO(b/357551503): add comments when implement this class + * + * @hide + */ + public AppFunctionManager(IAppFunctionManager mService, Context context) { + this.mService = mService; + this.mContext = context; + } +} diff --git a/core/java/android/app/appfunctions/IAppFunctionManager.aidl b/core/java/android/app/appfunctions/IAppFunctionManager.aidl new file mode 100644 index 000000000000..018bc758f69f --- /dev/null +++ b/core/java/android/app/appfunctions/IAppFunctionManager.aidl @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.appfunctions; + +/** +* Interface between an app and the server implementation service (AppFunctionManagerService). +* @hide +*/ +oneway interface IAppFunctionManager { +}
\ No newline at end of file diff --git a/core/java/android/app/appfunctions/OWNERS b/core/java/android/app/appfunctions/OWNERS new file mode 100644 index 000000000000..c6827cc93222 --- /dev/null +++ b/core/java/android/app/appfunctions/OWNERS @@ -0,0 +1,6 @@ +avayvod@google.com +oadesina@google.com +toki@google.com +tonymak@google.com +mingweiliao@google.com +anothermark@google.com diff --git a/core/java/android/app/appfunctions/flags/flags.aconfig b/core/java/android/app/appfunctions/flags/flags.aconfig new file mode 100644 index 000000000000..367effc9e9bb --- /dev/null +++ b/core/java/android/app/appfunctions/flags/flags.aconfig @@ -0,0 +1,11 @@ +package: "android.app.appfunctions.flags" +container: "system" + +flag { + name: "enable_app_function_manager" + is_exported: true + is_fixed_read_only: true + namespace: "machine_learning" + description: "This flag the new App Function manager system service." + bug: "357551503" +}
\ No newline at end of file diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 9aebfc8e5fd7..9dccc9ae7145 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -16,6 +16,7 @@ package android.content; +import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER; import static android.content.flags.Flags.FLAG_ENABLE_BIND_PACKAGE_ISOLATED_PROCESS; import android.annotation.AttrRes; @@ -51,6 +52,7 @@ import android.app.IApplicationThread; import android.app.IServiceConnection; import android.app.VrManager; import android.app.ambientcontext.AmbientContextManager; +import android.app.appfunctions.AppFunctionManager; import android.app.people.PeopleManager; import android.app.time.TimeManager; import android.companion.virtual.VirtualDeviceManager; @@ -6310,6 +6312,16 @@ public abstract class Context { /** * Use with {@link #getSystemService(String)} to retrieve an + * {@link AppFunctionManager} for + * executing app functions. + * + * @see #getSystemService(String) + */ + @FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER) + public static final String APP_FUNCTION_SERVICE = "app_function"; + + /** + * Use with {@link #getSystemService(String)} to retrieve an * {@link android.content.integrity.AppIntegrityManager}. * @hide */ diff --git a/core/java/android/content/TEST_MAPPING b/core/java/android/content/TEST_MAPPING index 41a4288eae5c..e353a0107bab 100644 --- a/core/java/android/content/TEST_MAPPING +++ b/core/java/android/content/TEST_MAPPING @@ -22,24 +22,7 @@ "file_patterns": ["(/|^)Context.java", "(/|^)ContextWrapper.java"] }, { - "name": "FrameworksCoreTests", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - }, - { - "include-filter": "android.content.ContextTest" - }, - { - "include-filter": "android.content.ComponentCallbacksControllerTest" - }, - { - "include-filter": "android.content.ContextWrapperTest" - } - ], + "name": "FrameworksCoreTests_android_content", "file_patterns": ["(/|^)Context.java", "(/|^)ContextWrapper.java", "(/|^)ComponentCallbacksController.java"] }, { diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING index b0ab11f48858..1fab3cffcebd 100644 --- a/core/java/android/content/pm/TEST_MAPPING +++ b/core/java/android/content/pm/TEST_MAPPING @@ -171,6 +171,17 @@ "include-filter": "android.content.pm.cts.PackageManagerShellCommandMultiUserTest" } ] + }, + { + "name":"CtsPackageInstallerCUJTestCases", + "options":[ + { + "exclude-annotation":"androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation":"org.junit.Ignore" + } + ] } ] } diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig index 59fca3bc8cb1..3a33ef9002cc 100644 --- a/core/java/android/content/pm/multiuser.aconfig +++ b/core/java/android/content/pm/multiuser.aconfig @@ -161,6 +161,16 @@ flag { } flag { + name: "fix_avatar_picker_not_responding_for_new_user" + namespace: "multiuser" + description: "Avatar picker is not responding after selecting photo for new user." + bug: "358407488" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "fix_get_user_property_cache" namespace: "multiuser" description: "Cache is not optimised for getUserProperty for values below 0, eg. UserHandler.USER_NULL or UserHandle.USER_ALL" @@ -288,6 +298,13 @@ flag { } flag { + name: "stop_previous_user_apps" + namespace: "multiuser" + description: "Stop the previous user apps early in a user switch" + bug: "323200731" +} + +flag { name: "disable_private_space_items_on_home" namespace: "profile_experiences" description: "Disables adding items belonging to Private Space on Home Screen manually as well as automatically" diff --git a/core/java/android/database/OWNERS b/core/java/android/database/OWNERS index 53f5bb0ab492..50b7015e6b5c 100644 --- a/core/java/android/database/OWNERS +++ b/core/java/android/database/OWNERS @@ -1,6 +1,2 @@ include /SQLITE_OWNERS -omakoto@google.com -jsharkey@android.com -yamasani@google.com - diff --git a/core/java/android/database/sqlite/TEST_MAPPING b/core/java/android/database/sqlite/TEST_MAPPING index 9dcf4e592454..659cf6cd9cf3 100644 --- a/core/java/android/database/sqlite/TEST_MAPPING +++ b/core/java/android/database/sqlite/TEST_MAPPING @@ -1,18 +1,7 @@ { "presubmit": [ { - "name": "FrameworksCoreTests", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - }, - { - "include-filter": "android.database.sqlite.SQLiteRawStatementTest" - } - ], + "name": "FrameworksCoreTests_sqlite", "file_patterns": [ "(/|^)SQLiteRawStatement.java", "(/|^)SQLiteDatabase.java", diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java index 678bd6bc6336..de1cac47ff46 100644 --- a/core/java/android/hardware/biometrics/BiometricManager.java +++ b/core/java/android/hardware/biometrics/BiometricManager.java @@ -415,7 +415,7 @@ public class BiometricManager { @RequiresPermission(TEST_BIOMETRIC) public BiometricTestSession createTestSession(int sensorId) { try { - return new BiometricTestSession(mContext, sensorId, + return new BiometricTestSession(mContext, getSensorProperties(), sensorId, (context, sensorId1, callback) -> mService .createTestSession(sensorId1, callback, context.getOpPackageName())); } catch (RemoteException e) { diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java index 9007b62bccfc..b11961cc2b21 100644 --- a/core/java/android/hardware/biometrics/BiometricPrompt.java +++ b/core/java/android/hardware/biometrics/BiometricPrompt.java @@ -638,17 +638,17 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan * Set caller's component name for getting logo icon/description. This should only be used * by ConfirmDeviceCredentialActivity, see b/337082634 for more context. * - * @param componentNameForConfirmDeviceCredentialActivity set the component name for - * ConfirmDeviceCredentialActivity. + * @param realCaller set the component name of real caller for + * ConfirmDeviceCredentialActivity. * @return This builder. * @hide */ @NonNull @RequiresPermission(anyOf = {TEST_BIOMETRIC, USE_BIOMETRIC_INTERNAL}) - public Builder setComponentNameForConfirmDeviceCredentialActivity( - ComponentName componentNameForConfirmDeviceCredentialActivity) { - mPromptInfo.setComponentNameForConfirmDeviceCredentialActivity( - componentNameForConfirmDeviceCredentialActivity); + public Builder setRealCallerForConfirmDeviceCredentialActivity(ComponentName realCaller) { + mPromptInfo.setRealCallerForConfirmDeviceCredentialActivity(realCaller); + mPromptInfo.setClassNameIfItIsConfirmDeviceCredentialActivity( + mContext.getClass().getName()); return this; } diff --git a/core/java/android/hardware/biometrics/BiometricTestSession.java b/core/java/android/hardware/biometrics/BiometricTestSession.java index 027d1015a4b5..8bd352888de1 100644 --- a/core/java/android/hardware/biometrics/BiometricTestSession.java +++ b/core/java/android/hardware/biometrics/BiometricTestSession.java @@ -27,12 +27,15 @@ import android.os.RemoteException; import android.util.ArraySet; import android.util.Log; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; /** * Common set of interfaces to test biometric-related APIs, including {@link BiometricPrompt} and * {@link android.hardware.fingerprint.FingerprintManager}. + * * @hide */ @TestApi @@ -48,21 +51,29 @@ public class BiometricTestSession implements AutoCloseable { @NonNull ITestSessionCallback callback) throws RemoteException; } - private final Context mContext; private final int mSensorId; - private final ITestSession mTestSession; + private final List<ITestSession> mTestSessionsForAllSensors = new ArrayList<>(); + private ITestSession mTestSession; // Keep track of users that were tested, which need to be cleaned up when finishing. - @NonNull private final ArraySet<Integer> mTestedUsers; + @NonNull + private final ArraySet<Integer> mTestedUsers; // Track the users currently cleaning up, and provide a latch that gets notified when all // users have finished cleaning up. This is an imperfect system, as there can technically be // multiple cleanups per user. Theoretically we should track the cleanup's BaseClientMonitor's // unique ID, but it's complicated to plumb it through. This should be fine for now. - @Nullable private CountDownLatch mCloseLatch; - @NonNull private final ArraySet<Integer> mUsersCleaningUp; + @Nullable + private CountDownLatch mCloseLatch; + @NonNull + private final ArraySet<Integer> mUsersCleaningUp; + + private class TestSessionCallbackIml extends ITestSessionCallback.Stub { + private final int mSensorId; + private TestSessionCallbackIml(int sensorId) { + mSensorId = sensorId; + } - private final ITestSessionCallback mCallback = new ITestSessionCallback.Stub() { @Override public void onCleanupStarted(int userId) { Log.d(getTag(), "onCleanupStarted, sensor: " + mSensorId + ", userId: " + userId); @@ -76,19 +87,30 @@ public class BiometricTestSession implements AutoCloseable { mUsersCleaningUp.remove(userId); if (mUsersCleaningUp.isEmpty() && mCloseLatch != null) { + Log.d(getTag(), "counting down"); mCloseLatch.countDown(); } } - }; + } /** * @hide */ - public BiometricTestSession(@NonNull Context context, int sensorId, - @NonNull TestSessionProvider testSessionProvider) throws RemoteException { - mContext = context; + public BiometricTestSession(@NonNull Context context, List<SensorProperties> sensors, + int sensorId, @NonNull TestSessionProvider testSessionProvider) throws RemoteException { mSensorId = sensorId; - mTestSession = testSessionProvider.createTestSession(context, sensorId, mCallback); + // When any of the sensors should create the test session, all the other sensors should + // set test hal enabled too. + for (SensorProperties sensor : sensors) { + final int id = sensor.getSensorId(); + final ITestSession session = testSessionProvider.createTestSession(context, id, + new TestSessionCallbackIml(id)); + mTestSessionsForAllSensors.add(session); + if (id == sensorId) { + mTestSession = session; + } + } + mTestedUsers = new ArraySet<>(); mUsersCleaningUp = new ArraySet<>(); setTestHalEnabled(true); @@ -107,8 +129,11 @@ public class BiometricTestSession implements AutoCloseable { @RequiresPermission(TEST_BIOMETRIC) private void setTestHalEnabled(boolean enabled) { try { - Log.w(getTag(), "setTestHalEnabled, sensor: " + mSensorId + " enabled: " + enabled); - mTestSession.setTestHalEnabled(enabled); + for (ITestSession session : mTestSessionsForAllSensors) { + Log.w(getTag(), "setTestHalEnabled, sensor: " + session.getSensorId() + " enabled: " + + enabled); + session.setTestHalEnabled(enabled); + } } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -175,10 +200,12 @@ public class BiometricTestSession implements AutoCloseable { /** * Simulates an acquired message from the HAL. * - * @param userId User that this command applies to. + * @param userId User that this command applies to. * @param acquireInfo See - * {@link BiometricPrompt.AuthenticationCallback#onAuthenticationAcquired(int)} and - * {@link FingerprintManager.AuthenticationCallback#onAuthenticationAcquired(int)} + * {@link + * BiometricPrompt.AuthenticationCallback#onAuthenticationAcquired(int)} and + * {@link + * FingerprintManager.AuthenticationCallback#onAuthenticationAcquired(int)} */ @RequiresPermission(TEST_BIOMETRIC) public void notifyAcquired(int userId, int acquireInfo) { @@ -192,10 +219,12 @@ public class BiometricTestSession implements AutoCloseable { /** * Simulates an error message from the HAL. * - * @param userId User that this command applies to. + * @param userId User that this command applies to. * @param errorCode See - * {@link BiometricPrompt.AuthenticationCallback#onAuthenticationError(int, CharSequence)} and - * {@link FingerprintManager.AuthenticationCallback#onAuthenticationError(int, CharSequence)} + * {@link BiometricPrompt.AuthenticationCallback#onAuthenticationError(int, + * CharSequence)} and + * {@link FingerprintManager.AuthenticationCallback#onAuthenticationError(int, + * CharSequence)} */ @RequiresPermission(TEST_BIOMETRIC) public void notifyError(int userId, int errorCode) { @@ -220,8 +249,20 @@ public class BiometricTestSession implements AutoCloseable { Log.w(getTag(), "Cleanup already in progress for user: " + userId); } - mUsersCleaningUp.add(userId); - mTestSession.cleanupInternalState(userId); + for (ITestSession session : mTestSessionsForAllSensors) { + mUsersCleaningUp.add(userId); + Log.d(getTag(), "cleanupInternalState for sensor: " + session.getSensorId()); + mCloseLatch = new CountDownLatch(1); + session.cleanupInternalState(userId); + + try { + Log.d(getTag(), "Awaiting latch..."); + mCloseLatch.await(3, TimeUnit.SECONDS); + Log.d(getTag(), "Finished awaiting"); + } catch (InterruptedException e) { + Log.e(getTag(), "Latch interrupted", e); + } + } } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -234,18 +275,9 @@ public class BiometricTestSession implements AutoCloseable { // Cleanup can be performed using the test HAL, since it always responds to enumerate with // zero enrollments. if (!mTestedUsers.isEmpty()) { - mCloseLatch = new CountDownLatch(1); for (int user : mTestedUsers) { cleanupInternalState(user); } - - try { - Log.d(getTag(), "Awaiting latch..."); - mCloseLatch.await(3, TimeUnit.SECONDS); - Log.d(getTag(), "Finished awaiting"); - } catch (InterruptedException e) { - Log.e(getTag(), "Latch interrupted", e); - } } if (!mUsersCleaningUp.isEmpty()) { diff --git a/core/java/android/hardware/biometrics/ITestSession.aidl b/core/java/android/hardware/biometrics/ITestSession.aidl index df9f504a2c05..bd99606808b7 100644 --- a/core/java/android/hardware/biometrics/ITestSession.aidl +++ b/core/java/android/hardware/biometrics/ITestSession.aidl @@ -59,4 +59,8 @@ interface ITestSession { // HAL is disabled (e.g. to clean up after a test). @EnforcePermission("TEST_BIOMETRIC") void cleanupInternalState(int userId); + + // Get the sensor id of the current test session. + @EnforcePermission("TEST_BIOMETRIC") + int getSensorId(); } diff --git a/core/java/android/hardware/biometrics/PromptInfo.java b/core/java/android/hardware/biometrics/PromptInfo.java index 901f6b7ba5c1..df5d864196b4 100644 --- a/core/java/android/hardware/biometrics/PromptInfo.java +++ b/core/java/android/hardware/biometrics/PromptInfo.java @@ -57,7 +57,8 @@ public class PromptInfo implements Parcelable { private boolean mIsForLegacyFingerprintManager = false; private boolean mShowEmergencyCallButton = false; private boolean mUseParentProfileForDeviceCredential = false; - private ComponentName mComponentNameForConfirmDeviceCredentialActivity = null; + private ComponentName mRealCallerForConfirmDeviceCredentialActivity = null; + private String mClassNameIfItIsConfirmDeviceCredentialActivity = null; public PromptInfo() { @@ -89,8 +90,9 @@ public class PromptInfo implements Parcelable { mIsForLegacyFingerprintManager = in.readBoolean(); mShowEmergencyCallButton = in.readBoolean(); mUseParentProfileForDeviceCredential = in.readBoolean(); - mComponentNameForConfirmDeviceCredentialActivity = in.readParcelable( + mRealCallerForConfirmDeviceCredentialActivity = in.readParcelable( ComponentName.class.getClassLoader(), ComponentName.class); + mClassNameIfItIsConfirmDeviceCredentialActivity = in.readString(); } public static final Creator<PromptInfo> CREATOR = new Creator<PromptInfo>() { @@ -136,7 +138,8 @@ public class PromptInfo implements Parcelable { dest.writeBoolean(mIsForLegacyFingerprintManager); dest.writeBoolean(mShowEmergencyCallButton); dest.writeBoolean(mUseParentProfileForDeviceCredential); - dest.writeParcelable(mComponentNameForConfirmDeviceCredentialActivity, 0); + dest.writeParcelable(mRealCallerForConfirmDeviceCredentialActivity, 0); + dest.writeString(mClassNameIfItIsConfirmDeviceCredentialActivity); } // LINT.IfChange @@ -155,7 +158,7 @@ public class PromptInfo implements Parcelable { return true; } else if (mShowEmergencyCallButton) { return true; - } else if (mComponentNameForConfirmDeviceCredentialActivity != null) { + } else if (mRealCallerForConfirmDeviceCredentialActivity != null) { return true; } return false; @@ -321,10 +324,8 @@ public class PromptInfo implements Parcelable { mShowEmergencyCallButton = showEmergencyCallButton; } - public void setComponentNameForConfirmDeviceCredentialActivity( - ComponentName componentNameForConfirmDeviceCredentialActivity) { - mComponentNameForConfirmDeviceCredentialActivity = - componentNameForConfirmDeviceCredentialActivity; + public void setRealCallerForConfirmDeviceCredentialActivity(ComponentName realCaller) { + mRealCallerForConfirmDeviceCredentialActivity = realCaller; } public void setUseParentProfileForDeviceCredential( @@ -332,6 +333,14 @@ public class PromptInfo implements Parcelable { mUseParentProfileForDeviceCredential = useParentProfileForDeviceCredential; } + /** + * Set the class name of ConfirmDeviceCredentialActivity. + */ + void setClassNameIfItIsConfirmDeviceCredentialActivity(String className) { + mClassNameIfItIsConfirmDeviceCredentialActivity = className; + } + + // Getters /** @@ -455,8 +464,15 @@ public class PromptInfo implements Parcelable { return mShowEmergencyCallButton; } - public ComponentName getComponentNameForConfirmDeviceCredentialActivity() { - return mComponentNameForConfirmDeviceCredentialActivity; + public ComponentName getRealCallerForConfirmDeviceCredentialActivity() { + return mRealCallerForConfirmDeviceCredentialActivity; } + /** + * Get the class name of ConfirmDeviceCredentialActivity. Returns null if the direct caller is + * not ConfirmDeviceCredentialActivity. + */ + public String getClassNameIfItIsConfirmDeviceCredentialActivity() { + return mClassNameIfItIsConfirmDeviceCredentialActivity; + } } diff --git a/core/java/android/hardware/camera2/params/SessionConfiguration.java b/core/java/android/hardware/camera2/params/SessionConfiguration.java index 0c55ed5323a0..9bd4860e7ccc 100644 --- a/core/java/android/hardware/camera2/params/SessionConfiguration.java +++ b/core/java/android/hardware/camera2/params/SessionConfiguration.java @@ -17,8 +17,6 @@ package android.hardware.camera2.params; -import static com.android.internal.util.Preconditions.*; - import android.annotation.CallbackExecutor; import android.annotation.FlaggedApi; import android.annotation.IntDef; @@ -32,8 +30,6 @@ import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CameraDevice.CameraDeviceSetup; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.impl.CameraMetadataNative; -import android.hardware.camera2.params.InputConfiguration; -import android.hardware.camera2.params.OutputConfiguration; import android.hardware.camera2.utils.HashCodeHelpers; import android.media.ImageReader; import android.os.Parcel; @@ -46,6 +42,7 @@ import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.concurrent.Executor; /** @@ -95,8 +92,8 @@ public final class SessionConfiguration implements Parcelable { public @interface SessionMode {}; // Camera capture session related parameters. - private List<OutputConfiguration> mOutputConfigurations; - private CameraCaptureSession.StateCallback mStateCallback; + private final @NonNull List<OutputConfiguration> mOutputConfigurations; + private CameraCaptureSession.StateCallback mStateCallback = null; private int mSessionType; private Executor mExecutor = null; private InputConfiguration mInputConfig = null; @@ -268,7 +265,8 @@ public final class SessionConfiguration implements Parcelable { */ @Override public int hashCode() { - return HashCodeHelpers.hashCode(mOutputConfigurations.hashCode(), mInputConfig.hashCode(), + return HashCodeHelpers.hashCode(mOutputConfigurations.hashCode(), + Objects.hashCode(mInputConfig), mSessionType); } diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index 903e91646332..7f1cac08b430 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -172,7 +172,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing @RequiresPermission(TEST_BIOMETRIC) public BiometricTestSession createTestSession(int sensorId) { try { - return new BiometricTestSession(mContext, sensorId, + return new BiometricTestSession(mContext, getSensorProperties(), sensorId, (context, sensorId1, callback) -> mService .createTestSession(sensorId1, callback, context.getOpPackageName())); } catch (RemoteException e) { diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index de3984756416..ff737a4f9fb8 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -58,8 +58,6 @@ import static android.view.inputmethod.Flags.FLAG_CONNECTIONLESS_HANDWRITING; import static android.view.inputmethod.Flags.ctrlShiftShortcut; import static android.view.inputmethod.Flags.predictiveBackIme; -import static java.lang.annotation.RetentionPolicy.SOURCE; - import android.annotation.AnyThread; import android.annotation.CallSuper; import android.annotation.DrawableRes; @@ -500,36 +498,53 @@ public class InputMethodService extends AbstractInputMethodService { public static final int BACK_DISPOSITION_ADJUST_NOTHING = 3; /** - * Enum flag to be used for {@link #setBackDisposition(int)}. + * The disposition mode that indicates the expected affordance for the back button. * * @hide */ - @Retention(SOURCE) - @IntDef(value = {BACK_DISPOSITION_DEFAULT, BACK_DISPOSITION_WILL_NOT_DISMISS, - BACK_DISPOSITION_WILL_DISMISS, BACK_DISPOSITION_ADJUST_NOTHING}, - prefix = "BACK_DISPOSITION_") + @IntDef(prefix = { "BACK_DISPOSITION_" }, value = { + BACK_DISPOSITION_DEFAULT, + BACK_DISPOSITION_WILL_NOT_DISMISS, + BACK_DISPOSITION_WILL_DISMISS, + BACK_DISPOSITION_ADJUST_NOTHING, + }) + @Retention(RetentionPolicy.SOURCE) public @interface BackDispositionMode {} /** + * The IME is active, and ready to accept touch/key events. It may or may not be visible. + * * @hide - * The IME is active. It may or may not be visible. */ - public static final int IME_ACTIVE = 0x1; + public static final int IME_ACTIVE = 1 << 0; /** - * @hide * The IME is perceptibly visible to the user. + * + * @hide */ - public static final int IME_VISIBLE = 0x2; + public static final int IME_VISIBLE = 1 << 1; /** - * @hide * The IME is visible, but not yet perceptible to the user (e.g. fading in) * by {@link android.view.WindowInsetsController}. * * @see InputMethodManager#reportPerceptible + * @hide + */ + public static final int IME_VISIBLE_IMPERCEPTIBLE = 1 << 2; + + /** + * The IME window visibility state. + * + * @hide */ - public static final int IME_VISIBLE_IMPERCEPTIBLE = 0x4; + @IntDef(flag = true, prefix = { "IME_" }, value = { + IME_ACTIVE, + IME_VISIBLE, + IME_VISIBLE_IMPERCEPTIBLE, + }) + public @interface ImeWindowVisibility {} // Min and max values for back disposition. private static final int BACK_DISPOSITION_MIN = BACK_DISPOSITION_DEFAULT; @@ -1342,7 +1357,8 @@ public class InputMethodService extends AbstractInputMethodService { mImeSurfaceRemoverRunnable = null; } - private void setImeWindowStatus(int visibilityFlags, int backDisposition) { + private void setImeWindowStatus(@ImeWindowVisibility int visibilityFlags, + @BackDispositionMode int backDisposition) { mPrivOps.setImeWindowStatusAsync(visibilityFlags, backDisposition); } @@ -3301,7 +3317,7 @@ public class InputMethodService extends AbstractInputMethodService { ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_IME_HIDE_WINDOW); ImeTracing.getInstance().triggerServiceDump("InputMethodService#hideWindow", mDumper, null /* icProto */); - setImeWindowStatus(0, mBackDisposition); + setImeWindowStatus(0 /* visibilityFlags */, mBackDisposition); if (android.view.inputmethod.Flags.refactorInsetsController()) { // The ImeInsetsSourceProvider need the statsToken when dispatching the control. We // send the token here, so that another request in the provider can be cancelled. @@ -4476,6 +4492,7 @@ public class InputMethodService extends AbstractInputMethodService { }; } + @ImeWindowVisibility private int mapToImeWindowStatus() { return IME_ACTIVE | (isInputViewShown() ? IME_VISIBLE : 0); diff --git a/core/java/android/os/TEST_MAPPING b/core/java/android/os/TEST_MAPPING index 2fde5e7eff6a..449a52fd98de 100644 --- a/core/java/android/os/TEST_MAPPING +++ b/core/java/android/os/TEST_MAPPING @@ -73,11 +73,7 @@ "PowerComponents\\.java", "[^/]*BatteryConsumer[^/]*\\.java" ], - "name": "FrameworksCoreTests", - "options": [ - { "include-filter": "com.android.internal.os.BatteryStatsTests" }, - { "exclude-annotation": "com.android.internal.os.SkipPresubmit" } - ] + "name": "FrameworksCoreTests_battery_stats" }, { "file_patterns": [ @@ -132,12 +128,7 @@ }, { "file_patterns": ["Environment[^/]*\\.java"], - "name": "FrameworksCoreTests", - "options": [ - { - "include-filter": "android.os.EnvironmentTest" - } - ] + "name": "FrameworksCoreTests_environment" } ], "postsubmit": [ diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java index edb3a641f107..4a37e0a70443 100644 --- a/core/java/android/os/Trace.java +++ b/core/java/android/os/Trace.java @@ -520,8 +520,20 @@ public final class Trace { * @param counterValue The counter value. */ public static void setCounter(@NonNull String counterName, long counterValue) { - if (isTagEnabled(TRACE_TAG_APP)) { - nativeTraceCounter(TRACE_TAG_APP, counterName, counterValue); + setCounter(TRACE_TAG_APP, counterName, counterValue); + } + + /** + * Writes trace message to indicate the value of a given counter under a given trace tag. + * + * @param traceTag The trace tag. + * @param counterName The counter name to appear in the trace. + * @param counterValue The counter value. + * @hide + */ + public static void setCounter(long traceTag, @NonNull String counterName, long counterValue) { + if (isTagEnabled(traceTag)) { + nativeTraceCounter(traceTag, counterName, counterValue); } } diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 06c516aee8f3..28f2c2530ae9 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -4824,6 +4824,7 @@ public class UserManager { * <p>Note that this does not alter the user's pre-existing user restrictions. * * @param userId the id of the user to become admin + * @throws SecurityException if changing ADMIN status of the user is not allowed * @hide */ @RequiresPermission(allOf = { @@ -4844,6 +4845,7 @@ public class UserManager { * <p>Note that this does not alter the user's pre-existing user restrictions. * * @param userId the id of the user to revoke admin rights from + * @throws SecurityException if changing ADMIN status of the user is not allowed * @hide */ @RequiresPermission(allOf = { diff --git a/core/java/android/os/vibrator/persistence/VibrationXmlSerializer.java b/core/java/android/os/vibrator/persistence/VibrationXmlSerializer.java index a26c6f434e15..a95ce7914d8b 100644 --- a/core/java/android/os/vibrator/persistence/VibrationXmlSerializer.java +++ b/core/java/android/os/vibrator/persistence/VibrationXmlSerializer.java @@ -104,7 +104,7 @@ public final class VibrationXmlSerializer { public static void serialize(@NonNull VibrationEffect effect, @NonNull Writer writer, @Flags int flags) throws IOException { // Serialize effect first to fail early. - XmlSerializedVibration<VibrationEffect> serializedVibration = + XmlSerializedVibration<? extends VibrationEffect> serializedVibration = toSerializedVibration(effect, flags); TypedXmlSerializer xmlSerializer = Xml.newFastSerializer(); xmlSerializer.setFeature(XML_FEATURE_INDENT_OUTPUT, (flags & FLAG_PRETTY_PRINT) != 0); @@ -114,9 +114,9 @@ public final class VibrationXmlSerializer { xmlSerializer.endDocument(); } - private static XmlSerializedVibration<VibrationEffect> toSerializedVibration( + private static XmlSerializedVibration<? extends VibrationEffect> toSerializedVibration( VibrationEffect effect, @Flags int flags) throws SerializationFailedException { - XmlSerializedVibration<VibrationEffect> serializedVibration; + XmlSerializedVibration<? extends VibrationEffect> serializedVibration; int serializerFlags = 0; if ((flags & FLAG_ALLOW_HIDDEN_APIS) != 0) { serializerFlags |= XmlConstants.FLAG_ALLOW_HIDDEN_APIS; diff --git a/core/java/android/security/net/config/SystemCertificateSource.java b/core/java/android/security/net/config/SystemCertificateSource.java index 3a254c1d92fc..bdda42a389eb 100644 --- a/core/java/android/security/net/config/SystemCertificateSource.java +++ b/core/java/android/security/net/config/SystemCertificateSource.java @@ -19,6 +19,8 @@ package android.security.net.config; import android.os.Environment; import android.os.UserHandle; +import com.android.internal.util.ArrayUtils; + import java.io.File; /** @@ -45,7 +47,7 @@ public final class SystemCertificateSource extends DirectoryCertificateSource { } File updatable_dir = new File("/apex/com.android.conscrypt/cacerts"); if (updatable_dir.exists() - && !(updatable_dir.list().length == 0)) { + && !(ArrayUtils.isEmpty(updatable_dir.list()))) { return updatable_dir; } return new File(System.getenv("ANDROID_ROOT") + "/etc/security/cacerts"); diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java index 06e53ac8e7a2..133b3d1add49 100644 --- a/core/java/android/service/dreams/DreamService.java +++ b/core/java/android/service/dreams/DreamService.java @@ -267,10 +267,10 @@ public class DreamService extends Service implements Window.Callback { private boolean mDozing; private boolean mWindowless; private boolean mPreviewMode; - private volatile int mDozeScreenState = Display.STATE_UNKNOWN; - private volatile @Display.StateReason int mDozeScreenStateReason = Display.STATE_REASON_UNKNOWN; - private volatile int mDozeScreenBrightness = PowerManager.BRIGHTNESS_DEFAULT; - private volatile float mDozeScreenBrightnessFloat = PowerManager.BRIGHTNESS_INVALID_FLOAT; + private int mDozeScreenState = Display.STATE_UNKNOWN; + private @Display.StateReason int mDozeScreenStateReason = Display.STATE_REASON_UNKNOWN; + private int mDozeScreenBrightness = PowerManager.BRIGHTNESS_DEFAULT; + private float mDozeScreenBrightnessFloat = PowerManager.BRIGHTNESS_INVALID_FLOAT; private boolean mDebug = false; @@ -913,13 +913,15 @@ public class DreamService extends Service implements Window.Callback { */ @UnsupportedAppUsage public void startDozing() { - if (mCanDoze && !mDozing) { - mDozing = true; - updateDoze(); + synchronized (this) { + if (mCanDoze && !mDozing) { + mDozing = true; + updateDoze(); + } } } - private void updateDoze() { + private synchronized void updateDoze() { if (mDreamToken == null) { Slog.w(mTag, "Updating doze without a dream token."); return; @@ -927,6 +929,9 @@ public class DreamService extends Service implements Window.Callback { if (mDozing) { try { + Slog.v(mTag, "UpdateDoze mDozeScreenState=" + mDozeScreenState + + " mDozeScreenBrightness=" + mDozeScreenBrightness + + " mDozeScreenBrightnessFloat=" + mDozeScreenBrightnessFloat); if (startAndStopDozingInBackground()) { mDreamManager.startDozingOneway( mDreamToken, mDozeScreenState, mDozeScreenStateReason, @@ -1048,10 +1053,12 @@ public class DreamService extends Service implements Window.Callback { */ @UnsupportedAppUsage public void setDozeScreenState(int state, @Display.StateReason int reason) { - if (mDozeScreenState != state) { - mDozeScreenState = state; - mDozeScreenStateReason = reason; - updateDoze(); + synchronized (this) { + if (mDozeScreenState != state) { + mDozeScreenState = state; + mDozeScreenStateReason = reason; + updateDoze(); + } } } @@ -1103,9 +1110,11 @@ public class DreamService extends Service implements Window.Callback { if (brightness != PowerManager.BRIGHTNESS_DEFAULT) { brightness = clampAbsoluteBrightness(brightness); } - if (mDozeScreenBrightness != brightness) { - mDozeScreenBrightness = brightness; - updateDoze(); + synchronized (this) { + if (mDozeScreenBrightness != brightness) { + mDozeScreenBrightness = brightness; + updateDoze(); + } } } @@ -1141,9 +1150,12 @@ public class DreamService extends Service implements Window.Callback { if (!Float.isNaN(brightness)) { brightness = clampAbsoluteBrightnessFloat(brightness); } - if (!BrightnessSynchronizer.floatEquals(mDozeScreenBrightnessFloat, brightness)) { - mDozeScreenBrightnessFloat = brightness; - updateDoze(); + + synchronized (this) { + if (!BrightnessSynchronizer.floatEquals(mDozeScreenBrightnessFloat, brightness)) { + mDozeScreenBrightnessFloat = brightness; + updateDoze(); + } } } diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java index 224379b4fd98..fc6c2e88779f 100644 --- a/core/java/android/service/notification/ZenModeConfig.java +++ b/core/java/android/service/notification/ZenModeConfig.java @@ -25,7 +25,6 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF; import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; -import static android.service.notification.Condition.STATE_TRUE; import static android.service.notification.SystemZenRules.PACKAGE_ANDROID; import static android.service.notification.ZenAdapters.peopleTypeToPrioritySenders; import static android.service.notification.ZenAdapters.prioritySendersToPeopleType; @@ -76,8 +75,9 @@ import android.util.PluralsMessageFormatter; import android.util.Slog; import android.util.proto.ProtoOutputStream; +import androidx.annotation.VisibleForTesting; + import com.android.internal.R; -import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.XmlUtils; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; @@ -1074,7 +1074,7 @@ public class ZenModeConfig implements Parcelable { rt.manualRule.type = AutomaticZenRule.TYPE_OTHER; rt.manualRule.condition = new Condition( rt.manualRule.conditionId != null ? rt.manualRule.conditionId - : Uri.EMPTY, "", STATE_TRUE); + : Uri.EMPTY, "", Condition.STATE_TRUE); } } return rt; @@ -2540,10 +2540,34 @@ public class ZenModeConfig implements Parcelable { } public static class ZenRule implements Parcelable { + + /** No manual override. Rule owner can decide its state. */ + public static final int OVERRIDE_NONE = 0; + /** + * User has manually activated a mode. This will temporarily overrule the rule owner's + * decision to deactivate it (see {@link #reconsiderConditionOverride}). + */ + public static final int OVERRIDE_ACTIVATE = 1; + /** + * User has manually deactivated an active mode, or setting ZEN_MODE_OFF (for the few apps + * still allowed to do that) snoozed the mode. This will temporarily overrule the rule + * owner's decision to activate it (see {@link #reconsiderConditionOverride}). + */ + public static final int OVERRIDE_DEACTIVATE = 2; + + @IntDef(prefix = { "OVERRIDE" }, value = { + OVERRIDE_NONE, + OVERRIDE_ACTIVATE, + OVERRIDE_DEACTIVATE + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ConditionOverride {} + @UnsupportedAppUsage public boolean enabled; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public boolean snoozing; // user manually disabled this instance + @Deprecated + public boolean snoozing; // user manually disabled this instance. Obsolete with MODES_UI @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public String name; // required for automatic @UnsupportedAppUsage @@ -2579,6 +2603,15 @@ public class ZenModeConfig implements Parcelable { // ZenPolicy, so we store them here, only for the manual rule. @FlaggedApi(Flags.FLAG_MODES_UI) int legacySuppressedEffects; + /** + * Signals a user's action to (temporarily or permanently) activate or deactivate this + * rule, overruling the condition set by the owner. This value is not stored to disk, as + * it shouldn't survive reboots or be involved in B&R. It might be reset by certain + * owner-provided state transitions as well. + */ + @FlaggedApi(Flags.FLAG_MODES_UI) + @ConditionOverride + int conditionOverride = OVERRIDE_NONE; public ZenRule() { } @@ -2620,6 +2653,7 @@ public class ZenModeConfig implements Parcelable { if (Flags.modesUi()) { disabledOrigin = source.readInt(); legacySuppressedEffects = source.readInt(); + conditionOverride = source.readInt(); } } } @@ -2698,6 +2732,7 @@ public class ZenModeConfig implements Parcelable { if (Flags.modesUi()) { dest.writeInt(disabledOrigin); dest.writeInt(legacySuppressedEffects); + dest.writeInt(conditionOverride); } } } @@ -2708,9 +2743,16 @@ public class ZenModeConfig implements Parcelable { .append("id=").append(id) .append(",state=").append(condition == null ? "STATE_FALSE" : Condition.stateToString(condition.state)) - .append(",enabled=").append(String.valueOf(enabled).toUpperCase()) - .append(",snoozing=").append(snoozing) - .append(",name=").append(name) + .append(",enabled=").append(String.valueOf(enabled).toUpperCase()); + + if (Flags.modesUi()) { + sb.append(",conditionOverride=") + .append(conditionOverrideToString(conditionOverride)); + } else { + sb.append(",snoozing=").append(snoozing); + } + + sb.append(",name=").append(name) .append(",zenMode=").append(Global.zenModeToString(zenMode)) .append(",conditionId=").append(conditionId) .append(",pkg=").append(pkg) @@ -2753,6 +2795,15 @@ public class ZenModeConfig implements Parcelable { return sb.append(']').toString(); } + private static String conditionOverrideToString(@ConditionOverride int value) { + return switch(value) { + case OVERRIDE_ACTIVATE -> "OVERRIDE_ACTIVATE"; + case OVERRIDE_DEACTIVATE -> "OVERRIDE_DEACTIVATE"; + case OVERRIDE_NONE -> "OVERRIDE_NONE"; + default -> "UNKNOWN"; + }; + } + /** @hide */ // TODO: add configuration activity public void dumpDebug(ProtoOutputStream proto, long fieldId) { @@ -2763,7 +2814,11 @@ public class ZenModeConfig implements Parcelable { proto.write(ZenRuleProto.CREATION_TIME_MS, creationTime); proto.write(ZenRuleProto.ENABLED, enabled); proto.write(ZenRuleProto.ENABLER, enabler); - proto.write(ZenRuleProto.IS_SNOOZING, snoozing); + if (Flags.modesApi() && Flags.modesUi()) { + proto.write(ZenRuleProto.IS_SNOOZING, conditionOverride == OVERRIDE_DEACTIVATE); + } else { + proto.write(ZenRuleProto.IS_SNOOZING, snoozing); + } proto.write(ZenRuleProto.ZEN_MODE, zenMode); if (conditionId != null) { proto.write(ZenRuleProto.CONDITION_ID, conditionId.toString()); @@ -2816,7 +2871,8 @@ public class ZenModeConfig implements Parcelable { if (Flags.modesUi()) { finalEquals = finalEquals && other.disabledOrigin == disabledOrigin - && other.legacySuppressedEffects == legacySuppressedEffects; + && other.legacySuppressedEffects == legacySuppressedEffects + && other.conditionOverride == conditionOverride; } } @@ -2832,7 +2888,8 @@ public class ZenModeConfig implements Parcelable { zenDeviceEffects, modified, allowManualInvocation, iconResName, triggerDescription, type, userModifiedFields, zenPolicyUserModifiedFields, zenDeviceEffectsUserModifiedFields, - deletionInstant, disabledOrigin, legacySuppressedEffects); + deletionInstant, disabledOrigin, legacySuppressedEffects, + conditionOverride); } else { return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition, component, configurationActivity, pkg, id, enabler, zenPolicy, @@ -2858,8 +2915,74 @@ public class ZenModeConfig implements Parcelable { } } + // TODO: b/333527800 - Rename to isActive() public boolean isAutomaticActive() { - return enabled && !snoozing && getPkg() != null && isTrueOrUnknown(); + if (Flags.modesApi() && Flags.modesUi()) { + if (!enabled || getPkg() == null) { + return false; + } else if (conditionOverride == OVERRIDE_ACTIVATE) { + return true; + } else if (conditionOverride == OVERRIDE_DEACTIVATE) { + return false; + } else { + return isTrueOrUnknown(); + } + } else { + return enabled && !snoozing && getPkg() != null && isTrueOrUnknown(); + } + } + + @VisibleForTesting(otherwise = VisibleForTesting.NONE) + @ConditionOverride + public int getConditionOverride() { + if (Flags.modesApi() && Flags.modesUi()) { + return conditionOverride; + } else { + return snoozing ? OVERRIDE_DEACTIVATE : OVERRIDE_NONE; + } + } + + public void setConditionOverride(@ConditionOverride int value) { + if (Flags.modesApi() && Flags.modesUi()) { + conditionOverride = value; + } else { + if (value == OVERRIDE_ACTIVATE) { + Slog.wtf(TAG, "Shouldn't set OVERRIDE_ACTIVATE if MODES_UI is off"); + } else if (value == OVERRIDE_DEACTIVATE) { + snoozing = true; + } else if (value == OVERRIDE_NONE) { + snoozing = false; + } + } + } + + public void resetConditionOverride() { + setConditionOverride(OVERRIDE_NONE); + } + + /** + * Possibly remove the override, depending on the rule owner's intended state. + * + * <p>This allows rule owners to "take over" manually-provided state with their smartness, + * but only once both agree. + * + * <p>For example, a manually activated rule wins over rule owner's opinion that it should + * be off, until the owner says it should be on, at which point it will turn off (without + * manual intervention) when the rule owner says it should be off. And symmetrically for + * manual deactivation (which used to be called "snoozing"). + */ + public void reconsiderConditionOverride() { + if (Flags.modesApi() && Flags.modesUi()) { + if (conditionOverride == OVERRIDE_ACTIVATE && isTrueOrUnknown()) { + resetConditionOverride(); + } else if (conditionOverride == OVERRIDE_DEACTIVATE && !isTrueOrUnknown()) { + resetConditionOverride(); + } + } else { + if (snoozing && !isTrueOrUnknown()) { + snoozing = false; + } + } } public String getPkg() { diff --git a/core/java/android/service/notification/ZenModeDiff.java b/core/java/android/service/notification/ZenModeDiff.java index a37e2277f0d1..05c2a9c26709 100644 --- a/core/java/android/service/notification/ZenModeDiff.java +++ b/core/java/android/service/notification/ZenModeDiff.java @@ -454,6 +454,8 @@ public class ZenModeDiff { */ public static class RuleDiff extends BaseDiff { public static final String FIELD_ENABLED = "enabled"; + public static final String FIELD_CONDITION_OVERRIDE = "conditionOverride"; + @Deprecated public static final String FIELD_SNOOZING = "snoozing"; public static final String FIELD_NAME = "name"; public static final String FIELD_ZEN_MODE = "zenMode"; @@ -507,8 +509,15 @@ public class ZenModeDiff { if (from.enabled != to.enabled) { addField(FIELD_ENABLED, new FieldDiff<>(from.enabled, to.enabled)); } - if (from.snoozing != to.snoozing) { - addField(FIELD_SNOOZING, new FieldDiff<>(from.snoozing, to.snoozing)); + if (Flags.modesApi() && Flags.modesUi()) { + if (from.conditionOverride != to.conditionOverride) { + addField(FIELD_CONDITION_OVERRIDE, + new FieldDiff<>(from.conditionOverride, to.conditionOverride)); + } + } else { + if (from.snoozing != to.snoozing) { + addField(FIELD_SNOOZING, new FieldDiff<>(from.snoozing, to.snoozing)); + } } if (!Objects.equals(from.name, to.name)) { addField(FIELD_NAME, new FieldDiff<>(from.name, to.name)); diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig index 4d176f2939a8..bb3f6c9928ac 100644 --- a/core/java/android/text/flags/flags.aconfig +++ b/core/java/android/text/flags/flags.aconfig @@ -277,13 +277,3 @@ flag { purpose: PURPOSE_BUGFIX } } - -flag { - name: "typeface_cache_for_var_settings" - namespace: "text" - description: "Cache Typeface instance for font variation settings." - bug: "355462362" - metadata { - purpose: PURPOSE_BUGFIX - } -}
\ No newline at end of file diff --git a/core/java/android/util/TEST_MAPPING b/core/java/android/util/TEST_MAPPING index c681f86ce439..64b2e6eeccc7 100644 --- a/core/java/android/util/TEST_MAPPING +++ b/core/java/android/util/TEST_MAPPING @@ -1,27 +1,11 @@ { "presubmit": [ { - "name": "FrameworksCoreTests", - "options": [ - { - "include-filter": "android.util.CharsetUtilsTest" - }, - { - "include-filter": "com.android.internal.util.FastDataTest" - } - ], + "name": "FrameworksCoreTests_util_data_charset", "file_patterns": ["CharsetUtils|FastData"] }, { - "name": "FrameworksCoreTests", - "options": [ - { - "include-filter": "android.util.XmlTest" - }, - { - "include-filter": "android.util.BinaryXmlTest" - } - ], + "name": "FrameworksCoreTests_xml", "file_patterns": ["Xml"] } ], diff --git a/core/java/android/util/apk/TEST_MAPPING b/core/java/android/util/apk/TEST_MAPPING index 7668eec474ab..3ae470ad8a95 100644 --- a/core/java/android/util/apk/TEST_MAPPING +++ b/core/java/android/util/apk/TEST_MAPPING @@ -1,12 +1,7 @@ { "presubmit": [ { - "name": "FrameworksCoreTests", - "options": [ - { - "include-filter": "android.util.apk.SourceStampVerifierTest" - } - ] + "name": "FrameworksCoreTests_util_apk" } ], "presubmit-large": [ diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index a7641c07bb90..9e4b27d3fa55 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -3574,7 +3574,7 @@ public final class SurfaceControl implements Parcelable { checkPreconditions(sc); if (SurfaceControlRegistry.sCallStackDebuggingEnabled) { SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging( - "reparent", this, sc, + "setColor", this, sc, "r=" + color[0] + " g=" + color[1] + " b=" + color[2]); } nativeSetColor(mNativeObject, sc.mNativeObject, color); diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index dbd65de32471..3088fd3094b0 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -16444,7 +16444,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED) { try { - Trace.traceBegin(TRACE_TAG_VIEW, "View.onTouchListener#onTouch"); + if (Trace.isTagEnabled(TRACE_TAG_VIEW)) { + Trace.traceBegin(TRACE_TAG_VIEW, + "View.onTouchListener#onTouch - " + getClass().getSimpleName() + + ", eventId - " + event.getId()); + } handled = li.mOnTouchListener.onTouch(this, event); } finally { Trace.traceEnd(TRACE_TAG_VIEW); diff --git a/core/java/android/view/animation/Animation.java b/core/java/android/view/animation/Animation.java index 09306c791537..288be9c392e1 100644 --- a/core/java/android/view/animation/Animation.java +++ b/core/java/android/view/animation/Animation.java @@ -28,6 +28,7 @@ import android.os.Handler; import android.os.SystemProperties; import android.util.AttributeSet; import android.util.TypedValue; +import android.view.WindowInsets; import dalvik.system.CloseGuard; @@ -881,12 +882,13 @@ public abstract class Animation implements Cloneable { } /** - * @return if a window animation has outsets applied to it. + * @return the edges to which outsets should be applied if run as a windoow animation. * * @hide */ - public boolean hasExtension() { - return false; + @WindowInsets.Side.InsetsSide + public int getExtensionEdges() { + return 0x0; } /** diff --git a/core/java/android/view/animation/AnimationSet.java b/core/java/android/view/animation/AnimationSet.java index 5aaa994f3f8f..bbdc9d0392ba 100644 --- a/core/java/android/view/animation/AnimationSet.java +++ b/core/java/android/view/animation/AnimationSet.java @@ -21,6 +21,7 @@ import android.content.res.TypedArray; import android.graphics.RectF; import android.os.Build; import android.util.AttributeSet; +import android.view.WindowInsets; import java.util.ArrayList; import java.util.List; @@ -540,12 +541,12 @@ public class AnimationSet extends Animation { /** @hide */ @Override - public boolean hasExtension() { + @WindowInsets.Side.InsetsSide + public int getExtensionEdges() { + int edge = 0x0; for (Animation animation : mAnimations) { - if (animation.hasExtension()) { - return true; - } + edge |= animation.getExtensionEdges(); } - return false; + return edge; } } diff --git a/core/java/android/view/animation/ExtendAnimation.java b/core/java/android/view/animation/ExtendAnimation.java index 210eb8a1ca9d..1aeee07538f8 100644 --- a/core/java/android/view/animation/ExtendAnimation.java +++ b/core/java/android/view/animation/ExtendAnimation.java @@ -20,6 +20,7 @@ import android.content.Context; import android.content.res.TypedArray; import android.graphics.Insets; import android.util.AttributeSet; +import android.view.WindowInsets; /** * An animation that controls the outset of an object. @@ -151,9 +152,12 @@ public class ExtendAnimation extends Animation { /** @hide */ @Override - public boolean hasExtension() { - return mFromInsets.left < 0 || mFromInsets.top < 0 || mFromInsets.right < 0 - || mFromInsets.bottom < 0; + @WindowInsets.Side.InsetsSide + public int getExtensionEdges() { + return (mFromInsets.left < 0 || mToInsets.left < 0 ? WindowInsets.Side.LEFT : 0) + | (mFromInsets.right < 0 || mToInsets.right < 0 ? WindowInsets.Side.RIGHT : 0) + | (mFromInsets.top < 0 || mToInsets.top < 0 ? WindowInsets.Side.TOP : 0) + | (mFromInsets.bottom < 0 || mToInsets.bottom < 0 ? WindowInsets.Side.BOTTOM : 0); } @Override diff --git a/core/java/android/view/textclassifier/TEST_MAPPING b/core/java/android/view/textclassifier/TEST_MAPPING index 2f9e737dc213..050c65191cad 100644 --- a/core/java/android/view/textclassifier/TEST_MAPPING +++ b/core/java/android/view/textclassifier/TEST_MAPPING @@ -1,15 +1,7 @@ { "presubmit": [ { - "name": "FrameworksCoreTests", - "options": [ - { - "include-filter": "android.view.textclassifier" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "FrameworksCoreTests_textclassifier" }, { "name": "CtsTextClassifierTestCases", diff --git a/core/java/android/window/ITaskFragmentOrganizerController.aidl b/core/java/android/window/ITaskFragmentOrganizerController.aidl index ac57c0056c2b..58b5757a47bb 100644 --- a/core/java/android/window/ITaskFragmentOrganizerController.aidl +++ b/core/java/android/window/ITaskFragmentOrganizerController.aidl @@ -16,6 +16,7 @@ package android.window; +import android.os.Bundle; import android.os.IBinder; import android.view.RemoteAnimationDefinition; import android.window.ITaskFragmentOrganizer; @@ -24,14 +25,21 @@ import android.window.WindowContainerTransaction; /** @hide */ interface ITaskFragmentOrganizerController { - /** * Registers a TaskFragmentOrganizer to manage TaskFragments. Registering a system * organizer requires MANAGE_ACTIVITY_TASKS permission, and the organizer will have additional * system capabilities. + * + * @param organizer The TaskFragmentOrganizer to register + * @param isSystemOrganizer If it is a system organizer + * @param outSavedState Returning the saved state (if any) that previously saved. This is + * useful when retrieve the state from the same TaskFragmentOrganizer + * that was killed by the system (e.g. to reclaim memory). Note that + * the save state is dropped and unable to retrieve once the system + * restarts or the organizer is unregistered. */ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value=android.Manifest.permission.MANAGE_ACTIVITY_TASKS, conditional=true)") - void registerOrganizer(in ITaskFragmentOrganizer organizer, in boolean isSystemOrganizer); + void registerOrganizer(in ITaskFragmentOrganizer organizer, in boolean isSystemOrganizer, out Bundle outSavedState); /** * Unregisters a previously registered TaskFragmentOrganizer. @@ -39,6 +47,12 @@ interface ITaskFragmentOrganizerController { void unregisterOrganizer(in ITaskFragmentOrganizer organizer); /** + * Saves the state in the system, where the state can be restored if the process of + * the TaskFragmentOrganizer is restarted. + */ + void setSavedState(in ITaskFragmentOrganizer organizer, in Bundle savedState); + + /** * Notifies the server that the organizer has finished handling the given transaction. The * server should apply the given {@link WindowContainerTransaction} for the necessary changes. */ diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java index 8e429cb376a6..027d323bfcd1 100644 --- a/core/java/android/window/TaskFragmentOrganizer.java +++ b/core/java/android/window/TaskFragmentOrganizer.java @@ -165,17 +165,12 @@ public class TaskFragmentOrganizer extends WindowOrganizer { */ @CallSuper public void registerOrganizer() { - // TODO(b/302420256) point to registerOrganizer(boolean) when flag is removed. - try { - getController().registerOrganizer(mInterface, false /* isSystemOrganizer */); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + registerOrganizer(false /* isSystemOrganizer */, null /* outSavedState */); } /** * Registers a {@link TaskFragmentOrganizer} to manage TaskFragments. - * + * <p> * Registering a system organizer requires MANAGE_ACTIVITY_TASKS permission, and the organizer * will have additional system capabilities, including: (1) it will receive SurfaceControl for * the organized TaskFragment, and (2) it needs to update the @@ -187,8 +182,31 @@ public class TaskFragmentOrganizer extends WindowOrganizer { @RequiresPermission(value = "android.permission.MANAGE_ACTIVITY_TASKS", conditional = true) @FlaggedApi(Flags.FLAG_TASK_FRAGMENT_SYSTEM_ORGANIZER_FLAG) public void registerOrganizer(boolean isSystemOrganizer) { + registerOrganizer(isSystemOrganizer, null /* outSavedState */); + } + + /** + * Registers a {@link TaskFragmentOrganizer} to manage TaskFragments. + * <p> + * Registering a system organizer requires MANAGE_ACTIVITY_TASKS permission, and the organizer + * will have additional system capabilities, including: (1) it will receive SurfaceControl for + * the organized TaskFragment, and (2) it needs to update the + * {@link android.view.SurfaceControl} following the window change accordingly. + * + * @param isSystemOrganizer If it is a system organizer + * @param outSavedState Returning the saved state (if any) that previously saved. This is + * useful when retrieve the state from the same TaskFragmentOrganizer + * that was killed by the system (e.g. to reclaim memory). Note that + * the save state is dropped and unable to retrieve once the system + * restarts or the organizer is unregistered. + * @hide + */ + @CallSuper + @RequiresPermission(value = "android.permission.MANAGE_ACTIVITY_TASKS", conditional = true) + public void registerOrganizer(boolean isSystemOrganizer, @Nullable Bundle outSavedState) { try { - getController().registerOrganizer(mInterface, isSystemOrganizer); + getController().registerOrganizer(mInterface, isSystemOrganizer, + outSavedState != null ? outSavedState : new Bundle()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -207,6 +225,30 @@ public class TaskFragmentOrganizer extends WindowOrganizer { } /** + * Saves the state in the system, where the state can be restored if the process of + * the TaskFragmentOrganizer is restarted. + * + * @hide + * + * @param state the state to save. + */ + public void setSavedState(@NonNull Bundle state) { + if (!Flags.aeBackStackRestore()) { + return; + } + + if (state.getSize() > 200000) { + throw new IllegalArgumentException("Saved state too large, " + state.getSize()); + } + + try { + getController().setSavedState(mInterface, state); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Notifies the server that the organizer has finished handling the given transaction. The * server should apply the given {@link WindowContainerTransaction} for the necessary changes. * diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java index 7bbc3dbb29db..b6c0d7cb00ef 100644 --- a/core/java/android/window/WindowOnBackInvokedDispatcher.java +++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java @@ -306,7 +306,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { } private boolean callOnKeyPreIme() { - if (mViewRoot != null && !isOnBackInvokedCallbackEnabled(mViewRoot.mContext)) { + if (mViewRoot != null && !isOnBackInvokedCallbackEnabled()) { return mViewRoot.injectBackKeyEvents(/*preImeOnly*/ true); } else { return false; @@ -505,7 +505,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { if (callback instanceof ImeBackAnimationController || callback instanceof ImeOnBackInvokedDispatcher.ImeOnBackInvokedCallback) { // call onKeyPreIme API if the current callback is an IME callback and the app has - // not set enableOnBackInvokedCallback="false" + // not set enableOnBackInvokedCallback="true" try { boolean consumed = mOnKeyPreIme.getAsBoolean(); if (consumed) { diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig index a6ae948604a5..adbc59867c0c 100644 --- a/core/java/android/window/flags/windowing_sdk.aconfig +++ b/core/java/android/window/flags/windowing_sdk.aconfig @@ -61,16 +61,6 @@ flag { flag { namespace: "windowing_sdk" - name: "fix_pip_restore_to_overlay" - description: "Restore exit-pip activity back to ActivityEmbedding overlay" - bug: "297887697" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { - namespace: "windowing_sdk" name: "activity_embedding_animation_customization_flag" description: "Whether the animation customization feature for AE is enabled" bug: "293658614" @@ -128,3 +118,11 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + namespace: "windowing_sdk" + name: "ae_back_stack_restore" + description: "Allow the ActivityEmbedding back stack to be restored after process restarted" + bug: "289875940" + is_fixed_read_only: true +} diff --git a/core/java/com/android/internal/content/om/TEST_MAPPING b/core/java/com/android/internal/content/om/TEST_MAPPING index ab3abb1c935a..c27c3251e100 100644 --- a/core/java/com/android/internal/content/om/TEST_MAPPING +++ b/core/java/com/android/internal/content/om/TEST_MAPPING @@ -1,12 +1,7 @@ { "presubmit": [ { - "name": "FrameworksCoreTests", - "options": [ - { - "include-filter": "com.android.internal.content." - } - ] + "name": "FrameworksCoreTests_internal_content" }, { "name": "SelfTargetingOverlayDeviceTests" diff --git a/core/java/com/android/internal/graphics/ColorUtils.java b/core/java/com/android/internal/graphics/ColorUtils.java index f72a5ca2bffb..f210741e070b 100644 --- a/core/java/com/android/internal/graphics/ColorUtils.java +++ b/core/java/com/android/internal/graphics/ColorUtils.java @@ -21,7 +21,7 @@ import android.annotation.FloatRange; import android.annotation.IntRange; import android.annotation.NonNull; import android.graphics.Color; - +import android.ravenwood.annotation.RavenwoodKeepWholeClass; import com.android.internal.graphics.cam.Cam; /** @@ -29,6 +29,7 @@ import com.android.internal.graphics.cam.Cam; * * A set of color-related utility methods, building upon those available in {@code Color}. */ +@RavenwoodKeepWholeClass public final class ColorUtils { private static final double XYZ_WHITE_REFERENCE_X = 95.047; @@ -696,4 +697,4 @@ public final class ColorUtils { double calculateContrast(int foreground, int background, int alpha); } -}
\ No newline at end of file +} diff --git a/core/java/com/android/internal/graphics/cam/Cam.java b/core/java/com/android/internal/graphics/cam/Cam.java index 1df85c389322..49fa37bd0ed3 100644 --- a/core/java/com/android/internal/graphics/cam/Cam.java +++ b/core/java/com/android/internal/graphics/cam/Cam.java @@ -18,6 +18,7 @@ package com.android.internal.graphics.cam; import android.annotation.NonNull; import android.annotation.Nullable; +import android.ravenwood.annotation.RavenwoodKeepWholeClass; import com.android.internal.graphics.ColorUtils; @@ -25,6 +26,7 @@ import com.android.internal.graphics.ColorUtils; * A color appearance model, based on CAM16, extended to use L* as the lightness dimension, and * coupled to a gamut mapping algorithm. Creates a color system, enables a digital design system. */ +@RavenwoodKeepWholeClass public class Cam { // The maximum difference between the requested L* and the L* returned. private static final float DL_MAX = 0.2f; diff --git a/core/java/com/android/internal/graphics/cam/CamUtils.java b/core/java/com/android/internal/graphics/cam/CamUtils.java index f54172996168..76fabc6529d8 100644 --- a/core/java/com/android/internal/graphics/cam/CamUtils.java +++ b/core/java/com/android/internal/graphics/cam/CamUtils.java @@ -19,6 +19,7 @@ package com.android.internal.graphics.cam; import android.annotation.NonNull; import android.graphics.Color; +import android.ravenwood.annotation.RavenwoodKeepWholeClass; import com.android.internal.graphics.ColorUtils; @@ -45,6 +46,7 @@ import com.android.internal.graphics.ColorUtils; * consistent, and reasonably good. It worked." - Fairchild, Color Models and Systems: Handbook of * Color Psychology, 2015 */ +@RavenwoodKeepWholeClass public final class CamUtils { private CamUtils() { } diff --git a/core/java/com/android/internal/graphics/cam/Frame.java b/core/java/com/android/internal/graphics/cam/Frame.java index 0ac7cbc2f60e..c419fabc9d89 100644 --- a/core/java/com/android/internal/graphics/cam/Frame.java +++ b/core/java/com/android/internal/graphics/cam/Frame.java @@ -17,6 +17,7 @@ package com.android.internal.graphics.cam; import android.annotation.NonNull; +import android.ravenwood.annotation.RavenwoodKeepWholeClass; import android.util.MathUtils; import com.android.internal.annotations.VisibleForTesting; @@ -33,6 +34,7 @@ import com.android.internal.annotations.VisibleForTesting; * number of calculations during the color => CAM conversion process that depend only on the viewing * conditions. Caching those calculations in a Frame instance saves a significant amount of time. */ +@RavenwoodKeepWholeClass public final class Frame { // Standard viewing conditions assumed in RGB specification - Stokes, Anderson, Chandrasekar, // Motta - A Standard Default Color Space for the Internet: sRGB, 1996. diff --git a/core/java/com/android/internal/graphics/cam/HctSolver.java b/core/java/com/android/internal/graphics/cam/HctSolver.java index d7a869185cd7..6e558e7809a5 100644 --- a/core/java/com/android/internal/graphics/cam/HctSolver.java +++ b/core/java/com/android/internal/graphics/cam/HctSolver.java @@ -16,6 +16,8 @@ package com.android.internal.graphics.cam; +import android.ravenwood.annotation.RavenwoodKeepWholeClass; + /** * An efficient algorithm for determining the closest sRGB color to a set of HCT coordinates, * based on geometrical insights for finding intersections in linear RGB, CAM16, and L*a*b*. @@ -24,6 +26,7 @@ package com.android.internal.graphics.cam; * Copied from //java/com/google/ux/material/libmonet/hct on May 22 2022. * ColorUtils/MathUtils functions that were required were added to CamUtils. */ +@RavenwoodKeepWholeClass public class HctSolver { private HctSolver() {} diff --git a/core/java/com/android/internal/infra/TEST_MAPPING b/core/java/com/android/internal/infra/TEST_MAPPING index c09181f2f496..e4550c0db135 100644 --- a/core/java/com/android/internal/infra/TEST_MAPPING +++ b/core/java/com/android/internal/infra/TEST_MAPPING @@ -20,12 +20,7 @@ ] }, { - "name": "FrameworksCoreTests", - "options": [ - { - "include-filter": "com.android.internal.infra." - } - ] + "name": "FrameworksCoreTests_internal_infra" } ] } diff --git a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java index 921363c3e5af..b009c99dc9e3 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java +++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java @@ -20,6 +20,8 @@ import android.annotation.AnyThread; import android.annotation.DrawableRes; import android.annotation.NonNull; import android.annotation.Nullable; +import android.inputmethodservice.InputMethodService.BackDispositionMode; +import android.inputmethodservice.InputMethodService.ImeWindowVisibility; import android.net.Uri; import android.os.IBinder; import android.os.RemoteException; @@ -106,13 +108,10 @@ public final class InputMethodPrivilegedOperations { * * @param vis visibility flags * @param backDisposition disposition flags - * @see android.inputmethodservice.InputMethodService#IME_ACTIVE - * @see android.inputmethodservice.InputMethodService#IME_VISIBLE - * @see android.inputmethodservice.InputMethodService#BACK_DISPOSITION_DEFAULT - * @see android.inputmethodservice.InputMethodService#BACK_DISPOSITION_ADJUST_NOTHING */ @AnyThread - public void setImeWindowStatusAsync(int vis, int backDisposition) { + public void setImeWindowStatusAsync(@ImeWindowVisibility int vis, + @BackDispositionMode int backDisposition) { final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull(); if (ops == null) { return; diff --git a/core/java/com/android/internal/jank/Cuj.java b/core/java/com/android/internal/jank/Cuj.java index 69d1cb34005d..7bfb8003fd18 100644 --- a/core/java/com/android/internal/jank/Cuj.java +++ b/core/java/com/android/internal/jank/Cuj.java @@ -210,8 +210,16 @@ public class Cuj { */ public static final int CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE = 116; + /** + * Track interaction of exiting desktop mode on closing the last window. + * + * <p>Tracking starts when the last window is closed and finishes when the animation to exit + * desktop mode ends. + */ + public static final int CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE = 117; + // When adding a CUJ, update this and make sure to also update CUJ_TO_STATSD_INTERACTION_TYPE. - @VisibleForTesting static final int LAST_CUJ = CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE; + @VisibleForTesting static final int LAST_CUJ = CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE; /** @hide */ @IntDef({ @@ -319,7 +327,8 @@ public class Cuj { CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_OPEN, CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE, CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH, - CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE + CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE, + CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE }) @Retention(RetentionPolicy.SOURCE) public @interface CujType {} @@ -438,6 +447,7 @@ public class Cuj { CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE; CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH; CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE; } private Cuj() { @@ -666,6 +676,8 @@ public class Cuj { return "LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH"; case CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE: return "DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE"; + case CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE: + return "DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE"; } return "UNKNOWN"; } diff --git a/core/java/com/android/internal/jank/TEST_MAPPING b/core/java/com/android/internal/jank/TEST_MAPPING index 4e00ff19e9d9..e7f3dc38e44b 100644 --- a/core/java/com/android/internal/jank/TEST_MAPPING +++ b/core/java/com/android/internal/jank/TEST_MAPPING @@ -1,18 +1,7 @@ { "presubmit": [ { - "name": "FrameworksCoreTests", - "options": [ - { - "include-filter": "com.android.internal.jank" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - } - ], + "name": "FrameworksCoreTests_internal_jank", "file_patterns": [ "core/java/com/android/internal/jank/.*", "core/tests/coretests/src/com/android/internal/jank/.*" diff --git a/core/java/com/android/internal/os/TEST_MAPPING b/core/java/com/android/internal/os/TEST_MAPPING index ae43acfdedb6..8346d7160bc7 100644 --- a/core/java/com/android/internal/os/TEST_MAPPING +++ b/core/java/com/android/internal/os/TEST_MAPPING @@ -6,11 +6,7 @@ "Kernel[^/]*\\.java", "[^/]*Power[^/]*\\.java" ], - "name": "FrameworksCoreTests", - "options": [ - { "include-filter": "com.android.internal.os.BatteryStatsTests" }, - { "exclude-annotation": "com.android.internal.os.SkipPresubmit" } - ] + "name": "FrameworksCoreTests_battery_stats" }, { "file_patterns": [ @@ -24,11 +20,7 @@ "file_patterns": [ "BinderDeathDispatcher\\.java" ], - "name": "FrameworksCoreTests", - "options": [ - { "include-filter": "com.android.internal.os.BinderDeathDispatcherTest" }, - { "exclude-annotation": "com.android.internal.os.SkipPresubmit" } - ] + "name": "FrameworksCoreTests_internal_os_binder" }, { "file_patterns": [ @@ -50,25 +42,7 @@ "name": "PowerStatsTests" }, { - "name": "FrameworksCoreTests", - "options": [ - { - "include-filter": "com.android.internal.os.KernelCpuUidFreqTimeReaderTest" - }, - { - "include-filter": "com.android.internal.os.KernelCpuUidActiveTimeReaderTest" - }, - { - "include-filter": "com.android.internal.os.KernelCpuUidClusterTimeReaderTest" - }, - { - "include-filter": "com.android.internal.os.KernelSingleUidTimeReaderTest" - }, - { - "include-filter": "com.android.internal.os.KernelCpuUidBpfMapReaderTest" - } - - ], + "name": "FrameworksCoreTests_internal_os_kernel", "file_patterns": [ "KernelCpuUidTimeReader\\.java", "KernelCpuUidBpfMapReader\\.java", diff --git a/core/java/com/android/internal/power/TEST_MAPPING b/core/java/com/android/internal/power/TEST_MAPPING index 1946f5cc99eb..3f184b22a299 100644 --- a/core/java/com/android/internal/power/TEST_MAPPING +++ b/core/java/com/android/internal/power/TEST_MAPPING @@ -1,11 +1,7 @@ { "presubmit": [ { - "name": "FrameworksCoreTests", - "options": [ - { "include-filter": "com.android.internal.os.BatteryStatsTests" }, - { "exclude-annotation": "com.android.internal.os.SkipPresubmit" } - ] + "name": "FrameworksCoreTests_battery_stats" }, { "name": "PowerStatsTests" diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java index e0c90d83768c..cb20ceb5484b 100644 --- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java +++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java @@ -33,6 +33,7 @@ import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Gro import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MESSAGES; import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.GROUP_ID; import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.LEVEL; +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.LOCATION; import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.MESSAGE; import static android.internal.perfetto.protos.TracePacketOuterClass.TracePacket.INTERNED_DATA; import static android.internal.perfetto.protos.TracePacketOuterClass.TracePacket.PROTOLOG_MESSAGE; @@ -449,6 +450,8 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto case (int) GROUP_ID: os.write(GROUP_ID, pis.readInt(GROUP_ID)); break; + case (int) LOCATION: + os.write(LOCATION, pis.readInt(LOCATION)); default: throw new RuntimeException( "Unexpected field id " + pis.getFieldNumber()); diff --git a/core/java/com/android/internal/security/TEST_MAPPING b/core/java/com/android/internal/security/TEST_MAPPING index 0af3b03edefc..5bd9d2e4512d 100644 --- a/core/java/com/android/internal/security/TEST_MAPPING +++ b/core/java/com/android/internal/security/TEST_MAPPING @@ -1,15 +1,7 @@ { "presubmit": [ { - "name": "FrameworksCoreTests", - "options": [ - { - "include-filter": "com.android.internal.security." - }, - { - "include-annotation": "android.platform.test.annotations.Presubmit" - } - ] + "name": "FrameworksCoreTests_internal_security" }, { "name": "UpdatableSystemFontTest", diff --git a/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java b/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java index 7240aff022d4..3adc6b20ecb1 100644 --- a/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java +++ b/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java @@ -16,6 +16,8 @@ package com.android.internal.statusbar; +import android.inputmethodservice.InputMethodService.BackDispositionMode; +import android.inputmethodservice.InputMethodService.ImeWindowVisibility; import android.os.Parcel; import android.os.Parcelable; import android.util.ArrayMap; @@ -30,7 +32,9 @@ public final class RegisterStatusBarResult implements Parcelable { public final int mDisabledFlags1; // switch[0] public final int mAppearance; // switch[1] public final AppearanceRegion[] mAppearanceRegions; // switch[2] + @ImeWindowVisibility public final int mImeWindowVis; // switch[3] + @BackDispositionMode public final int mImeBackDisposition; // switch[4] public final boolean mShowImeSwitcher; // switch[5] public final int mDisabledFlags2; // switch[6] @@ -42,10 +46,11 @@ public final class RegisterStatusBarResult implements Parcelable { public final LetterboxDetails[] mLetterboxDetails; public RegisterStatusBarResult(ArrayMap<String, StatusBarIcon> icons, int disabledFlags1, - int appearance, AppearanceRegion[] appearanceRegions, int imeWindowVis, - int imeBackDisposition, boolean showImeSwitcher, int disabledFlags2, - boolean navbarColorManagedByIme, int behavior, int requestedVisibleTypes, - String packageName, int transientBarTypes, LetterboxDetails[] letterboxDetails) { + int appearance, AppearanceRegion[] appearanceRegions, + @ImeWindowVisibility int imeWindowVis, @BackDispositionMode int imeBackDisposition, + boolean showImeSwitcher, int disabledFlags2, boolean navbarColorManagedByIme, + int behavior, int requestedVisibleTypes, String packageName, int transientBarTypes, + LetterboxDetails[] letterboxDetails) { mIcons = new ArrayMap<>(icons); mDisabledFlags1 = disabledFlags1; mAppearance = appearance; diff --git a/core/java/com/android/internal/util/TEST_MAPPING b/core/java/com/android/internal/util/TEST_MAPPING index 00a8118c0e4b..a0221f3beff2 100644 --- a/core/java/com/android/internal/util/TEST_MAPPING +++ b/core/java/com/android/internal/util/TEST_MAPPING @@ -5,30 +5,11 @@ "file_patterns": ["ScreenshotHelper"] }, { - "name": "FrameworksCoreTests", - "options": [ - { - "include-filter": "android.util.XmlTest" - }, - { - "include-filter": "android.util.BinaryXmlTest" - } - ], + "name": "FrameworksCoreTests_xml", "file_patterns": ["Xml"] }, { - "name": "FrameworksCoreTests", - "options": [ - { - "include-filter": "com.android.internal.util.LatencyTrackerTest" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - } - ], + "name": "FrameworksCoreTests_internal_util_latency_tracker", "file_patterns": ["LatencyTracker.java"] } ] diff --git a/core/java/com/android/internal/vibrator/persistence/SerializedAmplitudeStepWaveform.java b/core/java/com/android/internal/vibrator/persistence/SerializedAmplitudeStepWaveform.java index 15ecedd0f59e..cd7dcfdac906 100644 --- a/core/java/com/android/internal/vibrator/persistence/SerializedAmplitudeStepWaveform.java +++ b/core/java/com/android/internal/vibrator/persistence/SerializedAmplitudeStepWaveform.java @@ -29,7 +29,7 @@ import android.os.VibrationEffect; import android.util.IntArray; import android.util.LongArray; -import com.android.internal.vibrator.persistence.SerializedVibrationEffect.SerializedSegment; +import com.android.internal.vibrator.persistence.SerializedComposedEffect.SerializedSegment; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; diff --git a/core/java/com/android/internal/vibrator/persistence/SerializedVibrationEffect.java b/core/java/com/android/internal/vibrator/persistence/SerializedComposedEffect.java index 23df3048e69c..6c562c99565e 100644 --- a/core/java/com/android/internal/vibrator/persistence/SerializedVibrationEffect.java +++ b/core/java/com/android/internal/vibrator/persistence/SerializedComposedEffect.java @@ -29,24 +29,24 @@ import java.io.IOException; import java.util.Arrays; /** - * Serialized representation of a {@link VibrationEffect}. + * Serialized representation of a {@link VibrationEffect.Composed}. * * <p>The vibration is represented by a list of serialized segments that can be added to a * {@link VibrationEffect.Composition} during the {@link #deserialize()} procedure. * * @hide */ -final class SerializedVibrationEffect implements XmlSerializedVibration<VibrationEffect> { +final class SerializedComposedEffect implements XmlSerializedVibration<VibrationEffect.Composed> { @NonNull private final SerializedSegment[] mSegments; - SerializedVibrationEffect(@NonNull SerializedSegment segment) { + SerializedComposedEffect(@NonNull SerializedSegment segment) { requireNonNull(segment); mSegments = new SerializedSegment[]{ segment }; } - SerializedVibrationEffect(@NonNull SerializedSegment[] segments) { + SerializedComposedEffect(@NonNull SerializedSegment[] segments) { requireNonNull(segments); checkArgument(segments.length > 0, "Unsupported empty vibration"); mSegments = segments; @@ -54,12 +54,12 @@ final class SerializedVibrationEffect implements XmlSerializedVibration<Vibratio @NonNull @Override - public VibrationEffect deserialize() { + public VibrationEffect.Composed deserialize() { VibrationEffect.Composition composition = VibrationEffect.startComposition(); for (SerializedSegment segment : mSegments) { segment.deserializeIntoComposition(composition); } - return composition.compose(); + return (VibrationEffect.Composed) composition.compose(); } @Override @@ -79,7 +79,7 @@ final class SerializedVibrationEffect implements XmlSerializedVibration<Vibratio @Override public String toString() { - return "SerializedVibrationEffect{" + return "SerializedComposedEffect{" + "segments=" + Arrays.toString(mSegments) + '}'; } diff --git a/core/java/com/android/internal/vibrator/persistence/SerializedCompositionPrimitive.java b/core/java/com/android/internal/vibrator/persistence/SerializedCompositionPrimitive.java index db5c7ff830b5..862f7cb1d476 100644 --- a/core/java/com/android/internal/vibrator/persistence/SerializedCompositionPrimitive.java +++ b/core/java/com/android/internal/vibrator/persistence/SerializedCompositionPrimitive.java @@ -27,7 +27,7 @@ import android.annotation.Nullable; import android.os.VibrationEffect; import android.os.vibrator.PrimitiveSegment; -import com.android.internal.vibrator.persistence.SerializedVibrationEffect.SerializedSegment; +import com.android.internal.vibrator.persistence.SerializedComposedEffect.SerializedSegment; import com.android.internal.vibrator.persistence.XmlConstants.PrimitiveEffectName; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; diff --git a/core/java/com/android/internal/vibrator/persistence/SerializedPredefinedEffect.java b/core/java/com/android/internal/vibrator/persistence/SerializedPredefinedEffect.java index 8924311f9c33..a6f48a445564 100644 --- a/core/java/com/android/internal/vibrator/persistence/SerializedPredefinedEffect.java +++ b/core/java/com/android/internal/vibrator/persistence/SerializedPredefinedEffect.java @@ -25,7 +25,7 @@ import android.annotation.NonNull; import android.os.VibrationEffect; import android.os.vibrator.PrebakedSegment; -import com.android.internal.vibrator.persistence.SerializedVibrationEffect.SerializedSegment; +import com.android.internal.vibrator.persistence.SerializedComposedEffect.SerializedSegment; import com.android.internal.vibrator.persistence.XmlConstants.PredefinedEffectName; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; diff --git a/core/java/com/android/internal/vibrator/persistence/SerializedVendorEffect.java b/core/java/com/android/internal/vibrator/persistence/SerializedVendorEffect.java new file mode 100644 index 000000000000..aa1b0a236723 --- /dev/null +++ b/core/java/com/android/internal/vibrator/persistence/SerializedVendorEffect.java @@ -0,0 +1,127 @@ +/* + * 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.vibrator.persistence; + +import static com.android.internal.vibrator.persistence.XmlConstants.TAG_VENDOR_EFFECT; + +import static java.util.Objects.requireNonNull; + +import android.annotation.NonNull; +import android.annotation.SuppressLint; +import android.os.PersistableBundle; +import android.os.VibrationEffect; +import android.text.TextUtils; +import android.util.Base64; + +import com.android.modules.utils.TypedXmlPullParser; +import com.android.modules.utils.TypedXmlSerializer; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +/** + * Serialized representation of a {@link VibrationEffect.VendorEffect}. + * + * <p>The vibration is represented by an opaque {@link PersistableBundle} that can be used by + * {@link VibrationEffect#createVendorEffect(PersistableBundle)} during the {@link #deserialize()} + * procedure. + * + * @hide + */ +final class SerializedVendorEffect implements XmlSerializedVibration<VibrationEffect.VendorEffect> { + + @NonNull + private final PersistableBundle mVendorData; + + SerializedVendorEffect(@NonNull PersistableBundle vendorData) { + requireNonNull(vendorData); + mVendorData = vendorData; + } + + @SuppressLint("MissingPermission") + @NonNull + @Override + public VibrationEffect.VendorEffect deserialize() { + return (VibrationEffect.VendorEffect) VibrationEffect.createVendorEffect(mVendorData); + } + + @Override + public void write(@NonNull TypedXmlSerializer serializer) + throws IOException { + serializer.startTag(XmlConstants.NAMESPACE, XmlConstants.TAG_VIBRATION_EFFECT); + writeContent(serializer); + serializer.endTag(XmlConstants.NAMESPACE, XmlConstants.TAG_VIBRATION_EFFECT); + } + + @Override + public void writeContent(@NonNull TypedXmlSerializer serializer) throws IOException { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + mVendorData.writeToStream(outputStream); + + serializer.startTag(XmlConstants.NAMESPACE, XmlConstants.TAG_VENDOR_EFFECT); + serializer.text(Base64.encodeToString(outputStream.toByteArray(), Base64.NO_WRAP)); + serializer.endTag(XmlConstants.NAMESPACE, XmlConstants.TAG_VENDOR_EFFECT); + } + + @Override + public String toString() { + return "SerializedVendorEffect{" + + "vendorData=" + mVendorData + + '}'; + } + + /** Parser implementation for {@link SerializedVendorEffect}. */ + static final class Parser { + + @NonNull + static SerializedVendorEffect parseNext(@NonNull TypedXmlPullParser parser, + @XmlConstants.Flags int flags) throws XmlParserException, IOException { + XmlValidator.checkStartTag(parser, TAG_VENDOR_EFFECT); + XmlValidator.checkTagHasNoUnexpectedAttributes(parser); + + PersistableBundle vendorData; + XmlReader.readNextText(parser, TAG_VENDOR_EFFECT); + + try { + String text = parser.getText().trim(); + XmlValidator.checkParserCondition(!text.isEmpty(), + "Expected tag %s to have base64 representation of vendor data, got empty", + TAG_VENDOR_EFFECT); + + vendorData = PersistableBundle.readFromStream( + new ByteArrayInputStream(Base64.decode(text, Base64.DEFAULT))); + XmlValidator.checkParserCondition(!vendorData.isEmpty(), + "Expected tag %s to have non-empty vendor data, got empty bundle", + TAG_VENDOR_EFFECT); + } catch (IllegalArgumentException | NullPointerException e) { + throw new XmlParserException( + TextUtils.formatSimple( + "Expected base64 representation of vendor data in tag %s, got %s", + TAG_VENDOR_EFFECT, parser.getText()), + e); + } catch (IOException e) { + throw new XmlParserException("Error reading vendor data from decoded bytes", e); + } + + // Consume tag + XmlReader.readEndTag(parser); + + return new SerializedVendorEffect(vendorData); + } + } +} diff --git a/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlParser.java b/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlParser.java index 2b8b61d50a6c..a9fbcafa128d 100644 --- a/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlParser.java +++ b/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlParser.java @@ -18,13 +18,15 @@ package com.android.internal.vibrator.persistence; import static com.android.internal.vibrator.persistence.XmlConstants.TAG_PREDEFINED_EFFECT; import static com.android.internal.vibrator.persistence.XmlConstants.TAG_PRIMITIVE_EFFECT; +import static com.android.internal.vibrator.persistence.XmlConstants.TAG_VENDOR_EFFECT; import static com.android.internal.vibrator.persistence.XmlConstants.TAG_VIBRATION_EFFECT; import static com.android.internal.vibrator.persistence.XmlConstants.TAG_WAVEFORM_EFFECT; import android.annotation.NonNull; import android.os.VibrationEffect; +import android.os.vibrator.Flags; -import com.android.internal.vibrator.persistence.SerializedVibrationEffect.SerializedSegment; +import com.android.internal.vibrator.persistence.SerializedComposedEffect.SerializedSegment; import com.android.modules.utils.TypedXmlPullParser; import java.io.IOException; @@ -80,6 +82,16 @@ import java.util.List; * } * </pre> * + * * Vendor vibration effects + * + * <pre> + * {@code + * <vibration-effect> + * <vendor-effect>base64-representation-of-persistable-bundle</vendor-effect> + * </vibration-effect> + * } + * </pre> + * * @hide */ public class VibrationEffectXmlParser { @@ -87,11 +99,9 @@ public class VibrationEffectXmlParser { /** * Parses the current XML tag with all nested tags into a single {@link XmlSerializedVibration} * wrapping a {@link VibrationEffect}. - * - * @see XmlParser#parseTag(TypedXmlPullParser) */ @NonNull - public static XmlSerializedVibration<VibrationEffect> parseTag( + public static XmlSerializedVibration<? extends VibrationEffect> parseTag( @NonNull TypedXmlPullParser parser, @XmlConstants.Flags int flags) throws XmlParserException, IOException { XmlValidator.checkStartTag(parser, TAG_VIBRATION_EFFECT); @@ -107,8 +117,9 @@ public class VibrationEffectXmlParser { * <p>This can be reused for reading a vibration from an XML root tag or from within a combined * vibration, but it should always be called from places that validates the top level tag. */ - static SerializedVibrationEffect parseVibrationContent(TypedXmlPullParser parser, - @XmlConstants.Flags int flags) throws XmlParserException, IOException { + private static XmlSerializedVibration<? extends VibrationEffect> parseVibrationContent( + TypedXmlPullParser parser, @XmlConstants.Flags int flags) + throws XmlParserException, IOException { String vibrationTagName = parser.getName(); int vibrationTagDepth = parser.getDepth(); @@ -116,11 +127,16 @@ public class VibrationEffectXmlParser { XmlReader.readNextTagWithin(parser, vibrationTagDepth), "Unsupported empty vibration tag"); - SerializedVibrationEffect serializedVibration; + XmlSerializedVibration<? extends VibrationEffect> serializedVibration; switch (parser.getName()) { + case TAG_VENDOR_EFFECT: + if (Flags.vendorVibrationEffects()) { + serializedVibration = SerializedVendorEffect.Parser.parseNext(parser, flags); + break; + } // else fall through case TAG_PREDEFINED_EFFECT: - serializedVibration = new SerializedVibrationEffect( + serializedVibration = new SerializedComposedEffect( SerializedPredefinedEffect.Parser.parseNext(parser, flags)); break; case TAG_PRIMITIVE_EFFECT: @@ -128,11 +144,11 @@ public class VibrationEffectXmlParser { do { // First primitive tag already open primitives.add(SerializedCompositionPrimitive.Parser.parseNext(parser)); } while (XmlReader.readNextTagWithin(parser, vibrationTagDepth)); - serializedVibration = new SerializedVibrationEffect( + serializedVibration = new SerializedComposedEffect( primitives.toArray(new SerializedSegment[primitives.size()])); break; case TAG_WAVEFORM_EFFECT: - serializedVibration = new SerializedVibrationEffect( + serializedVibration = new SerializedComposedEffect( SerializedAmplitudeStepWaveform.Parser.parseNext(parser)); break; default: diff --git a/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlSerializer.java b/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlSerializer.java index f561c1485f1d..d74a23d47f4a 100644 --- a/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlSerializer.java +++ b/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlSerializer.java @@ -17,13 +17,15 @@ package com.android.internal.vibrator.persistence; import android.annotation.NonNull; +import android.os.PersistableBundle; import android.os.VibrationEffect; +import android.os.vibrator.Flags; import android.os.vibrator.PrebakedSegment; import android.os.vibrator.PrimitiveSegment; import android.os.vibrator.StepSegment; import android.os.vibrator.VibrationEffectSegment; -import com.android.internal.vibrator.persistence.SerializedVibrationEffect.SerializedSegment; +import com.android.internal.vibrator.persistence.SerializedComposedEffect.SerializedSegment; import com.android.internal.vibrator.persistence.XmlConstants.PredefinedEffectName; import com.android.internal.vibrator.persistence.XmlConstants.PrimitiveEffectName; @@ -41,6 +43,7 @@ import java.util.List; * <li>{@link VibrationEffect#createWaveform(long[], int[], int)} * <li>A composition created exclusively via * {@link VibrationEffect.Composition#addPrimitive(int, float, int)} + * <li>{@link VibrationEffect#createVendorEffect(PersistableBundle)} * </ul> * * @hide @@ -49,13 +52,16 @@ public final class VibrationEffectXmlSerializer { /** * Creates a serialized representation of the input {@code vibration}. - * - * @see XmlSerializer#serialize */ @NonNull - public static XmlSerializedVibration<VibrationEffect> serialize( + public static XmlSerializedVibration<? extends VibrationEffect> serialize( @NonNull VibrationEffect vibration, @XmlConstants.Flags int flags) throws XmlSerializerException { + if (Flags.vendorVibrationEffects() + && (vibration instanceof VibrationEffect.VendorEffect vendorEffect)) { + return serializeVendorEffect(vendorEffect); + } + XmlValidator.checkSerializerCondition(vibration instanceof VibrationEffect.Composed, "Unsupported VibrationEffect type %s", vibration); @@ -73,7 +79,7 @@ public final class VibrationEffectXmlSerializer { return serializeWaveformEffect(composed); } - private static SerializedVibrationEffect serializePredefinedEffect( + private static SerializedComposedEffect serializePredefinedEffect( VibrationEffect.Composed effect, @XmlConstants.Flags int flags) throws XmlSerializerException { List<VibrationEffectSegment> segments = effect.getSegments(); @@ -81,10 +87,15 @@ public final class VibrationEffectXmlSerializer { "Unsupported repeating predefined effect %s", effect); XmlValidator.checkSerializerCondition(segments.size() == 1, "Unsupported multiple segments in predefined effect %s", effect); - return new SerializedVibrationEffect(serializePrebakedSegment(segments.get(0), flags)); + return new SerializedComposedEffect(serializePrebakedSegment(segments.get(0), flags)); + } + + private static SerializedVendorEffect serializeVendorEffect( + VibrationEffect.VendorEffect effect) { + return new SerializedVendorEffect(effect.getVendorData()); } - private static SerializedVibrationEffect serializePrimitiveEffect( + private static SerializedComposedEffect serializePrimitiveEffect( VibrationEffect.Composed effect) throws XmlSerializerException { List<VibrationEffectSegment> segments = effect.getSegments(); XmlValidator.checkSerializerCondition(effect.getRepeatIndex() == -1, @@ -95,10 +106,10 @@ public final class VibrationEffectXmlSerializer { primitives[i] = serializePrimitiveSegment(segments.get(i)); } - return new SerializedVibrationEffect(primitives); + return new SerializedComposedEffect(primitives); } - private static SerializedVibrationEffect serializeWaveformEffect( + private static SerializedComposedEffect serializeWaveformEffect( VibrationEffect.Composed effect) throws XmlSerializerException { SerializedAmplitudeStepWaveform.Builder serializedWaveformBuilder = new SerializedAmplitudeStepWaveform.Builder(); @@ -120,7 +131,7 @@ public final class VibrationEffectXmlSerializer { segment.getDuration(), toAmplitudeInt(segment.getAmplitude())); } - return new SerializedVibrationEffect(serializedWaveformBuilder.build()); + return new SerializedComposedEffect(serializedWaveformBuilder.build()); } private static SerializedPredefinedEffect serializePrebakedSegment( diff --git a/core/java/com/android/internal/vibrator/persistence/XmlConstants.java b/core/java/com/android/internal/vibrator/persistence/XmlConstants.java index 8b92153b27db..2a55d999bc0f 100644 --- a/core/java/com/android/internal/vibrator/persistence/XmlConstants.java +++ b/core/java/com/android/internal/vibrator/persistence/XmlConstants.java @@ -40,6 +40,7 @@ public final class XmlConstants { public static final String TAG_PREDEFINED_EFFECT = "predefined-effect"; public static final String TAG_PRIMITIVE_EFFECT = "primitive-effect"; + public static final String TAG_VENDOR_EFFECT = "vendor-effect"; public static final String TAG_WAVEFORM_EFFECT = "waveform-effect"; public static final String TAG_WAVEFORM_ENTRY = "waveform-entry"; public static final String TAG_REPEATING = "repeating"; diff --git a/core/java/com/android/internal/vibrator/persistence/XmlParser.java b/core/java/com/android/internal/vibrator/persistence/XmlParser.java deleted file mode 100644 index 6712f1c46a50..000000000000 --- a/core/java/com/android/internal/vibrator/persistence/XmlParser.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.internal.vibrator.persistence; - -import android.annotation.NonNull; - -import com.android.modules.utils.TypedXmlPullParser; - -import java.io.IOException; - -/** - * Parse XML tags into valid {@link XmlSerializedVibration} instances. - * - * @param <T> The vibration type that will be parsed. - * @see XmlSerializedVibration - * @hide - */ -@FunctionalInterface -public interface XmlParser<T> { - - /** - * Parses the current XML tag with all nested tags into a single {@link XmlSerializedVibration}. - * - * <p>This method will consume nested XML tags until it finds the - * {@link TypedXmlPullParser#END_TAG} for the current tag. - * - * <p>The vibration reconstructed by the returned {@link XmlSerializedVibration#deserialize()} - * is guaranteed to be valid. This method will throw an exception otherwise. - * - * @param pullParser The {@link TypedXmlPullParser} with the input XML. - * @return The parsed vibration wrapped in a {@link XmlSerializedVibration} representation. - * @throws IOException On any I/O error while reading the input XML - * @throws XmlParserException If the XML content does not represent a valid vibration. - */ - XmlSerializedVibration<T> parseTag(@NonNull TypedXmlPullParser pullParser) - throws XmlParserException, IOException; -} diff --git a/core/java/com/android/internal/vibrator/persistence/XmlParserException.java b/core/java/com/android/internal/vibrator/persistence/XmlParserException.java index 7507864eea38..e2b30e74b0d5 100644 --- a/core/java/com/android/internal/vibrator/persistence/XmlParserException.java +++ b/core/java/com/android/internal/vibrator/persistence/XmlParserException.java @@ -23,7 +23,6 @@ import org.xmlpull.v1.XmlPullParserException; /** * Represents an error while parsing a vibration XML input. * - * @see XmlParser * @hide */ public final class XmlParserException extends Exception { diff --git a/core/java/com/android/internal/vibrator/persistence/XmlReader.java b/core/java/com/android/internal/vibrator/persistence/XmlReader.java index a5ace8438142..0ac6fefc8cb2 100644 --- a/core/java/com/android/internal/vibrator/persistence/XmlReader.java +++ b/core/java/com/android/internal/vibrator/persistence/XmlReader.java @@ -130,6 +130,25 @@ public final class XmlReader { } /** + * Read the next element, ignoring comments and ignorable whitespace, and returns only if it's a + * {@link XmlPullParser#TEXT}. Any other tag will fail this check. + * + * <p>The parser will be pointing to the first next element after skipping comments, + * instructions and ignorable whitespace. + */ + public static void readNextText(TypedXmlPullParser parser, String tagName) + throws XmlParserException, IOException { + try { + int type = parser.next(); // skips comments, instruction tokens and ignorable whitespace + XmlValidator.checkParserCondition(type == XmlPullParser.TEXT, + "Unexpected event %s of type %d, expected text event inside tag %s", + parser.getName(), type, tagName); + } catch (XmlPullParserException e) { + throw XmlParserException.createFromPullParserException("text event", e); + } + } + + /** * Check parser has a {@link XmlPullParser#END_TAG} as the next tag, with no nested tags. * * <p>The parser will be pointing to the end tag after this method. diff --git a/core/java/com/android/internal/vibrator/persistence/XmlSerializedVibration.java b/core/java/com/android/internal/vibrator/persistence/XmlSerializedVibration.java index 3233fa224694..c20b7d2026ec 100644 --- a/core/java/com/android/internal/vibrator/persistence/XmlSerializedVibration.java +++ b/core/java/com/android/internal/vibrator/persistence/XmlSerializedVibration.java @@ -26,8 +26,7 @@ import java.io.IOException; * Serialized representation of a generic vibration. * * <p>This can be used to represent a {@link android.os.CombinedVibration} or a - * {@link android.os.VibrationEffect}. Instances can be created from vibration objects via - * {@link XmlSerializer}, or from XML content via {@link XmlParser}. + * {@link android.os.VibrationEffect}. * * <p>The separation of serialization and writing procedures enables configurable rules to define * which vibrations can be successfully serialized before any data is written to the output stream. diff --git a/core/java/com/android/internal/vibrator/persistence/XmlSerializer.java b/core/java/com/android/internal/vibrator/persistence/XmlSerializer.java deleted file mode 100644 index 102e6c1db395..000000000000 --- a/core/java/com/android/internal/vibrator/persistence/XmlSerializer.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.internal.vibrator.persistence; - -import android.annotation.NonNull; - -/** - * Creates a {@link XmlSerializedVibration} instance representing a vibration. - * - * @param <T> The vibration type that will be serialized. - * @see XmlSerializedVibration - * @hide - */ -@FunctionalInterface -public interface XmlSerializer<T> { - - /** - * Creates a serialized representation of the input {@code vibration}. - * - * @param vibration The vibration to be serialized - * @return The serialized representation of the input vibration - * @throws XmlSerializerException If the input vibration cannot be serialized - */ - @NonNull - XmlSerializedVibration<T> serialize(@NonNull T vibration) throws XmlSerializerException; -} diff --git a/core/java/com/android/internal/vibrator/persistence/XmlSerializerException.java b/core/java/com/android/internal/vibrator/persistence/XmlSerializerException.java index c57ff5d50cd2..2e7ad090cf0f 100644 --- a/core/java/com/android/internal/vibrator/persistence/XmlSerializerException.java +++ b/core/java/com/android/internal/vibrator/persistence/XmlSerializerException.java @@ -19,7 +19,6 @@ package com.android.internal.vibrator.persistence; /** * Represents an error while serializing a vibration input. * - * @see XmlSerializer * @hide */ public final class XmlSerializerException extends Exception { diff --git a/core/java/com/android/internal/vibrator/persistence/XmlValidator.java b/core/java/com/android/internal/vibrator/persistence/XmlValidator.java index 84d4f3f49e8a..1b5a3561c3ef 100644 --- a/core/java/com/android/internal/vibrator/persistence/XmlValidator.java +++ b/core/java/com/android/internal/vibrator/persistence/XmlValidator.java @@ -18,7 +18,7 @@ package com.android.internal.vibrator.persistence; import static java.util.Objects.requireNonNull; -import android.annotation.NonNull; +import android.os.VibrationEffect; import android.text.TextUtils; import com.android.internal.util.ArrayUtils; @@ -82,11 +82,11 @@ public final class XmlValidator { * Check given {@link XmlSerializedVibration} represents the expected {@code vibration} object * when it's deserialized. */ - @NonNull - public static <T> void checkSerializedVibration( - XmlSerializedVibration<T> serializedVibration, T expectedVibration) + public static void checkSerializedVibration( + XmlSerializedVibration<? extends VibrationEffect> serializedVibration, + VibrationEffect expectedVibration) throws XmlSerializerException { - T deserializedVibration = requireNonNull(serializedVibration.deserialize()); + VibrationEffect deserializedVibration = requireNonNull(serializedVibration.deserialize()); checkSerializerCondition(Objects.equals(expectedVibration, deserializedVibration), "Unexpected serialized vibration %s: found deserialization %s, expected %s", serializedVibration, deserializedVibration, expectedVibration); diff --git a/core/jni/OWNERS b/core/jni/OWNERS index b2bc19c9b521..c0fe098c6a20 100644 --- a/core/jni/OWNERS +++ b/core/jni/OWNERS @@ -51,6 +51,10 @@ per-file EphemeralStorage* = file:platform/system/libhwbinder:/OWNERS # Sensor per-file android_hardware_SensorManager* = arthuri@google.com, bduddie@google.com, stange@google.com +# Security +per-file android_os_SELinux.cpp = file:/core/java/android/security/OWNERS +per-file android_security_* = file:/core/java/android/security/OWNERS + per-file *Zygote* = file:/ZYGOTE_OWNERS per-file core_jni_helpers.* = file:/ZYGOTE_OWNERS per-file fd_utils.* = file:/ZYGOTE_OWNERS @@ -67,7 +71,6 @@ per-file android_opengl_* = file:/opengl/java/android/opengl/OWNERS per-file android_os_storage_* = file:/core/java/android/os/storage/OWNERS per-file android_os_Trace* = file:/TRACE_OWNERS per-file android_se_* = file:/omapi/java/android/se/OWNERS -per-file android_security_* = file:/core/java/android/security/OWNERS per-file android_view_* = file:/core/java/android/view/OWNERS per-file com_android_internal_net_* = file:/services/core/java/com/android/server/net/OWNERS diff --git a/core/jni/TEST_MAPPING b/core/jni/TEST_MAPPING index ea0b01e16bdf..fa73a4d2d5a9 100644 --- a/core/jni/TEST_MAPPING +++ b/core/jni/TEST_MAPPING @@ -1,15 +1,7 @@ { "presubmit": [ { - "name": "FrameworksCoreTests", - "options": [ - { - "include-filter": "android.util.CharsetUtilsTest" - }, - { - "include-filter": "com.android.internal.util.FastDataTest" - } - ], + "name": "FrameworksCoreTests_util_data_charset", "file_patterns": ["CharsetUtils|FastData"] }, { diff --git a/core/proto/android/server/vibrator/vibratormanagerservice.proto b/core/proto/android/server/vibrator/vibratormanagerservice.proto index 12804d43b04e..e7f0560612cc 100644 --- a/core/proto/android/server/vibrator/vibratormanagerservice.proto +++ b/core/proto/android/server/vibrator/vibratormanagerservice.proto @@ -163,7 +163,7 @@ message VibratorManagerServiceDumpProto { optional bool vibrator_under_external_control = 5; optional bool low_power_mode = 6; optional bool vibrate_on = 24; - optional bool keyboard_vibration_on = 25; + reserved 25; // prev keyboard_vibration_on optional int32 default_vibration_amplitude = 26; optional int32 alarm_intensity = 18; optional int32 alarm_default_intensity = 19; diff --git a/core/res/res/layout/time_picker_text_input_material.xml b/core/res/res/layout/time_picker_text_input_material.xml index 4988842cb99c..86070b1773dc 100644 --- a/core/res/res/layout/time_picker_text_input_material.xml +++ b/core/res/res/layout/time_picker_text_input_material.xml @@ -34,19 +34,29 @@ android:layoutDirection="ltr"> <EditText android:id="@+id/input_hour" - android:layout_width="50dp" + android:layout_width="50sp" android:layout_height="wrap_content" + android:layout_alignEnd="@id/hour_label_holder" android:inputType="number" android:textAppearance="@style/TextAppearance.Material.TimePicker.InputField" android:imeOptions="actionNext"/> - <TextView - android:id="@+id/label_hour" + <!-- Ensure the label_hour takes up at least 50sp of space --> + <FrameLayout + android:id="@+id/hour_label_holder" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_below="@id/input_hour" - android:layout_alignStart="@id/input_hour" - android:labelFor="@+id/input_hour" - android:text="@string/time_picker_hour_label"/> + android:layout_below="@id/input_hour"> + <TextView + android:id="@+id/label_hour" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:labelFor="@+id/input_hour" + android:text="@string/time_picker_hour_label"/> + <Space + android:layout_width="50sp" + android:layout_height="0dp"/> + </FrameLayout> <TextView android:id="@+id/input_separator" @@ -58,21 +68,30 @@ <EditText android:id="@+id/input_minute" - android:layout_width="50dp" + android:layout_width="50sp" android:layout_height="wrap_content" android:layout_alignBaseline="@id/input_hour" android:layout_toEndOf="@id/input_separator" android:inputType="number" android:textAppearance="@style/TextAppearance.Material.TimePicker.InputField" /> - <TextView - android:id="@+id/label_minute" + <!-- Ensure the label_minute takes up at least 50sp of space --> + <FrameLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/input_minute" android:layout_alignStart="@id/input_minute" - android:labelFor="@+id/input_minute" - android:text="@string/time_picker_minute_label"/> - + > + <TextView + android:id="@+id/label_minute" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:labelFor="@+id/input_minute" + android:text="@string/time_picker_minute_label"/> + <Space + android:layout_width="50sp" + android:layout_height="0dp"/> + </FrameLayout> <TextView android:visibility="invisible" android:id="@+id/label_error" diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index df288f9eecf0..c0027f4928f5 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -4982,7 +4982,7 @@ <!-- URI for camera shutter sound --> <string translatable="false" name="config_cameraShutterSound">/product/media/audio/ui/camera_click.ogg</string> - <!-- URI for default ringtone sound file to be used for silent ringer vibration --> + <!-- @deprecated This configuration is no longer used. --> <string translatable="false" name="config_defaultRingtoneVibrationSound"></string> <!-- Default number of notifications from the same app before they are automatically grouped by the OS --> diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp index 5793bbe306f1..2bbaf9cb0cda 100644 --- a/core/tests/coretests/Android.bp +++ b/core/tests/coretests/Android.bp @@ -249,6 +249,7 @@ android_ravenwood_test { ], srcs: [ "src/android/app/ActivityManagerTest.java", + "src/android/colormodel/CamTest.java", "src/android/content/ContextTest.java", "src/android/content/pm/PackageManagerTest.java", "src/android/content/pm/UserInfoTest.java", diff --git a/core/tests/coretests/src/android/colormodel/CamTest.java b/core/tests/coretests/src/android/colormodel/CamTest.java index 05fc0e04515c..cf398db22d16 100644 --- a/core/tests/coretests/src/android/colormodel/CamTest.java +++ b/core/tests/coretests/src/android/colormodel/CamTest.java @@ -18,9 +18,12 @@ package com.android.internal.graphics.cam; import static org.junit.Assert.assertEquals; +import android.platform.test.ravenwood.RavenwoodRule; + import androidx.test.filters.LargeTest; import org.junit.Assert; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -35,6 +38,9 @@ public final class CamTest { static final int GREEN = 0xff00ff00; static final int BLUE = 0xff0000ff; + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); + @Test public void camFromIntToInt() { Cam cam = Cam.fromInt(RED); diff --git a/core/tests/coretests/src/android/content/TEST_MAPPING b/core/tests/coretests/src/android/content/TEST_MAPPING index bbc2458f5d8b..fd9fda3ab96a 100644 --- a/core/tests/coretests/src/android/content/TEST_MAPPING +++ b/core/tests/coretests/src/android/content/TEST_MAPPING @@ -1,18 +1,7 @@ { "presubmit": [ { - "name": "FrameworksCoreTests", - "options": [ - { - "include-filter": "android.content.ContentCaptureOptionsTest" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - } - ] + "name": "FrameworksCoreTests_content_capture_options" } ] } diff --git a/core/tests/coretests/src/android/content/integrity/TEST_MAPPING b/core/tests/coretests/src/android/content/integrity/TEST_MAPPING index 2920716f5d5d..d22fe84f3d84 100644 --- a/core/tests/coretests/src/android/content/integrity/TEST_MAPPING +++ b/core/tests/coretests/src/android/content/integrity/TEST_MAPPING @@ -1,12 +1,7 @@ { "presubmit": [ { - "name": "FrameworksCoreTests", - "options": [ - { - "include-filter": "android.content.integrity." - } - ] + "name": "FrameworksCoreTests_android_content_integrity" } ] } diff --git a/core/tests/coretests/src/android/content/pm/TEST_MAPPING b/core/tests/coretests/src/android/content/pm/TEST_MAPPING index 978d80cb52f6..9ab438ef9fd2 100644 --- a/core/tests/coretests/src/android/content/pm/TEST_MAPPING +++ b/core/tests/coretests/src/android/content/pm/TEST_MAPPING @@ -1,21 +1,7 @@ { "presubmit": [ { - "name": "FrameworksCoreTests", - "options": [ - { - "include-filter": "android.content.pm." - }, - { - "include-annotation": "android.platform.test.annotations.Presubmit" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - } - ] + "name": "FrameworksCoreTests_android_content_pm_PreSubmit" } ], "postsubmit": [ diff --git a/core/tests/coretests/src/android/content/res/TEST_MAPPING b/core/tests/coretests/src/android/content/res/TEST_MAPPING index 4ea6e40a7225..25927de55d33 100644 --- a/core/tests/coretests/src/android/content/res/TEST_MAPPING +++ b/core/tests/coretests/src/android/content/res/TEST_MAPPING @@ -1,24 +1,7 @@ { "presubmit": [ { - "name": "FrameworksCoreTests", - "options": [ - { - "include-filter": "android.content.res." - }, - { - "include-annotation": "android.platform.test.annotations.Presubmit" - }, - { - "exclude-annotation": "android.platform.test.annotations.Postsubmit" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - } - ] + "name": "FrameworksCoreTests_android_content_res" } ], "postsubmit": [ diff --git a/core/tests/coretests/src/android/graphics/PaintFontVariationTest.java b/core/tests/coretests/src/android/graphics/PaintFontVariationTest.java deleted file mode 100644 index 8a54e5b998e7..000000000000 --- a/core/tests/coretests/src/android/graphics/PaintFontVariationTest.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.graphics; - -import static com.google.common.truth.Truth.assertThat; - -import android.platform.test.annotations.RequiresFlagsEnabled; -import android.platform.test.flag.junit.CheckFlagsRule; -import android.platform.test.flag.junit.DeviceFlagsValueProvider; -import android.test.InstrumentationTestCase; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import com.android.text.flags.Flags; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; - -/** - * PaintTest tests {@link Paint}. - */ -@SmallTest -@RunWith(AndroidJUnit4.class) -public class PaintFontVariationTest extends InstrumentationTestCase { - @Rule - public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); - - @RequiresFlagsEnabled(Flags.FLAG_TYPEFACE_CACHE_FOR_VAR_SETTINGS) - @Test - public void testDerivedFromSameTypeface() { - final Paint p = new Paint(); - - p.setTypeface(Typeface.SANS_SERIF); - assertThat(p.setFontVariationSettings("'wght' 450")).isTrue(); - Typeface first = p.getTypeface(); - - p.setTypeface(Typeface.SANS_SERIF); - assertThat(p.setFontVariationSettings("'wght' 480")).isTrue(); - Typeface second = p.getTypeface(); - - assertThat(first.getDerivedFrom()).isSameInstanceAs(second.getDerivedFrom()); - } - - @RequiresFlagsEnabled(Flags.FLAG_TYPEFACE_CACHE_FOR_VAR_SETTINGS) - @Test - public void testDerivedFromChained() { - final Paint p = new Paint(); - - p.setTypeface(Typeface.SANS_SERIF); - assertThat(p.setFontVariationSettings("'wght' 450")).isTrue(); - Typeface first = p.getTypeface(); - - assertThat(p.setFontVariationSettings("'wght' 480")).isTrue(); - Typeface second = p.getTypeface(); - - assertThat(first.getDerivedFrom()).isSameInstanceAs(second.getDerivedFrom()); - } -} diff --git a/core/tests/coretests/src/android/graphics/PaintTest.java b/core/tests/coretests/src/android/graphics/PaintTest.java index 878ba703c8fe..0dec756d7611 100644 --- a/core/tests/coretests/src/android/graphics/PaintTest.java +++ b/core/tests/coretests/src/android/graphics/PaintTest.java @@ -16,22 +16,13 @@ package android.graphics; -import static com.google.common.truth.Truth.assertThat; - import static org.junit.Assert.assertNotEquals; -import android.platform.test.annotations.RequiresFlagsEnabled; -import android.platform.test.flag.junit.CheckFlagsRule; -import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.test.InstrumentationTestCase; import android.text.TextUtils; import androidx.test.filters.SmallTest; -import com.android.text.flags.Flags; - -import org.junit.Rule; - import java.util.Arrays; import java.util.HashSet; @@ -39,9 +30,6 @@ import java.util.HashSet; * PaintTest tests {@link Paint}. */ public class PaintTest extends InstrumentationTestCase { - @Rule - public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); - private static final String FONT_PATH = "fonts/HintedAdvanceWidthTest-Regular.ttf"; static void assertEquals(String message, float[] expected, float[] actual) { @@ -415,33 +403,4 @@ public class PaintTest extends InstrumentationTestCase { assertEquals(6, getClusterCount(p, rtlStr + ltrStr)); assertEquals(9, getClusterCount(p, ltrStr + rtlStr + ltrStr)); } - - @RequiresFlagsEnabled(Flags.FLAG_TYPEFACE_CACHE_FOR_VAR_SETTINGS) - public void testDerivedFromSameTypeface() { - final Paint p = new Paint(); - - p.setTypeface(Typeface.SANS_SERIF); - assertThat(p.setFontVariationSettings("'wght' 450")).isTrue(); - Typeface first = p.getTypeface(); - - p.setTypeface(Typeface.SANS_SERIF); - assertThat(p.setFontVariationSettings("'wght' 480")).isTrue(); - Typeface second = p.getTypeface(); - - assertThat(first.getDerivedFrom()).isSameInstanceAs(second.getDerivedFrom()); - } - - @RequiresFlagsEnabled(Flags.FLAG_TYPEFACE_CACHE_FOR_VAR_SETTINGS) - public void testDerivedFromChained() { - final Paint p = new Paint(); - - p.setTypeface(Typeface.SANS_SERIF); - assertThat(p.setFontVariationSettings("'wght' 450")).isTrue(); - Typeface first = p.getTypeface(); - - assertThat(p.setFontVariationSettings("'wght' 480")).isTrue(); - Typeface second = p.getTypeface(); - - assertThat(first.getDerivedFrom()).isSameInstanceAs(second.getDerivedFrom()); - } } diff --git a/core/tests/coretests/src/android/service/TEST_MAPPING b/core/tests/coretests/src/android/service/TEST_MAPPING index bec72d988e74..21f248d3d799 100644 --- a/core/tests/coretests/src/android/service/TEST_MAPPING +++ b/core/tests/coretests/src/android/service/TEST_MAPPING @@ -1,17 +1,7 @@ { "presubmit": [ { - "name": "FrameworksCoreTests", - "options": [ - {"include-filter": "android.service.controls"}, - {"include-filter": "android.service.controls.actions"}, - {"include-filter": "android.service.controls.templates"}, - {"include-filter": "android.service.euicc"}, - {"include-filter": "android.service.notification"}, - {"include-filter": "android.service.quicksettings"}, - {"include-filter": "android.service.settings.suggestions"}, - {"exclude-annotation": "org.junit.Ignore"} - ] + "name": "FrameworksCoreTests_android_service" } ] } diff --git a/core/tests/coretests/src/android/view/contentcapture/TEST_MAPPING b/core/tests/coretests/src/android/view/contentcapture/TEST_MAPPING index f8beac2814db..c2cf40dc4ebe 100644 --- a/core/tests/coretests/src/android/view/contentcapture/TEST_MAPPING +++ b/core/tests/coretests/src/android/view/contentcapture/TEST_MAPPING @@ -1,18 +1,7 @@ { "presubmit": [ { - "name": "FrameworksCoreTests", - "options": [ - { - "include-filter": "android.view.contentcapture" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - } - ] + "name": "FrameworksCoreTests_android_view_contentcapture" } ] } diff --git a/core/tests/coretests/src/android/view/contentprotection/TEST_MAPPING b/core/tests/coretests/src/android/view/contentprotection/TEST_MAPPING index 3cd4e17d820b..3ef1ac1e6978 100644 --- a/core/tests/coretests/src/android/view/contentprotection/TEST_MAPPING +++ b/core/tests/coretests/src/android/view/contentprotection/TEST_MAPPING @@ -1,18 +1,7 @@ { "presubmit": [ { - "name": "FrameworksCoreTests", - "options": [ - { - "include-filter": "android.view.contentprotection" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - } - ] + "name": "FrameworksCoreTests_android_view_contentprotection" } ] } diff --git a/core/tests/coretests/src/com/android/internal/content/res/TEST_MAPPING b/core/tests/coretests/src/com/android/internal/content/res/TEST_MAPPING index 9aed8be4f10f..4a46244e0162 100644 --- a/core/tests/coretests/src/com/android/internal/content/res/TEST_MAPPING +++ b/core/tests/coretests/src/com/android/internal/content/res/TEST_MAPPING @@ -1,21 +1,7 @@ { "presubmit": [ { - "name": "FrameworksCoreTests", - "options": [ - { - "include-filter": "com.android.internal.content." - }, - { - "include-annotation": "android.platform.test.annotations.Presubmit" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - } - ] + "name": "FrameworksCoreTests_com_android_internal_content_Presubmit" } ] } diff --git a/core/tests/resourceflaggingtests/Android.bp b/core/tests/resourceflaggingtests/Android.bp index dd86094127da..efb843735aef 100644 --- a/core/tests/resourceflaggingtests/Android.bp +++ b/core/tests/resourceflaggingtests/Android.bp @@ -22,54 +22,6 @@ package { default_team: "trendy_team_android_resources", } -genrule { - name: "resource-flagging-test-app-resources-compile", - tools: ["aapt2"], - srcs: [ - "flagged_resources_res/values/bools.xml", - ], - out: ["values_bools.arsc.flat"], - cmd: "$(location aapt2) compile $(in) -o $(genDir) " + - "--feature-flags test.package.falseFlag:ro=false,test.package.trueFlag:ro=true", -} - -genrule { - name: "resource-flagging-test-app-resources-compile2", - tools: ["aapt2"], - srcs: [ - "flagged_resources_res/values/bools2.xml", - ], - out: ["values_bools2.arsc.flat"], - cmd: "$(location aapt2) compile $(in) -o $(genDir) " + - "--feature-flags test.package.falseFlag:ro=false,test.package.trueFlag:ro=true", -} - -genrule { - name: "resource-flagging-test-app-apk", - tools: ["aapt2"], - // The first input file in the list must be the manifest - srcs: [ - "TestAppAndroidManifest.xml", - ":resource-flagging-test-app-resources-compile", - ":resource-flagging-test-app-resources-compile2", - ], - out: ["resapp.apk"], - cmd: "$(location aapt2) link -o $(out) --manifest $(in)", -} - -java_genrule { - name: "resource-flagging-apk-as-resource", - srcs: [ - ":resource-flagging-test-app-apk", - ], - out: ["apks_as_resources.res.zip"], - tools: ["soong_zip"], - - cmd: "mkdir -p $(genDir)/res/raw && " + - "cp $(in) $(genDir)/res/raw/$$(basename $(in)) && " + - "$(location soong_zip) -o $(out) -C $(genDir)/res -D $(genDir)/res", -} - android_test { name: "ResourceFlaggingTests", srcs: [ @@ -82,6 +34,6 @@ android_test { "testng", "compatibility-device-util-axt", ], - resource_zips: [":resource-flagging-apk-as-resource"], + resource_zips: [":resource-flagging-test-app-apk-as-resource"], test_suites: ["device-tests"], } diff --git a/core/tests/resourceflaggingtests/src/com/android/resourceflaggingtests/ResourceFlaggingTest.java b/core/tests/resourceflaggingtests/src/com/android/resourceflaggingtests/ResourceFlaggingTest.java index ad8542e0f6b3..c1e357864fff 100644 --- a/core/tests/resourceflaggingtests/src/com/android/resourceflaggingtests/ResourceFlaggingTest.java +++ b/core/tests/resourceflaggingtests/src/com/android/resourceflaggingtests/ResourceFlaggingTest.java @@ -69,11 +69,23 @@ public class ResourceFlaggingTest { } private boolean getBoolean(String name) { - int resId = mResources.getIdentifier(name, "bool", "com.android.intenal.flaggedresources"); + int resId = mResources.getIdentifier( + name, + "bool", + "com.android.intenal.flaggedresources"); assertThat(resId).isNotEqualTo(0); return mResources.getBoolean(resId); } + private String getString(String name) { + int resId = mResources.getIdentifier( + name, + "string", + "com.android.intenal.flaggedresources"); + assertThat(resId).isNotEqualTo(0); + return mResources.getString(resId); + } + private String extractApkAndGetPath(int id) throws Exception { final Resources resources = mContext.getResources(); try (InputStream is = resources.openRawResource(id)) { diff --git a/core/tests/vibrator/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java b/core/tests/vibrator/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java index bf9a820aca5c..1cc38ded1c2c 100644 --- a/core/tests/vibrator/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java +++ b/core/tests/vibrator/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java @@ -24,22 +24,32 @@ import static android.os.vibrator.persistence.VibrationXmlParser.isSupportedMime import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertThrows; +import android.os.PersistableBundle; import android.os.VibrationEffect; +import android.os.vibrator.Flags; import android.os.vibrator.PrebakedSegment; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; import android.util.Xml; import com.android.modules.utils.TypedXmlPullParser; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.xmlpull.v1.XmlPullParser; +import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.io.StringReader; import java.io.StringWriter; import java.util.Arrays; +import java.util.Base64; import java.util.HashMap; import java.util.Map; import java.util.Set; @@ -53,6 +63,9 @@ import java.util.Set; @RunWith(JUnit4.class) public class VibrationEffectXmlSerializationTest { + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Test public void isSupportedMimeType_onlySupportsVibrationXmlMimeType() { // Single MIME type supported @@ -422,6 +435,97 @@ public class VibrationEffectXmlSerializationTest { } } + @Test + @EnableFlags(Flags.FLAG_VENDOR_VIBRATION_EFFECTS) + public void testVendorEffect_featureFlagEnabled_allSucceed() throws Exception { + PersistableBundle vendorData = new PersistableBundle(); + vendorData.putInt("id", 1); + vendorData.putDouble("scale", 0.5); + vendorData.putBoolean("loop", false); + vendorData.putLongArray("amplitudes", new long[] { 0, 255, 128 }); + vendorData.putString("label", "vibration"); + + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + vendorData.writeToStream(outputStream); + String vendorDataStr = Base64.getEncoder().encodeToString(outputStream.toByteArray()); + + VibrationEffect effect = VibrationEffect.createVendorEffect(vendorData); + String xml = "<vibration-effect><vendor-effect> " // test trailing whitespace is ignored + + vendorDataStr + + " \n </vendor-effect></vibration-effect>"; + + assertPublicApisParserSucceeds(xml, effect); + assertPublicApisSerializerSucceeds(effect, vendorDataStr); + assertPublicApisRoundTrip(effect); + + assertHiddenApisParserSucceeds(xml, effect); + assertHiddenApisSerializerSucceeds(effect, vendorDataStr); + assertHiddenApisRoundTrip(effect); + + // Check PersistableBundle from round-trip + PersistableBundle parsedVendorData = + ((VibrationEffect.VendorEffect) parseVibrationEffect(serialize(effect), + /* flags= */ 0)).getVendorData(); + assertThat(parsedVendorData.size()).isEqualTo(vendorData.size()); + assertThat(parsedVendorData.getInt("id")).isEqualTo(1); + assertThat(parsedVendorData.getDouble("scale")).isEqualTo(0.5); + assertThat(parsedVendorData.getBoolean("loop")).isFalse(); + assertArrayEquals(parsedVendorData.getLongArray("amplitudes"), new long[] { 0, 255, 128 }); + assertThat(parsedVendorData.getString("label")).isEqualTo("vibration"); + } + + @Test + @EnableFlags(Flags.FLAG_VENDOR_VIBRATION_EFFECTS) + public void testInvalidVendorEffect_featureFlagEnabled_allFail() throws IOException { + String emptyTag = "<vibration-effect><vendor-effect/></vibration-effect>"; + assertPublicApisParserFails(emptyTag); + assertHiddenApisParserFails(emptyTag); + + String emptyStringTag = + "<vibration-effect><vendor-effect> \n </vendor-effect></vibration-effect>"; + assertPublicApisParserFails(emptyStringTag); + assertHiddenApisParserFails(emptyStringTag); + + String invalidString = + "<vibration-effect><vendor-effect>invalid</vendor-effect></vibration-effect>"; + assertPublicApisParserFails(invalidString); + assertHiddenApisParserFails(invalidString); + + String validBase64String = + "<vibration-effect><vendor-effect>c29tZXNh</vendor-effect></vibration-effect>"; + assertPublicApisParserFails(validBase64String); + assertHiddenApisParserFails(validBase64String); + + PersistableBundle emptyData = new PersistableBundle(); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + emptyData.writeToStream(outputStream); + String emptyBundleString = "<vibration-effect><vendor-effect>" + + Base64.getEncoder().encodeToString(outputStream.toByteArray()) + + "</vendor-effect></vibration-effect>"; + assertPublicApisParserFails(emptyBundleString); + assertHiddenApisParserFails(emptyBundleString); + } + + @Test + @DisableFlags(Flags.FLAG_VENDOR_VIBRATION_EFFECTS) + public void testVendorEffect_featureFlagDisabled_allFail() throws Exception { + PersistableBundle vendorData = new PersistableBundle(); + vendorData.putInt("id", 1); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + vendorData.writeToStream(outputStream); + String vendorDataStr = Base64.getEncoder().encodeToString(outputStream.toByteArray()); + String xml = "<vibration-effect><vendor-effect>" + + vendorDataStr + + "</vendor-effect></vibration-effect>"; + VibrationEffect vendorEffect = VibrationEffect.createVendorEffect(vendorData); + + assertPublicApisParserFails(xml); + assertPublicApisSerializerFails(vendorEffect); + + assertHiddenApisParserFails(xml); + assertHiddenApisSerializerFails(vendorEffect); + } + private void assertPublicApisParserFails(String xml) { assertThrows("Expected parseVibrationEffect to fail for " + xml, VibrationXmlParser.ParseFailedException.class, @@ -493,6 +597,12 @@ public class VibrationEffectXmlSerializationTest { () -> serialize(effect)); } + private void assertHiddenApisSerializerFails(VibrationEffect effect) { + assertThrows("Expected serialization to fail for " + effect, + VibrationXmlSerializer.SerializationFailedException.class, + () -> serialize(effect, VibrationXmlSerializer.FLAG_ALLOW_HIDDEN_APIS)); + } + private void assertPublicApisSerializerSucceeds(VibrationEffect effect, String... expectedSegments) throws Exception { assertSerializationContainsSegments(serialize(effect), expectedSegments); diff --git a/core/xsd/vibrator/vibration/schema/current.txt b/core/xsd/vibrator/vibration/schema/current.txt index f0e13c4e8ca3..280b40516b7e 100644 --- a/core/xsd/vibrator/vibration/schema/current.txt +++ b/core/xsd/vibrator/vibration/schema/current.txt @@ -41,9 +41,11 @@ package com.android.internal.vibrator.persistence { ctor public VibrationEffect(); method public com.android.internal.vibrator.persistence.PredefinedEffect getPredefinedEffect_optional(); method public com.android.internal.vibrator.persistence.PrimitiveEffect getPrimitiveEffect_optional(); + method public byte[] getVendorEffect_optional(); method public com.android.internal.vibrator.persistence.WaveformEffect getWaveformEffect_optional(); method public void setPredefinedEffect_optional(com.android.internal.vibrator.persistence.PredefinedEffect); method public void setPrimitiveEffect_optional(com.android.internal.vibrator.persistence.PrimitiveEffect); + method public void setVendorEffect_optional(byte[]); method public void setWaveformEffect_optional(com.android.internal.vibrator.persistence.WaveformEffect); } diff --git a/core/xsd/vibrator/vibration/vibration-plus-hidden-apis.xsd b/core/xsd/vibrator/vibration/vibration-plus-hidden-apis.xsd index fcd250b4fc06..21a6facad242 100644 --- a/core/xsd/vibrator/vibration/vibration-plus-hidden-apis.xsd +++ b/core/xsd/vibrator/vibration/vibration-plus-hidden-apis.xsd @@ -46,6 +46,9 @@ <!-- Predefined vibration effect --> <xs:element name="predefined-effect" type="PredefinedEffect"/> + <!-- Vendor vibration effect --> + <xs:element name="vendor-effect" type="VendorEffect"/> + <!-- Primitive composition effect --> <xs:sequence> <xs:element name="primitive-effect" type="PrimitiveEffect"/> @@ -136,6 +139,10 @@ </xs:restriction> </xs:simpleType> + <xs:simpleType name="VendorEffect"> + <xs:restriction base="xs:base64Binary"/> + </xs:simpleType> + <xs:complexType name="PrimitiveEffect"> <xs:attribute name="name" type="PrimitiveEffectName" use="required"/> <xs:attribute name="scale" type="PrimitiveScale"/> diff --git a/core/xsd/vibrator/vibration/vibration.xsd b/core/xsd/vibrator/vibration/vibration.xsd index b9de6914b9dc..d35d777d4450 100644 --- a/core/xsd/vibrator/vibration/vibration.xsd +++ b/core/xsd/vibrator/vibration/vibration.xsd @@ -44,6 +44,9 @@ <!-- Predefined vibration effect --> <xs:element name="predefined-effect" type="PredefinedEffect"/> + <!-- Vendor vibration effect --> + <xs:element name="vendor-effect" type="VendorEffect"/> + <!-- Primitive composition effect --> <xs:sequence> <xs:element name="primitive-effect" type="PrimitiveEffect"/> @@ -113,6 +116,10 @@ </xs:restriction> </xs:simpleType> + <xs:simpleType name="VendorEffect"> + <xs:restriction base="xs:base64Binary"/> + </xs:simpleType> + <xs:complexType name="PrimitiveEffect"> <xs:attribute name="name" type="PrimitiveEffectName" use="required"/> <xs:attribute name="scale" type="PrimitiveScale"/> diff --git a/data/etc/Android.bp b/data/etc/Android.bp index 050f9b5e264f..8f85617acae3 100644 --- a/data/etc/Android.bp +++ b/data/etc/Android.bp @@ -78,6 +78,12 @@ prebuilt_etc { src: "package-shareduid-allowlist.xml", } +prebuilt_etc { + name: "oem-defined-uids.xml", + sub_dir: "sysconfig", + src: "oem-defined-uids.xml", +} + // Privapp permission whitelist files prebuilt_etc { diff --git a/data/etc/oem-defined-uids.xml b/data/etc/oem-defined-uids.xml new file mode 100644 index 000000000000..87435b9cd04a --- /dev/null +++ b/data/etc/oem-defined-uids.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2024 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<!-- +This XML defines a list of UIDs for OEMs to register as shared UIDs. They will be registered at the +start of the system, which allows OEMs to create services with these UIDs. The range of these UIDs +must be in the OEM reserved range. + +OEM must provide a preloaded app that is installed at boot time to retain the newly registered UID +by adding a android:sharedUserId tag in the manifest of the preloaded app, with the value of the tag +set to the name of the UID defined in this config file. Otherwise, the uid will be cleared at the +end of the boot and this config file will take no effect. + +- The "name" XML attribute refers to the name of the shared UID. It must start with "android.uid.". +- The "uid" XML attribute refers to the value of the shared UID. It must be in range [2900, 2999]. + +Example usage + <oem-defined-uid name="android.uid.vendordata" uid="2918"/> + Indicates that a shared UID named "android.uid.vendordata" will be added to the system with the + UID of 2918. +--> + +<config> +</config> diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java index 889a778556b7..fd788167a0d8 100644 --- a/graphics/java/android/graphics/Typeface.java +++ b/graphics/java/android/graphics/Typeface.java @@ -56,7 +56,6 @@ import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; -import com.android.text.flags.Flags; import dalvik.annotation.optimization.CriticalNative; import dalvik.annotation.optimization.FastNative; @@ -75,7 +74,6 @@ import java.nio.ByteOrder; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Objects; @@ -145,23 +143,6 @@ public class Typeface { private static final LruCache<String, Typeface> sDynamicTypefaceCache = new LruCache<>(16); private static final Object sDynamicCacheLock = new Object(); - private static final LruCache<Long, LruCache<String, Typeface>> sVariableCache = - new LruCache<>(16); - private static final Object sVariableCacheLock = new Object(); - - /** @hide */ - @VisibleForTesting - public static void clearTypefaceCachesForTestingPurpose() { - synchronized (sWeightCacheLock) { - sWeightTypefaceCache.clear(); - } - synchronized (sDynamicCacheLock) { - sDynamicTypefaceCache.evictAll(); - } - synchronized (sVariableCacheLock) { - sVariableCache.evictAll(); - } - } @GuardedBy("SYSTEM_FONT_MAP_LOCK") static Typeface sDefaultTypeface; @@ -214,8 +195,6 @@ public class Typeface { @UnsupportedAppUsage public final long native_instance; - private final Typeface mDerivedFrom; - private final String mSystemFontFamilyName; private final Runnable mCleaner; @@ -295,18 +274,6 @@ public class Typeface { } /** - * Returns the Typeface used for creating this Typeface. - * - * Maybe null if this is not derived from other Typeface. - * TODO(b/357707916): Make this public API. - * @hide - */ - @VisibleForTesting - public final @Nullable Typeface getDerivedFrom() { - return mDerivedFrom; - } - - /** * Returns the system font family name if the typeface was created from a system font family, * otherwise returns null. */ @@ -1054,51 +1021,9 @@ public class Typeface { return typeface; } - private static String axesToVarKey(@NonNull List<FontVariationAxis> axes) { - // The given list can be mutated because it is allocated in Paint#setFontVariationSettings. - // Currently, Paint#setFontVariationSettings is the only code path reaches this method. - axes.sort(Comparator.comparingInt(FontVariationAxis::getOpenTypeTagValue)); - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < axes.size(); ++i) { - final FontVariationAxis fva = axes.get(i); - sb.append(fva.getTag()); - sb.append(fva.getStyleValue()); - } - return sb.toString(); - } - - /** - * TODO(b/357707916): Make this public API. - * @hide - */ + /** @hide */ public static Typeface createFromTypefaceWithVariation(@Nullable Typeface family, @NonNull List<FontVariationAxis> axes) { - if (Flags.typefaceCacheForVarSettings()) { - final Typeface target = (family == null) ? Typeface.DEFAULT : family; - final Typeface base = (target.mDerivedFrom == null) ? target : target.mDerivedFrom; - - final String key = axesToVarKey(axes); - - synchronized (sVariableCacheLock) { - LruCache<String, Typeface> innerCache = sVariableCache.get(base.native_instance); - if (innerCache == null) { - // Cache up to 16 var instance per root Typeface - innerCache = new LruCache<>(16); - sVariableCache.put(base.native_instance, innerCache); - } else { - Typeface cached = innerCache.get(key); - if (cached != null) { - return cached; - } - } - Typeface typeface = new Typeface( - nativeCreateFromTypefaceWithVariation(base.native_instance, axes), - base.getSystemFontFamilyName(), base); - innerCache.put(key, typeface); - return typeface; - } - } - final Typeface base = family == null ? Typeface.DEFAULT : family; Typeface typeface = new Typeface( nativeCreateFromTypefaceWithVariation(base.native_instance, axes), @@ -1259,19 +1184,11 @@ public class Typeface { // don't allow clients to call this directly @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private Typeface(long ni) { - this(ni, null, null); + this(ni, null); } - // don't allow clients to call this directly - // This is kept for robolectric. private Typeface(long ni, @Nullable String systemFontFamilyName) { - this(ni, systemFontFamilyName, null); - } - - // don't allow clients to call this directly - private Typeface(long ni, @Nullable String systemFontFamilyName, - @Nullable Typeface derivedFrom) { if (ni == 0) { throw new RuntimeException("native typeface cannot be made"); } @@ -1281,7 +1198,6 @@ public class Typeface { mStyle = nativeGetStyle(ni); mWeight = nativeGetWeight(ni); mSystemFontFamilyName = systemFontFamilyName; - mDerivedFrom = derivedFrom; } /** diff --git a/graphics/java/android/graphics/drawable/TEST_MAPPING b/graphics/java/android/graphics/drawable/TEST_MAPPING index 1018702e01c5..4f064522b037 100644 --- a/graphics/java/android/graphics/drawable/TEST_MAPPING +++ b/graphics/java/android/graphics/drawable/TEST_MAPPING @@ -12,13 +12,7 @@ }, { - "name": "FrameworksCoreTests", - "file_patterns": ["(/|^)Icon\\.java"], - "options" : [ - { - "include-filter": "android.graphics.drawable.IconTest" - } - ] + "name": "FrameworksCoreTests_drawable" } ] } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index 25e710784a8f..26d180cdcb1a 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -695,12 +695,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen break; case TYPE_ACTIVITY_REPARENTED_TO_TASK: final IBinder candidateAssociatedActToken, lastOverlayToken; - if (Flags.fixPipRestoreToOverlay()) { - candidateAssociatedActToken = change.getOtherActivityToken(); - lastOverlayToken = change.getTaskFragmentToken(); - } else { - candidateAssociatedActToken = lastOverlayToken = null; - } + candidateAssociatedActToken = change.getOtherActivityToken(); + lastOverlayToken = change.getTaskFragmentToken(); onActivityReparentedToTask( wct, taskId, @@ -1023,10 +1019,6 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @Nullable OverlayContainerRestoreParams getOverlayContainerRestoreParams( @Nullable IBinder associatedActivityToken, @Nullable IBinder overlayToken) { - if (!Flags.fixPipRestoreToOverlay()) { - return null; - } - if (associatedActivityToken == null || overlayToken == null) { return null; } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java index d0e2c998e961..ee3e6f368505 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java @@ -36,7 +36,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; -import com.android.window.flags.Flags; import java.util.ArrayList; import java.util.Collections; @@ -257,7 +256,7 @@ class TaskFragmentContainer { mPendingAppearedIntent = pendingAppearedIntent; // Save the information necessary for restoring the overlay when needed. - if (Flags.fixPipRestoreToOverlay() && overlayTag != null && pendingAppearedIntent != null + if (overlayTag != null && pendingAppearedIntent != null && associatedActivity != null && !associatedActivity.isFinishing()) { final IBinder associatedActivityToken = associatedActivity.getActivityToken(); final OverlayContainerRestoreParams params = new OverlayContainerRestoreParams(mToken, diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java index 475475b05272..90eeb583d070 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java @@ -874,8 +874,6 @@ public class OverlayPresentationTest { @Test public void testOnActivityReparentedToTask_overlayRestoration() { - mSetFlagRule.enableFlags(Flags.FLAG_FIX_PIP_RESTORE_TO_OVERLAY); - // Prepares and mock the data necessary for the test. final IBinder activityToken = mActivity.getActivityToken(); final Intent intent = new Intent(); diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp index 5135e9ee14bc..1c3d9c30c91c 100644 --- a/libs/WindowManager/Shell/Android.bp +++ b/libs/WindowManager/Shell/Android.bp @@ -166,6 +166,16 @@ java_library { }, } +java_library { + name: "WindowManager-Shell-lite-proto", + + srcs: ["src/com/android/wm/shell/desktopmode/education/data/proto/**/*.proto"], + + proto: { + type: "lite", + }, +} + filegroup { name: "wm_shell-shared-aidls", @@ -215,6 +225,7 @@ android_library { "androidx.core_core-animation", "androidx.core_core-ktx", "androidx.arch.core_core-runtime", + "androidx.datastore_datastore", "androidx.compose.material3_material3", "androidx-constraintlayout_constraintlayout", "androidx.dynamicanimation_dynamicanimation", @@ -225,6 +236,7 @@ android_library { "//frameworks/libs/systemui:iconloader_base", "com_android_wm_shell_flags_lib", "WindowManager-Shell-proto", + "WindowManager-Shell-lite-proto", "WindowManager-Shell-shared", "perfetto_trace_java_protos", "dagger2", diff --git a/libs/WindowManager/Shell/OWNERS b/libs/WindowManager/Shell/OWNERS index 2e19d52fb4bb..c6044a45200d 100644 --- a/libs/WindowManager/Shell/OWNERS +++ b/libs/WindowManager/Shell/OWNERS @@ -1,5 +1,5 @@ xutan@google.com # Give submodule owners in shell resource approval -per-file res*/*/*.xml = atsjenk@google.com, hwwang@google.com, jorgegil@google.com, lbill@google.com, madym@google.com, vaniadesmonda@google.com, pbdr@google.com, tkachenkoi@google.com, mpodolian@google.com, liranb@google.com, pragyabajoria@google.com, uysalorhan@google.com, gsennton@google.com +per-file res*/*/*.xml = atsjenk@google.com, hwwang@google.com, jorgegil@google.com, lbill@google.com, madym@google.com, vaniadesmonda@google.com, pbdr@google.com, tkachenkoi@google.com, mpodolian@google.com, liranb@google.com, pragyabajoria@google.com, uysalorhan@google.com, gsennton@google.com, mattsziklay@google.com, mdehaini@google.com per-file res*/*/tv_*.xml = bronger@google.com diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt index 5e673338bad3..84f7bb27ca82 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt @@ -53,6 +53,7 @@ import org.junit.runner.RunWith import org.mockito.kotlin.mock import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags +import com.android.wm.shell.common.bubbles.BubbleBarLocation import java.util.concurrent.Semaphore import java.util.concurrent.TimeUnit import java.util.function.Consumer @@ -458,5 +459,7 @@ class BubbleStackViewTest { override fun isShowingAsBubbleBar(): Boolean = false override fun hideCurrentInputMethod() {} + + override fun updateBubbleBarLocation(location: BubbleBarLocation) {} } } diff --git a/libs/WindowManager/Shell/res/values/ids.xml b/libs/WindowManager/Shell/res/values/ids.xml index bc59a235517d..debcba071d9c 100644 --- a/libs/WindowManager/Shell/res/values/ids.xml +++ b/libs/WindowManager/Shell/res/values/ids.xml @@ -42,6 +42,8 @@ <item type="id" name="action_move_top_right"/> <item type="id" name="action_move_bottom_left"/> <item type="id" name="action_move_bottom_right"/> + <item type="id" name="action_move_bubble_bar_left"/> + <item type="id" name="action_move_bubble_bar_right"/> <item type="id" name="dismiss_view"/> </resources> diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml index 0a8166fb1a8d..6a62d7a373c8 100644 --- a/libs/WindowManager/Shell/res/values/strings.xml +++ b/libs/WindowManager/Shell/res/values/strings.xml @@ -139,6 +139,14 @@ <string name="bubble_accessibility_action_move_bottom_left">Move bottom left</string> <!-- Action in accessibility menu to move the stack of bubbles to the bottom right of the screen. [CHAR LIMIT=30]--> <string name="bubble_accessibility_action_move_bottom_right">Move bottom right</string> + <!-- Click action label for bubbles to expand menu. [CHAR LIMIT=30]--> + <string name="bubble_accessibility_action_expand_menu">expand menu</string> + <!-- Click action label for bubbles to collapse menu. [CHAR LIMIT=30]--> + <string name="bubble_accessibility_action_collapse_menu">collapse menu</string> + <!-- Action in accessibility menu to move the bubble bar to the left side of the screen. [CHAR_LIMIT=30] --> + <string name="bubble_accessibility_action_move_bar_left">Move left</string> + <!-- Action in accessibility menu to move the bubble bar to the right side of the screen. [CHAR_LIMIT=30] --> + <string name="bubble_accessibility_action_move_bar_right">Move right</string> <!-- Accessibility announcement when the stack of bubbles expands. [CHAR LIMIT=NONE]--> <string name="bubble_accessibility_announce_expand">expand <xliff:g id="bubble_title" example="Messages">%1$s</xliff:g></string> <!-- Accessibility announcement when the stack of bubbles collapses. [CHAR LIMIT=NONE]--> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java index 8d30db64a3e5..53551387230c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java @@ -18,6 +18,7 @@ package com.android.wm.shell.activityembedding; import static android.graphics.Matrix.MTRANS_X; import static android.graphics.Matrix.MTRANS_Y; +import static android.window.TransitionInfo.FLAG_TRANSLUCENT; import android.annotation.CallSuper; import android.graphics.Point; @@ -146,6 +147,14 @@ class ActivityEmbeddingAnimationAdapter { /** To be overridden by subclasses to adjust the animation surface change. */ void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) { // Update the surface position and alpha. + if (com.android.graphics.libgui.flags.Flags.edgeExtensionShader() + && mAnimation.getExtensionEdges() != 0x0 + && !(mChange.hasFlags(FLAG_TRANSLUCENT) + && mChange.getActivityComponent() != null)) { + // Extend non-translucent activities + t.setEdgeExtensionEffect(mLeash, mAnimation.getExtensionEdges()); + } + mTransformation.getMatrix().postTranslate(mContentRelOffset.x, mContentRelOffset.y); t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix); t.setAlpha(mLeash, mTransformation.getAlpha()); @@ -165,7 +174,7 @@ class ActivityEmbeddingAnimationAdapter { if (!cropRect.intersect(mWholeAnimationBounds)) { // Hide the surface when it is outside of the animation area. t.setAlpha(mLeash, 0); - } else if (mAnimation.hasExtension()) { + } else if (mAnimation.getExtensionEdges() != 0) { // Allow the surface to be shown in its original bounds in case we want to use edge // extensions. cropRect.union(mContentBounds); @@ -180,6 +189,10 @@ class ActivityEmbeddingAnimationAdapter { @CallSuper void onAnimationEnd(@NonNull SurfaceControl.Transaction t) { onAnimationUpdate(t, mAnimation.getDuration()); + if (com.android.graphics.libgui.flags.Flags.edgeExtensionShader() + && mAnimation.getExtensionEdges() != 0x0) { + t.setEdgeExtensionEffect(mLeash, /* edge */ 0); + } } final long getDurationHint() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java index 5696a544152c..d2cef4baf798 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java @@ -144,8 +144,10 @@ class ActivityEmbeddingAnimationRunner { // ending states. prepareForJumpCut(info, startTransaction); } else { - addEdgeExtensionIfNeeded(startTransaction, finishTransaction, - postStartTransactionCallbacks, adapters); + if (!com.android.graphics.libgui.flags.Flags.edgeExtensionShader()) { + addEdgeExtensionIfNeeded(startTransaction, finishTransaction, + postStartTransactionCallbacks, adapters); + } addBackgroundColorIfNeeded(info, startTransaction, finishTransaction, adapters); for (ActivityEmbeddingAnimationAdapter adapter : adapters) { duration = Math.max(duration, adapter.getDurationHint()); @@ -341,7 +343,7 @@ class ActivityEmbeddingAnimationRunner { @NonNull List<ActivityEmbeddingAnimationAdapter> adapters) { for (ActivityEmbeddingAnimationAdapter adapter : adapters) { final Animation animation = adapter.mAnimation; - if (!animation.hasExtension()) { + if (animation.getExtensionEdges() == 0) { continue; } if (adapter.mChange.hasFlags(FLAG_TRANSLUCENT) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java index 7275c6494140..12422874ca5d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java @@ -95,6 +95,7 @@ import com.android.wm.shell.transition.Transitions; import java.io.PrintWriter; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Predicate; /** * Controls the window animation run when a user initiates a back gesture. @@ -1209,7 +1210,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont } if (info.getType() != WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION - && !isGestureBackTransition(info)) { + && isNotGestureBackTransition(info)) { return false; } @@ -1364,7 +1365,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont // try to handle unexpected transition mergePendingTransitions(info); - if (!isGestureBackTransition(info) || shouldCancelAnimation(info) + if (isNotGestureBackTransition(info) || shouldCancelAnimation(info) || !mCloseTransitionRequested) { if (mPrepareOpenTransition != null) { applyFinishOpenTransition(); @@ -1395,8 +1396,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont // Cancel close animation if something happen unexpected, let another handler to handle private boolean shouldCancelAnimation(@NonNull TransitionInfo info) { - final boolean noCloseAllowed = - info.getType() == WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION; + final boolean noCloseAllowed = !mCloseTransitionRequested + && info.getType() == WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION; boolean unableToHandle = false; boolean filterTargets = false; for (int i = info.getChanges().size() - 1; i >= 0; --i) { @@ -1455,7 +1456,11 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont if (info.getType() != WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION) { return false; } - + // Must have open target, must not have close target. + if (hasAnimationInMode(info, TransitionUtil::isClosingMode) + || !hasAnimationInMode(info, TransitionUtil::isOpeningMode)) { + return false; + } SurfaceControl openingLeash = null; if (mApps != null) { for (int i = mApps.length - 1; i >= 0; --i) { @@ -1482,17 +1487,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont return true; } - private boolean isGestureBackTransition(@NonNull TransitionInfo info) { - for (int i = info.getChanges().size() - 1; i >= 0; --i) { - final TransitionInfo.Change c = info.getChanges().get(i); - if (c.hasFlags(FLAG_BACK_GESTURE_ANIMATED) - && (TransitionUtil.isOpeningMode(c.getMode()) - || TransitionUtil.isClosingMode(c.getMode()))) { - return true; - } - } - return false; - } /** * Check whether this transition is triggered from back gesture commitment. * Reparent the transition targets to animation leashes, so the animation won't be broken. @@ -1501,10 +1495,18 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont @NonNull SurfaceControl.Transaction st, @NonNull SurfaceControl.Transaction ft, @NonNull Transitions.TransitionFinishCallback finishCallback) { - if (info.getType() == WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION - || !mCloseTransitionRequested) { + if (!mCloseTransitionRequested) { return false; } + // must have close target + if (!hasAnimationInMode(info, TransitionUtil::isClosingMode)) { + return false; + } + if (mApps == null) { + // animation is done + applyAndFinish(st, ft, finishCallback); + return true; + } SurfaceControl openingLeash = null; SurfaceControl closingLeash = null; for (int i = mApps.length - 1; i >= 0; --i) { @@ -1522,6 +1524,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont final Point offset = c.getEndRelOffset(); st.setPosition(c.getLeash(), offset.x, offset.y); st.reparent(c.getLeash(), openingLeash); + st.setAlpha(c.getLeash(), 1.0f); } else if (TransitionUtil.isClosingMode(c.getMode())) { st.reparent(c.getLeash(), closingLeash); } @@ -1592,6 +1595,21 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont } } + private static boolean isNotGestureBackTransition(@NonNull TransitionInfo info) { + return !hasAnimationInMode(info, TransitionUtil::isOpenOrCloseMode); + } + + private static boolean hasAnimationInMode(@NonNull TransitionInfo info, + Predicate<Integer> mode) { + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change c = info.getChanges().get(i); + if (c.hasFlags(FLAG_BACK_GESTURE_ANIMATED) && mode.test(c.getMode())) { + return true; + } + } + return false; + } + private static ComponentName findComponentName(TransitionInfo.Change change) { final ComponentName componentName = change.getActivityComponent(); if (componentName != null) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java index f9a1d940c734..dc511be59764 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java @@ -357,7 +357,9 @@ public class BadgedImageView extends ConstraintLayout { void showBadge() { Bitmap appBadgeBitmap = mBubble.getAppBadge(); - if (appBadgeBitmap == null) { + final boolean isAppLaunchIntent = (mBubble instanceof Bubble) + && ((Bubble) mBubble).isAppLaunchIntent(); + if (appBadgeBitmap == null || isAppLaunchIntent) { mAppIcon.setVisibility(GONE); return; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java index 7dbbb04e4406..5cd2cb7d51d5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java @@ -50,6 +50,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.InstanceId; import com.android.internal.protolog.ProtoLog; import com.android.launcher3.icons.BubbleIconFactory; +import com.android.wm.shell.Flags; import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView; import com.android.wm.shell.bubbles.bar.BubbleBarLayerView; import com.android.wm.shell.common.bubbles.BubbleInfo; @@ -246,7 +247,23 @@ public class Bubble implements BubbleViewProvider { mAppIntent = intent; mDesiredHeight = Integer.MAX_VALUE; mPackageName = intent.getPackage(); + } + private Bubble(ShortcutInfo info, Executor mainExecutor) { + mGroupKey = null; + mLocusId = null; + mFlags = 0; + mUser = info.getUserHandle(); + mIcon = info.getIcon(); + mIsAppBubble = false; + mKey = getBubbleKeyForShortcut(info); + mShowBubbleUpdateDot = false; + mMainExecutor = mainExecutor; + mTaskId = INVALID_TASK_ID; + mAppIntent = null; + mDesiredHeight = Integer.MAX_VALUE; + mPackageName = info.getPackage(); + mShortcutInfo = info; } /** Creates an app bubble. */ @@ -263,6 +280,13 @@ public class Bubble implements BubbleViewProvider { mainExecutor); } + /** Creates a shortcut bubble. */ + public static Bubble createShortcutBubble( + ShortcutInfo info, + Executor mainExecutor) { + return new Bubble(info, mainExecutor); + } + /** * Returns the key for an app bubble from an app with package name, {@code packageName} on an * Android user, {@code user}. @@ -273,6 +297,14 @@ public class Bubble implements BubbleViewProvider { return KEY_APP_BUBBLE + ":" + user.getIdentifier() + ":" + packageName; } + /** + * Returns the key for a shortcut bubble using {@code packageName}, {@code user}, and the + * {@code shortcutInfo} id. + */ + public static String getBubbleKeyForShortcut(ShortcutInfo info) { + return info.getPackage() + ":" + info.getUserId() + ":" + info.getId(); + } + @VisibleForTesting(visibility = PRIVATE) public Bubble(@NonNull final BubbleEntry entry, final Bubbles.BubbleMetadataFlagListener listener, @@ -888,6 +920,17 @@ public class Bubble implements BubbleViewProvider { return mIntent; } + /** + * Whether this bubble represents the full app, i.e. the intent used is the launch + * intent for an app. In this case we don't show a badge on the icon. + */ + public boolean isAppLaunchIntent() { + if (Flags.enableBubbleAnything() && mAppIntent != null) { + return mAppIntent.hasCategory("android.intent.category.LAUNCHER"); + } + return false; + } + @Nullable PendingIntent getDeleteIntent() { return mDeleteIntent; 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 949a7236434a..29520efd70b0 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 @@ -1335,6 +1335,40 @@ public class BubbleController implements ConfigurationChangeListener, } /** + * Expands and selects a bubble created or found via the provided shortcut info. + * + * @param info the shortcut info for the bubble. + */ + public void expandStackAndSelectBubble(ShortcutInfo info) { + if (!Flags.enableBubbleAnything()) return; + Bubble b = mBubbleData.getOrCreateBubble(info); // Removes from overflow + ProtoLog.v(WM_SHELL_BUBBLES, "expandStackAndSelectBubble - shortcut=%s", info); + if (b.isInflated()) { + mBubbleData.setSelectedBubbleAndExpandStack(b); + } else { + b.enable(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE); + inflateAndAdd(b, /* suppressFlyout= */ true, /* showInShade= */ false); + } + } + + /** + * Expands and selects a bubble created or found for this app. + * + * @param intent the intent for the bubble. + */ + public void expandStackAndSelectBubble(Intent intent) { + if (!Flags.enableBubbleAnything()) return; + Bubble b = mBubbleData.getOrCreateBubble(intent); // Removes from overflow + ProtoLog.v(WM_SHELL_BUBBLES, "expandStackAndSelectBubble - intent=%s", intent); + if (b.isInflated()) { + mBubbleData.setSelectedBubbleAndExpandStack(b); + } else { + b.enable(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE); + inflateAndAdd(b, /* suppressFlyout= */ true, /* showInShade= */ false); + } + } + + /** * Expands and selects a bubble based on the provided {@link BubbleEntry}. If no bubble * exists for this entry, and it is able to bubble, a new bubble will be created. * @@ -2323,6 +2357,7 @@ public class BubbleController implements ConfigurationChangeListener, * @param entry the entry to bubble. */ static boolean canLaunchInTaskView(Context context, BubbleEntry entry) { + if (Flags.enableBubbleAnything()) return true; PendingIntent intent = entry.getBubbleMetadata() != null ? entry.getBubbleMetadata().getIntent() : null; @@ -2439,6 +2474,16 @@ public class BubbleController implements ConfigurationChangeListener, } @Override + public void showShortcutBubble(ShortcutInfo info) { + mMainExecutor.execute(() -> mController.expandStackAndSelectBubble(info)); + } + + @Override + public void showAppBubble(Intent intent) { + mMainExecutor.execute(() -> mController.expandStackAndSelectBubble(intent)); + } + + @Override public void showBubble(String key, int topOnScreen) { mMainExecutor.execute( () -> mController.expandStackAndSelectBubbleFromLauncher(key, topOnScreen)); @@ -2634,6 +2679,13 @@ public class BubbleController implements ConfigurationChangeListener, } @Override + public void expandStackAndSelectBubble(ShortcutInfo info) { + mMainExecutor.execute(() -> { + BubbleController.this.expandStackAndSelectBubble(info); + }); + } + + @Override public void expandStackAndSelectBubble(Bubble bubble) { mMainExecutor.execute(() -> { BubbleController.this.expandStackAndSelectBubble(bubble); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java index b6da761b0f9c..3c6c6fa0d8d5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java @@ -23,8 +23,10 @@ import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES; import android.annotation.NonNull; import android.app.PendingIntent; import android.content.Context; +import android.content.Intent; import android.content.LocusId; import android.content.pm.ShortcutInfo; +import android.os.UserHandle; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; @@ -421,23 +423,19 @@ public class BubbleData { Bubble bubbleToReturn = getBubbleInStackWithKey(key); if (bubbleToReturn == null) { - bubbleToReturn = getOverflowBubbleWithKey(key); - if (bubbleToReturn != null) { - // Promoting from overflow - mOverflowBubbles.remove(bubbleToReturn); - if (mOverflowBubbles.isEmpty()) { - mStateChange.showOverflowChanged = true; + // Check if it's in the overflow + bubbleToReturn = findAndRemoveBubbleFromOverflow(key); + if (bubbleToReturn == null) { + if (entry != null) { + // Not in the overflow, have an entry, so it's a new bubble + bubbleToReturn = new Bubble(entry, + mBubbleMetadataFlagListener, + mCancelledListener, + mMainExecutor); + } else { + // If there's no entry it must be a persisted bubble + bubbleToReturn = persistedBubble; } - } else if (mPendingBubbles.containsKey(key)) { - // Update while it was pending - bubbleToReturn = mPendingBubbles.get(key); - } else if (entry != null) { - // New bubble - bubbleToReturn = new Bubble(entry, mBubbleMetadataFlagListener, mCancelledListener, - mMainExecutor); - } else { - // Persisted bubble being promoted - bubbleToReturn = persistedBubble; } } @@ -448,6 +446,46 @@ public class BubbleData { return bubbleToReturn; } + Bubble getOrCreateBubble(ShortcutInfo info) { + String bubbleKey = Bubble.getBubbleKeyForShortcut(info); + Bubble bubbleToReturn = findAndRemoveBubbleFromOverflow(bubbleKey); + if (bubbleToReturn == null) { + bubbleToReturn = Bubble.createShortcutBubble(info, mMainExecutor); + } + return bubbleToReturn; + } + + Bubble getOrCreateBubble(Intent intent) { + UserHandle user = UserHandle.of(mCurrentUserId); + String bubbleKey = Bubble.getAppBubbleKeyForApp(intent.getPackage(), + user); + Bubble bubbleToReturn = findAndRemoveBubbleFromOverflow(bubbleKey); + if (bubbleToReturn == null) { + bubbleToReturn = Bubble.createAppBubble(intent, user, null, mMainExecutor); + } + return bubbleToReturn; + } + + @Nullable + private Bubble findAndRemoveBubbleFromOverflow(String key) { + Bubble bubbleToReturn = getBubbleInStackWithKey(key); + if (bubbleToReturn != null) { + return bubbleToReturn; + } + bubbleToReturn = getOverflowBubbleWithKey(key); + if (bubbleToReturn != null) { + mOverflowBubbles.remove(bubbleToReturn); + // Promoting from overflow + mOverflowBubbles.remove(bubbleToReturn); + if (mOverflowBubbles.isEmpty()) { + mStateChange.showOverflowChanged = true; + } + } else if (mPendingBubbles.containsKey(key)) { + bubbleToReturn = mPendingBubbles.get(key); + } + return bubbleToReturn; + } + /** * When this method is called it is expected that all info in the bubble has completed loading. * @see Bubble#inflate(BubbleViewInfoTask.Callback, Context, BubbleExpandedViewManager, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java index fdb45239fa63..e16db226e3ba 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java @@ -232,6 +232,9 @@ public class BubbleExpandedView extends LinearLayout { fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT); fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); + final boolean isShortcutBubble = (mBubble.hasMetadataShortcutId() + || (mBubble.getShortcutInfo() != null && Flags.enableBubbleAnything())); + if (mBubble.isAppBubble()) { Context context = mContext.createContextAsUser( @@ -246,7 +249,8 @@ public class BubbleExpandedView extends LinearLayout { /* options= */ null); mTaskView.startActivity(pi, /* fillInIntent= */ null, options, launchBounds); - } else if (!mIsOverflow && mBubble.hasMetadataShortcutId()) { + } else if (!mIsOverflow && isShortcutBubble) { + ProtoLog.v(WM_SHELL_BUBBLES, "startingShortcutBubble=%s", getBubbleKey()); options.setApplyActivityFlagsForBubbles(true); mTaskView.startShortcutActivity(mBubble.getShortcutInfo(), options, launchBounds); @@ -1148,5 +1152,7 @@ public class BubbleExpandedView extends LinearLayout { pw.print(prefix); pw.println("BubbleExpandedView:"); pw.print(prefix); pw.print(" taskId: "); pw.println(mTaskId); pw.print(prefix); pw.print(" stackView: "); pw.println(mStackView); + pw.print(prefix); pw.print(" contentVisibility: "); pw.println(mIsContentVisible); + pw.print(prefix); pw.print(" isAnimating: "); pw.println(mIsAnimating); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt index 3d9bf032c1b0..4e80e903b522 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt @@ -16,6 +16,8 @@ package com.android.wm.shell.bubbles +import com.android.wm.shell.common.bubbles.BubbleBarLocation + /** Manager interface for bubble expanded views. */ interface BubbleExpandedViewManager { @@ -30,6 +32,7 @@ interface BubbleExpandedViewManager { fun isStackExpanded(): Boolean fun isShowingAsBubbleBar(): Boolean fun hideCurrentInputMethod() + fun updateBubbleBarLocation(location: BubbleBarLocation) companion object { /** @@ -78,6 +81,10 @@ interface BubbleExpandedViewManager { override fun hideCurrentInputMethod() { controller.hideCurrentInputMethod() } + + override fun updateBubbleBarLocation(location: BubbleBarLocation) { + controller.bubbleBarLocation = location + } } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index efa1031bf814..1016133db770 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -339,6 +339,7 @@ public class BubbleStackView extends FrameLayout pw.println(mExpandedViewContainer.getAnimationMatrix()); pw.print(" stack visibility : "); pw.println(getVisibility()); pw.print(" temporarilyInvisible: "); pw.println(mTemporarilyInvisible); + pw.print(" expandedViewTemporarilyHidden: "); pw.println(mExpandedViewTemporarilyHidden); mStackAnimationController.dump(pw); mExpandedAnimationController.dump(pw); @@ -1601,6 +1602,11 @@ public class BubbleStackView extends FrameLayout getResources().getColor(android.R.color.system_neutral1_1000))); mManageMenuScrim.setBackgroundDrawable(new ColorDrawable( getResources().getColor(android.R.color.system_neutral1_1000))); + if (mShowingManage) { + // the manage menu location depends on the manage button location which may need a + // layout pass, so post this to the looper + post(() -> showManageMenu(true)); + } } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java index 5e2141aa639e..5f8f0fd0c54c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java @@ -36,6 +36,7 @@ import android.view.ViewGroup; import androidx.annotation.Nullable; import com.android.internal.protolog.ProtoLog; +import com.android.wm.shell.Flags; import com.android.wm.shell.taskview.TaskView; /** @@ -110,6 +111,8 @@ public class BubbleTaskViewHelper { fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT); fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); + final boolean isShortcutBubble = (mBubble.hasMetadataShortcutId() + || (mBubble.getShortcutInfo() != null && Flags.enableBubbleAnything())); if (mBubble.isAppBubble()) { Context context = mContext.createContextAsUser( @@ -124,7 +127,7 @@ public class BubbleTaskViewHelper { /* options= */ null); mTaskView.startActivity(pi, /* fillInIntent= */ null, options, launchBounds); - } else if (mBubble.hasMetadataShortcutId()) { + } else if (isShortcutBubble) { options.setApplyActivityFlagsForBubbles(true); mTaskView.startShortcutActivity(mBubble.getShortcutInfo(), options, launchBounds); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java index 589dfd24624e..9a27fb65ac2c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java @@ -23,6 +23,7 @@ import static java.lang.annotation.RetentionPolicy.SOURCE; import android.app.NotificationChannel; import android.content.Intent; +import android.content.pm.ShortcutInfo; import android.content.pm.UserInfo; import android.graphics.drawable.Icon; import android.hardware.HardwareBuffer; @@ -118,6 +119,14 @@ public interface Bubbles { /** * Request the stack expand if needed, then select the specified Bubble as current. + * If no bubble exists for this entry, one is created. + * + * @param info the shortcut info to use to create the bubble. + */ + void expandStackAndSelectBubble(ShortcutInfo info); + + /** + * Request the stack expand if needed, then select the specified Bubble as current. * * @param bubble the bubble to be selected */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl index 0907ddd1de83..5c789749412c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl @@ -18,6 +18,7 @@ package com.android.wm.shell.bubbles; import android.content.Intent; import android.graphics.Rect; +import android.content.pm.ShortcutInfo; import com.android.wm.shell.bubbles.IBubblesListener; import com.android.wm.shell.common.bubbles.BubbleBarLocation; @@ -48,4 +49,8 @@ interface IBubbles { oneway void updateBubbleBarTopOnScreen(in int topOnScreen) = 10; oneway void stopBubbleDrag(in BubbleBarLocation location, in int topOnScreen) = 11; + + oneway void showShortcutBubble(in ShortcutInfo info) = 12; + + oneway void showAppBubble(in Intent intent) = 13; }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java index 24c568c23bf2..6d868d215482 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java @@ -26,14 +26,18 @@ import android.graphics.Color; import android.graphics.Insets; import android.graphics.Outline; import android.graphics.Rect; +import android.os.Bundle; import android.util.AttributeSet; import android.util.FloatProperty; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.ViewOutlineProvider; +import android.view.accessibility.AccessibilityNodeInfo; import android.widget.FrameLayout; +import androidx.annotation.NonNull; + import com.android.wm.shell.R; import com.android.wm.shell.bubbles.Bubble; import com.android.wm.shell.bubbles.BubbleExpandedViewManager; @@ -42,6 +46,7 @@ import com.android.wm.shell.bubbles.BubblePositioner; import com.android.wm.shell.bubbles.BubbleTaskView; import com.android.wm.shell.bubbles.BubbleTaskViewHelper; import com.android.wm.shell.bubbles.Bubbles; +import com.android.wm.shell.common.bubbles.BubbleBarLocation; import com.android.wm.shell.taskview.TaskView; import java.util.function.Supplier; @@ -81,6 +86,7 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView private static final String TAG = BubbleBarExpandedView.class.getSimpleName(); private static final int INVALID_TASK_ID = -1; + private Bubble mBubble; private BubbleExpandedViewManager mManager; private BubblePositioner mPositioner; private boolean mIsOverflow; @@ -188,12 +194,21 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView // Handle view needs to draw on top of task view. bringChildToFront(mHandleView); + + mHandleView.setAccessibilityDelegate(new HandleViewAccessibilityDelegate()); } mMenuViewController = new BubbleBarMenuViewController(mContext, this); mMenuViewController.setListener(new BubbleBarMenuViewController.Listener() { @Override public void onMenuVisibilityChanged(boolean visible) { setObscured(visible); + if (visible) { + mHandleView.setFocusable(false); + mHandleView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); + } else { + mHandleView.setFocusable(true); + mHandleView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_AUTO); + } } @Override @@ -319,6 +334,7 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView /** Updates the bubble shown in the expanded view. */ public void update(Bubble bubble) { + mBubble = bubble; mBubbleTaskViewHelper.update(bubble); mMenuViewController.updateMenu(bubble); } @@ -457,4 +473,51 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView invalidateOutline(); } } + + private class HandleViewAccessibilityDelegate extends AccessibilityDelegate { + @Override + public void onInitializeAccessibilityNodeInfo(@NonNull View host, + @NonNull AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(host, info); + info.addAction(new AccessibilityNodeInfo.AccessibilityAction( + AccessibilityNodeInfo.ACTION_CLICK, getResources().getString( + R.string.bubble_accessibility_action_expand_menu))); + info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_COLLAPSE); + info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_DISMISS); + if (mPositioner.isBubbleBarOnLeft()) { + info.addAction(new AccessibilityNodeInfo.AccessibilityAction( + R.id.action_move_bubble_bar_right, getResources().getString( + R.string.bubble_accessibility_action_move_bar_right))); + } else { + info.addAction(new AccessibilityNodeInfo.AccessibilityAction( + R.id.action_move_bubble_bar_left, getResources().getString( + R.string.bubble_accessibility_action_move_bar_left))); + } + } + + @Override + public boolean performAccessibilityAction(@NonNull View host, int action, + @Nullable Bundle args) { + if (super.performAccessibilityAction(host, action, args)) { + return true; + } + if (action == AccessibilityNodeInfo.ACTION_COLLAPSE) { + mManager.collapseStack(); + return true; + } + if (action == AccessibilityNodeInfo.ACTION_DISMISS) { + mManager.dismissBubble(mBubble, Bubbles.DISMISS_USER_GESTURE); + return true; + } + if (action == R.id.action_move_bubble_bar_left) { + mManager.updateBubbleBarLocation(BubbleBarLocation.LEFT); + return true; + } + if (action == R.id.action_move_bubble_bar_right) { + mManager.updateBubbleBarLocation(BubbleBarLocation.RIGHT); + return true; + } + return false; + } + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java index 8389c819f8ea..0300869cbbe1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java @@ -23,7 +23,9 @@ import android.graphics.Color; import android.graphics.drawable.Icon; import android.util.AttributeSet; import android.view.LayoutInflater; +import android.view.View; import android.view.ViewGroup; +import android.view.accessibility.AccessibilityNodeInfo; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; @@ -71,6 +73,16 @@ public class BubbleBarMenuView extends LinearLayout { mBubbleTitleView = findViewById(R.id.bubble_bar_manage_menu_bubble_title); mBubbleDismissIconView = findViewById(R.id.bubble_bar_manage_menu_dismiss_icon); updateThemeColors(); + + mBubbleSectionView.setAccessibilityDelegate(new AccessibilityDelegate() { + @Override + public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(host, info); + info.addAction(new AccessibilityNodeInfo.AccessibilityAction( + AccessibilityNodeInfo.ACTION_CLICK, getResources().getString( + R.string.bubble_accessibility_action_collapse_menu))); + } + }); } private void updateThemeColors() { 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 b8b62a76c568..955361ffac1b 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 @@ -72,6 +72,7 @@ import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler; import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator; import com.android.wm.shell.desktopmode.SpringDragToDesktopTransitionHandler; import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler; +import com.android.wm.shell.desktopmode.education.data.AppHandleEducationDatastoreRepository; import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.draganddrop.GlobalDragListener; import com.android.wm.shell.freeform.FreeformComponents; @@ -319,9 +320,19 @@ public abstract class WMShellModule { WindowDecorViewModel windowDecorViewModel, DisplayController displayController, @ShellMainThread ShellExecutor mainExecutor, - @ShellAnimationThread ShellExecutor animExecutor) { - return new FreeformTaskTransitionHandler(shellInit, transitions, context, - windowDecorViewModel, displayController, mainExecutor, animExecutor); + @ShellAnimationThread ShellExecutor animExecutor, + @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository, + InteractionJankMonitor interactionJankMonitor) { + return new FreeformTaskTransitionHandler( + shellInit, + transitions, + context, + windowDecorViewModel, + displayController, + mainExecutor, + animExecutor, + desktopModeTaskRepository, + interactionJankMonitor); } @WMSingleton @@ -679,6 +690,13 @@ public abstract class WMShellModule { return new DesktopModeEventLogger(); } + @WMSingleton + @Provides + static AppHandleEducationDatastoreRepository provideAppHandleEducationDatastoreRepository( + Context context) { + return new AppHandleEducationDatastoreRepository(context); + } + // // Drag and drop // diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt index 400882a8da9a..05c9d02a0de7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt @@ -73,25 +73,9 @@ class DesktopModeEventLogger { sessionId, taskUpdate.instanceId ) - FrameworkStatsLog.write( - DESKTOP_MODE_TASK_UPDATE_ATOM_ID, - /* task_event */ + logTaskUpdate( FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_ADDED, - /* instance_id */ - taskUpdate.instanceId, - /* uid */ - taskUpdate.uid, - /* task_height */ - taskUpdate.taskHeight, - /* task_width */ - taskUpdate.taskWidth, - /* task_x */ - taskUpdate.taskX, - /* task_y */ - taskUpdate.taskY, - /* session_id */ - sessionId - ) + sessionId, taskUpdate) } /** @@ -105,25 +89,9 @@ class DesktopModeEventLogger { sessionId, taskUpdate.instanceId ) - FrameworkStatsLog.write( - DESKTOP_MODE_TASK_UPDATE_ATOM_ID, - /* task_event */ + logTaskUpdate( FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_REMOVED, - /* instance_id */ - taskUpdate.instanceId, - /* uid */ - taskUpdate.uid, - /* task_height */ - taskUpdate.taskHeight, - /* task_width */ - taskUpdate.taskWidth, - /* task_x */ - taskUpdate.taskX, - /* task_y */ - taskUpdate.taskY, - /* session_id */ - sessionId - ) + sessionId, taskUpdate) } /** @@ -137,10 +105,16 @@ class DesktopModeEventLogger { sessionId, taskUpdate.instanceId ) + logTaskUpdate( + FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED, + sessionId, taskUpdate) + } + + private fun logTaskUpdate(taskEvent: Int, sessionId: Int, taskUpdate: TaskUpdate) { FrameworkStatsLog.write( DESKTOP_MODE_TASK_UPDATE_ATOM_ID, /* task_event */ - FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED, + taskEvent, /* instance_id */ taskUpdate.instanceId, /* uid */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt index 73aa7ceea68d..a6ed3b8cb50c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt @@ -22,6 +22,7 @@ import android.app.TaskInfo import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM import android.content.Context import android.os.IBinder +import android.os.Trace import android.util.SparseArray import android.view.SurfaceControl import android.view.WindowManager @@ -51,6 +52,8 @@ import com.android.wm.shell.shared.TransitionUtil import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.Transitions +const val VISIBLE_TASKS_COUNTER_NAME = "DESKTOP_MODE_VISIBLE_TASKS" + /** * A [Transitions.TransitionObserver] that observes transitions and the proposed changes to log * appropriate desktop mode session log events. This observes transitions related to desktop mode @@ -292,8 +295,14 @@ class DesktopModeLoggerTransitionObserver( val previousTaskInfo = preTransitionVisibleFreeformTasks[taskId] when { // new tasks added - previousTaskInfo == null -> + previousTaskInfo == null -> { desktopModeEventLogger.logTaskAdded(sessionId, currentTaskUpdate) + Trace.setCounter( + Trace.TRACE_TAG_WINDOW_MANAGER, + VISIBLE_TASKS_COUNTER_NAME, + postTransitionVisibleFreeformTasks.size().toLong() + ) + } // old tasks that were resized or repositioned // TODO(b/347935387): Log changes only once they are stable. buildTaskUpdateForTask(previousTaskInfo) != currentTaskUpdate -> @@ -305,6 +314,11 @@ class DesktopModeLoggerTransitionObserver( preTransitionVisibleFreeformTasks.forEach { taskId, taskInfo -> if (!postTransitionVisibleFreeformTasks.containsKey(taskId)) { desktopModeEventLogger.logTaskRemoved(sessionId, buildTaskUpdateForTask(taskInfo)) + Trace.setCounter( + Trace.TRACE_TAG_WINDOW_MANAGER, + VISIBLE_TASKS_COUNTER_NAME, + postTransitionVisibleFreeformTasks.size().toLong() + ) } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt index 83752945e9f9..9d041692200d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt @@ -194,6 +194,11 @@ class DesktopModeTaskRepository { fun getActiveNonMinimizedOrderedTasks(displayId: Int): List<Int> = getFreeformTasksInZOrder(displayId).filter { !isMinimizedTask(it) } + /** Returns the count of active non-minimized tasks for [displayId]. */ + fun getActiveNonMinimizedTaskCount(displayId: Int): Int { + return getActiveTasks(displayId).count { !isMinimizedTask(it) } + } + /** Returns a list of freeform tasks, ordered from top-bottom (top at index 0). */ fun getFreeformTasksInZOrder(displayId: Int): ArrayList<Int> = ArrayList(desktopTaskDataByDisplayId[displayId]?.freeformTasksInZOrder ?: emptyList()) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt index 38675129ce57..597637d3fbfc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt @@ -70,11 +70,11 @@ class DesktopTasksLimiter ( // TODO(b/333018485): replace this observer when implementing the minimize-animation private inner class MinimizeTransitionObserver : TransitionObserver { - private val mPendingTransitionTokensAndTasks = mutableMapOf<IBinder, TaskDetails>() - private val mActiveTransitionTokensAndTasks = mutableMapOf<IBinder, TaskDetails>() + private val pendingTransitionTokensAndTasks = mutableMapOf<IBinder, TaskDetails>() + private val activeTransitionTokensAndTasks = mutableMapOf<IBinder, TaskDetails>() fun addPendingTransitionToken(transition: IBinder, taskDetails: TaskDetails) { - mPendingTransitionTokensAndTasks[transition] = taskDetails + pendingTransitionTokensAndTasks[transition] = taskDetails } override fun onTransitionReady( @@ -83,9 +83,7 @@ class DesktopTasksLimiter ( startTransaction: SurfaceControl.Transaction, finishTransaction: SurfaceControl.Transaction ) { - val taskToMinimize = mPendingTransitionTokensAndTasks.remove(transition) ?: return - taskToMinimize.transitionInfo = info - mActiveTransitionTokensAndTasks[transition] = taskToMinimize + val taskToMinimize = pendingTransitionTokensAndTasks.remove(transition) ?: return if (!taskRepository.isActiveTask(taskToMinimize.taskId)) return @@ -97,6 +95,8 @@ class DesktopTasksLimiter ( return } + taskToMinimize.transitionInfo = info + activeTransitionTokensAndTasks[transition] = taskToMinimize this@DesktopTasksLimiter.markTaskMinimized( taskToMinimize.displayId, taskToMinimize.taskId) } @@ -121,7 +121,7 @@ class DesktopTasksLimiter ( } override fun onTransitionStarting(transition: IBinder) { - val mActiveTaskDetails = mActiveTransitionTokensAndTasks[transition] + val mActiveTaskDetails = activeTransitionTokensAndTasks[transition] if (mActiveTaskDetails != null && mActiveTaskDetails.transitionInfo != null) { // Begin minimize window CUJ instrumentation. interactionJankMonitor.begin( @@ -132,11 +132,11 @@ class DesktopTasksLimiter ( } override fun onTransitionMerged(merged: IBinder, playing: IBinder) { - if (mActiveTransitionTokensAndTasks.remove(merged) != null) { + if (activeTransitionTokensAndTasks.remove(merged) != null) { interactionJankMonitor.end(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW) } - mPendingTransitionTokensAndTasks.remove(merged)?.let { taskToTransfer -> - mPendingTransitionTokensAndTasks[playing] = taskToTransfer + pendingTransitionTokensAndTasks.remove(merged)?.let { taskToTransfer -> + pendingTransitionTokensAndTasks[playing] = taskToTransfer } } @@ -144,14 +144,14 @@ class DesktopTasksLimiter ( ProtoLog.v( ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, "DesktopTasksLimiter: transition %s finished", transition) - if (mActiveTransitionTokensAndTasks.remove(transition) != null) { + if (activeTransitionTokensAndTasks.remove(transition) != null) { if (aborted) { interactionJankMonitor.cancel(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW) } else { interactionJankMonitor.end(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW) } } - mPendingTransitionTokensAndTasks.remove(transition) + pendingTransitionTokensAndTasks.remove(transition) } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppHandleEducationDatastoreRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppHandleEducationDatastoreRepository.kt new file mode 100644 index 000000000000..bf4a2abf9edc --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppHandleEducationDatastoreRepository.kt @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.desktopmode.education.data + +import android.content.Context +import android.util.Log +import androidx.datastore.core.CorruptionException +import androidx.datastore.core.DataStore +import androidx.datastore.core.DataStoreFactory +import androidx.datastore.core.Serializer +import androidx.datastore.dataStore +import androidx.datastore.dataStoreFile +import com.android.framework.protobuf.InvalidProtocolBufferException +import com.android.internal.annotations.VisibleForTesting +import java.io.InputStream +import java.io.OutputStream +import kotlinx.coroutines.flow.first + +/** + * Manages interactions with the App Handle education datastore. + * + * This class provides a layer of abstraction between the UI/business logic and the underlying + * DataStore. + */ +class AppHandleEducationDatastoreRepository +@VisibleForTesting +constructor(private val dataStore: DataStore<WindowingEducationProto>) { + constructor( + context: Context + ) : this( + DataStoreFactory.create( + serializer = WindowingEducationProtoSerializer, + produceFile = { context.dataStoreFile(APP_HANDLE_EDUCATION_DATASTORE_FILEPATH) })) + + /** + * Reads and returns the [WindowingEducationProto] Proto object from the DataStore. If the + * DataStore is empty or there's an error reading, it returns the default value of Proto. + */ + suspend fun windowingEducationProto(): WindowingEducationProto = + try { + dataStore.data.first() + } catch (e: Exception) { + Log.e(TAG, "Unable to read from datastore") + WindowingEducationProto.getDefaultInstance() + } + + companion object { + private const val TAG = "AppHandleEducationDatastoreRepository" + private const val APP_HANDLE_EDUCATION_DATASTORE_FILEPATH = "app_handle_education.pb" + + object WindowingEducationProtoSerializer : Serializer<WindowingEducationProto> { + + override val defaultValue: WindowingEducationProto = + WindowingEducationProto.getDefaultInstance() + + override suspend fun readFrom(input: InputStream): WindowingEducationProto = + try { + WindowingEducationProto.parseFrom(input) + } catch (exception: InvalidProtocolBufferException) { + throw CorruptionException("Cannot read proto.", exception) + } + + override suspend fun writeTo(windowingProto: WindowingEducationProto, output: OutputStream) = + windowingProto.writeTo(output) + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/proto/windowing_education.proto b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/proto/windowing_education.proto new file mode 100644 index 000000000000..d29ec53d9c61 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/proto/windowing_education.proto @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; + +option java_package = "com.android.wm.shell.desktopmode.education.data"; +option java_multiple_files = true; + +// Desktop Windowing education data +message WindowingEducationProto { + // Timestamp in milliseconds of when the education was last viewed. + optional int64 education_viewed_timestamp_millis = 1; + // Timestamp in milliseconds of when the feature was last used. + optional int64 feature_used_timestamp_millis = 2; + oneof education_data { + // Fields specific to app handle education + AppHandleEducation app_handle_education = 3; + } + + message AppHandleEducation { + // Map that stores app launch count for corresponding package + map<string, int32> app_usage_stats = 1; + // Timestamp of when app_usage_stats was last cached + optional int64 app_usage_stats_last_update_timestamp_millis = 2; + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java index 84027753435b..4284d06a293f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java @@ -18,6 +18,7 @@ package com.android.wm.shell.freeform; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -37,8 +38,10 @@ import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.internal.jank.InteractionJankMonitor; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.windowdecor.WindowDecorViewModel; @@ -56,7 +59,9 @@ public class FreeformTaskTransitionHandler private final Context mContext; private final Transitions mTransitions; private final WindowDecorViewModel mWindowDecorViewModel; + private final DesktopModeTaskRepository mDesktopModeTaskRepository; private final DisplayController mDisplayController; + private final InteractionJankMonitor mInteractionJankMonitor; private final ShellExecutor mMainExecutor; private final ShellExecutor mAnimExecutor; @@ -71,11 +76,15 @@ public class FreeformTaskTransitionHandler WindowDecorViewModel windowDecorViewModel, DisplayController displayController, ShellExecutor mainExecutor, - ShellExecutor animExecutor) { + ShellExecutor animExecutor, + DesktopModeTaskRepository desktopModeTaskRepository, + InteractionJankMonitor interactionJankMonitor) { mTransitions = transitions; mContext = context; mWindowDecorViewModel = windowDecorViewModel; + mDesktopModeTaskRepository = desktopModeTaskRepository; mDisplayController = displayController; + mInteractionJankMonitor = interactionJankMonitor; mMainExecutor = mainExecutor; mAnimExecutor = animExecutor; if (Transitions.ENABLE_SHELL_TRANSITIONS) { @@ -238,13 +247,22 @@ public class FreeformTaskTransitionHandler startBounds.top + (animation.getAnimatedFraction() * screenHeight)); t.apply(); }); - animator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - animations.remove(animator); - onAnimFinish.run(); - } - }); + if (mDesktopModeTaskRepository.getActiveNonMinimizedTaskCount( + change.getTaskInfo().displayId) == 1) { + // Starting the jank trace if closing the last window in desktop mode. + mInteractionJankMonitor.begin( + sc, mContext, CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE); + } + animator.addListener( + new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + animations.remove(animator); + onAnimFinish.run(); + mInteractionJankMonitor.end( + CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE); + } + }); animations.add(animator); return true; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS index 93351c3f5f86..83b5bf658459 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS @@ -9,3 +9,5 @@ vaniadesmonda@google.com pragyabajoria@google.com uysalorhan@google.com gsennton@google.com +mattsziklay@google.com +mdehaini@google.com diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index 428cc9118900..86777dfb3d6b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -496,7 +496,9 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "startSwipePipToHome: %s, state=%s", componentName, mPipTransitionState); mPipTransitionState.setInSwipePipToHomeTransition(true); - if (!ENABLE_SHELL_TRANSITIONS) { + if (ENABLE_SHELL_TRANSITIONS) { + mPipTransitionController.onStartSwipePipToHome(); + } else { sendOnPipTransitionStarted(TRANSITION_DIRECTION_TO_PIP); } setBoundsStateForEntry(componentName, pictureInPictureParams, activityInfo); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java index ba97c8322c92..37e2fd0a6867 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java @@ -191,6 +191,11 @@ public class PipTransition extends PipTransitionController { } @Override + protected boolean isInSwipePipToHomeTransition() { + return mPipTransitionState.getInSwipePipToHomeTransition(); + } + + @Override public void startExitTransition(int type, WindowContainerTransaction out, @Nullable Rect destinationBounds) { if (destinationBounds != null) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java index fc9e2becf98c..9b815817d4d3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java @@ -189,6 +189,32 @@ public abstract class PipTransitionController implements Transitions.TransitionH mPipTransitionCallbacks.put(callback, executor); } + protected void onStartSwipePipToHome() { + if (Flags.enablePipUiStateCallbackOnEntering()) { + try { + ActivityTaskManager.getService().onPictureInPictureUiStateChanged( + new PictureInPictureUiState.Builder() + .setTransitioningToPip(true) + .build()); + } catch (RemoteException | IllegalStateException e) { + ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "Failed to set alert PiP state change."); + } + } + } + + /** + * Used in {@link #sendOnPipTransitionStarted(int)} to decide whether we should send the + * PictureInPictureUiState change callback on transition start. + * For instance, in auto-enter-pip case, {@link #onStartSwipePipToHome()} should have signaled + * the app, and we can return {@code true} here to avoid double callback. + * + * @return {@code true} if there is a ongoing swipe pip to home transition. + */ + protected boolean isInSwipePipToHomeTransition() { + return false; + } + protected void sendOnPipTransitionStarted( @PipAnimationController.TransitionDirection int direction) { final Rect pipBounds = mPipBoundsState.getBounds(); @@ -199,7 +225,8 @@ public abstract class PipTransitionController implements Transitions.TransitionH entry.getValue().execute( () -> entry.getKey().onPipTransitionStarted(direction, pipBounds)); } - if (isInPipDirection(direction) && Flags.enablePipUiStateCallbackOnEntering()) { + if (isInPipDirection(direction) && Flags.enablePipUiStateCallbackOnEntering() + && !isInSwipePipToHomeTransition()) { try { ActivityTaskManager.getService().onPictureInPictureUiStateChanged( new PictureInPictureUiState.Builder() diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java index 77743844f3c3..dc21f82c326c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java @@ -31,6 +31,8 @@ import android.graphics.Rect; import android.os.Bundle; import android.view.InsetsState; import android.view.SurfaceControl; +import android.window.DisplayAreaInfo; +import android.window.WindowContainerTransaction; import androidx.annotation.BinderThread; import androidx.annotation.Nullable; @@ -40,6 +42,7 @@ import com.android.internal.protolog.ProtoLog; import com.android.internal.util.Preconditions; import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.common.DisplayChangeController; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.DisplayLayout; @@ -71,7 +74,8 @@ import java.util.function.Consumer; */ public class PipController implements ConfigurationChangeListener, PipTransitionState.PipTransitionStateChangedListener, - DisplayController.OnDisplaysChangedListener, RemoteCallable<PipController> { + DisplayController.OnDisplaysChangedListener, + DisplayChangeController.OnDisplayChangingListener, RemoteCallable<PipController> { private static final String TAG = PipController.class.getSimpleName(); private static final String SWIPE_TO_PIP_APP_BOUNDS = "pip_app_bounds"; private static final String SWIPE_TO_PIP_OVERLAY = "swipe_to_pip_overlay"; @@ -197,11 +201,12 @@ public class PipController implements ConfigurationChangeListener, mPipDisplayLayoutState.setDisplayLayout(layout); mDisplayController.addDisplayWindowListener(this); + mDisplayController.addDisplayChangingController(this); mDisplayInsetsController.addInsetsChangedListener(mPipDisplayLayoutState.getDisplayId(), new DisplayInsetsController.OnInsetsChangedListener() { @Override public void insetsChanged(InsetsState insetsState) { - onDisplayChanged(mDisplayController + setDisplayLayout(mDisplayController .getDisplayLayout(mPipDisplayLayoutState.getDisplayId())); } }); @@ -264,11 +269,12 @@ public class PipController implements ConfigurationChangeListener, @Override public void onThemeChanged() { - onDisplayChanged(new DisplayLayout(mContext, mContext.getDisplay())); + setDisplayLayout(new DisplayLayout(mContext, mContext.getDisplay())); } // - // DisplayController.OnDisplaysChangedListener implementations + // DisplayController.OnDisplaysChangedListener and + // DisplayChangeController.OnDisplayChangingListener implementations // @Override @@ -276,7 +282,7 @@ public class PipController implements ConfigurationChangeListener, if (displayId != mPipDisplayLayoutState.getDisplayId()) { return; } - onDisplayChanged(mDisplayController.getDisplayLayout(displayId)); + setDisplayLayout(mDisplayController.getDisplayLayout(displayId)); } @Override @@ -284,10 +290,35 @@ public class PipController implements ConfigurationChangeListener, if (displayId != mPipDisplayLayoutState.getDisplayId()) { return; } - onDisplayChanged(mDisplayController.getDisplayLayout(displayId)); + setDisplayLayout(mDisplayController.getDisplayLayout(displayId)); } - private void onDisplayChanged(DisplayLayout layout) { + /** + * A callback for any observed transition that contains a display change in its + * {@link android.window.TransitionRequestInfo} with a non-zero rotation delta. + */ + @Override + public void onDisplayChange(int displayId, int fromRotation, int toRotation, + @Nullable DisplayAreaInfo newDisplayAreaInfo, WindowContainerTransaction t) { + if (!mPipTransitionState.isInPip()) { + return; + } + + // Calculate the snap fraction pre-rotation. + float snapFraction = mPipBoundsAlgorithm.getSnapFraction(mPipBoundsState.getBounds()); + + // Update the caches to reflect the new display layout and movement bounds. + mPipDisplayLayoutState.rotateTo(toRotation); + mPipTouchHandler.updateMovementBounds(); + + // The policy is to keep PiP width, height and snap fraction invariant. + Rect toBounds = mPipBoundsState.getBounds(); + mPipBoundsAlgorithm.applySnapFraction(toBounds, snapFraction); + mPipBoundsState.setBounds(toBounds); + t.setBounds(mPipTransitionState.mPipTaskToken, toBounds); + } + + private void setDisplayLayout(DisplayLayout layout) { mPipDisplayLayoutState.setDisplayLayout(layout); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java index d7c225b9e6e1..d75fa00b1fdd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java @@ -1081,7 +1081,7 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha * Updates the current movement bounds based on whether the menu is currently visible and * resized. */ - private void updateMovementBounds() { + void updateMovementBounds() { Rect insetBounds = new Rect(); mPipBoundsAlgorithm.getInsetBounds(insetBounds); mPipBoundsAlgorithm.getMovementBounds(mPipBoundsState.getBounds(), diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java index 7790c51f3bd3..846fa6268fc3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java @@ -137,6 +137,11 @@ public class PipTransition extends PipTransitionController implements } } + @Override + protected boolean isInSwipePipToHomeTransition() { + return mPipTransitionState.isInSwipePipToHomeTransition(); + } + // // Transition collection stage lifecycle hooks // diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java index 48d17ec6963f..c1f603839ef6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java @@ -439,9 +439,9 @@ class SplitScreenTransitions { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "setResizeTransition: hasPendingResize=%b", mPendingResize != null); if (mPendingResize != null) { + mPendingResize.cancel(null); mainDecor.cancelRunningAnimations(); sideDecor.cancelRunningAnimations(); - mPendingResize.cancel(null); mAnimations.clear(); onFinish(null /* wct */); } @@ -504,7 +504,9 @@ class SplitScreenTransitions { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTransitionConsumed for passThrough transition"); } - // TODO: handle transition consumed for active remote handler + if (mActiveRemoteHandler != null) { + mActiveRemoteHandler.onTransitionConsumed(transition, aborted, finishT); + } } void onFinish(WindowContainerTransaction wct) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java index 778478405dda..de6887a2173b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java @@ -502,15 +502,19 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { backgroundColorForTransition = getTransitionBackgroundColorIfSet(info, change, a, backgroundColorForTransition); - if (!isTask && a.hasExtension()) { - if (!TransitionUtil.isOpeningType(mode)) { - // Can screenshot now (before startTransaction is applied) - edgeExtendWindow(change, a, startTransaction, finishTransaction); + if (!isTask && a.getExtensionEdges() != 0x0) { + if (com.android.graphics.libgui.flags.Flags.edgeExtensionShader()) { + finishTransaction.setEdgeExtensionEffect(change.getLeash(), /* edge */ 0); } else { - // Need to screenshot after startTransaction is applied otherwise activity - // may not be visible or ready yet. - postStartTransactionCallbacks - .add(t -> edgeExtendWindow(change, a, t, finishTransaction)); + if (!TransitionUtil.isOpeningType(mode)) { + // Can screenshot now (before startTransaction is applied) + edgeExtendWindow(change, a, startTransaction, finishTransaction); + } else { + // Need to screenshot after startTransaction is applied otherwise + // activity may not be visible or ready yet. + postStartTransactionCallbacks + .add(t -> edgeExtendWindow(change, a, t, finishTransaction)); + } } } @@ -558,7 +562,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { buildSurfaceAnimation(animations, a, change.getLeash(), onAnimFinish, mTransactionPool, mMainExecutor, animRelOffset, cornerRadius, - clipRect); + clipRect, change.getActivityComponent() != null); final TransitionInfo.AnimationOptions options; if (Flags.moveAnimationOptionsToChange()) { @@ -823,7 +827,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { @NonNull Animation anim, @NonNull SurfaceControl leash, @NonNull Runnable finishCallback, @NonNull TransactionPool pool, @NonNull ShellExecutor mainExecutor, @Nullable Point position, float cornerRadius, - @Nullable Rect clipRect) { + @Nullable Rect clipRect, boolean isActivity) { final SurfaceControl.Transaction transaction = pool.acquire(); final ValueAnimator va = ValueAnimator.ofFloat(0f, 1f); final Transformation transformation = new Transformation(); @@ -835,13 +839,13 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { final long currentPlayTime = Math.min(va.getDuration(), va.getCurrentPlayTime()); applyTransformation(currentPlayTime, transaction, leash, anim, transformation, matrix, - position, cornerRadius, clipRect); + position, cornerRadius, clipRect, isActivity); }; va.addUpdateListener(updateListener); final Runnable finisher = () -> { applyTransformation(va.getDuration(), transaction, leash, anim, transformation, matrix, - position, cornerRadius, clipRect); + position, cornerRadius, clipRect, isActivity); pool.release(transaction); mainExecutor.execute(() -> { @@ -931,7 +935,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { a.restrictDuration(MAX_ANIMATION_DURATION); a.scaleCurrentDuration(mTransitionAnimationScaleSetting); buildSurfaceAnimation(animations, a, wt.getSurface(), finisher, mTransactionPool, - mMainExecutor, change.getEndRelOffset(), cornerRadius, change.getEndAbsBounds()); + mMainExecutor, change.getEndRelOffset(), cornerRadius, change.getEndAbsBounds(), + change.getActivityComponent() != null); } private void attachThumbnailAnimation(@NonNull ArrayList<Animator> animations, @@ -955,7 +960,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { a.restrictDuration(MAX_ANIMATION_DURATION); a.scaleCurrentDuration(mTransitionAnimationScaleSetting); buildSurfaceAnimation(animations, a, wt.getSurface(), finisher, mTransactionPool, - mMainExecutor, change.getEndRelOffset(), cornerRadius, change.getEndAbsBounds()); + mMainExecutor, change.getEndRelOffset(), cornerRadius, change.getEndAbsBounds(), + change.getActivityComponent() != null); } private static int getWallpaperTransitType(TransitionInfo info) { @@ -1005,9 +1011,14 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { private static void applyTransformation(long time, SurfaceControl.Transaction t, SurfaceControl leash, Animation anim, Transformation tmpTransformation, float[] matrix, - Point position, float cornerRadius, @Nullable Rect immutableClipRect) { + Point position, float cornerRadius, @Nullable Rect immutableClipRect, + boolean isActivity) { tmpTransformation.clear(); anim.getTransformation(time, tmpTransformation); + if (com.android.graphics.libgui.flags.Flags.edgeExtensionShader() + && anim.getExtensionEdges() != 0x0 && isActivity) { + t.setEdgeExtensionEffect(leash, anim.getExtensionEdges()); + } if (position != null) { tmpTransformation.getMatrix().postTranslate(position.x, position.y); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java index c5dc668582bc..b7b42c723929 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java @@ -193,6 +193,8 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler { public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted, @Nullable SurfaceControl.Transaction finishTransaction) { try { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, + "OneShot onTransitionConsumed for %s", mRemote); mRemote.getRemoteTransition().onTransitionConsumed(transition, aborted); } catch (RemoteException e) { Log.e(Transitions.TAG, "Error calling onTransitionConsumed()", e); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java index e196254628d0..195882553602 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java @@ -325,21 +325,21 @@ class ScreenRotationAnimation { @NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor) { buildSurfaceAnimation(animations, mRotateEnterAnimation, mSurfaceControl, finishCallback, mTransactionPool, mainExecutor, null /* position */, 0 /* cornerRadius */, - null /* clipRect */); + null /* clipRect */, false /* isActivity */); } private void startScreenshotRotationAnimation(@NonNull ArrayList<Animator> animations, @NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor) { buildSurfaceAnimation(animations, mRotateExitAnimation, mAnimLeash, finishCallback, mTransactionPool, mainExecutor, null /* position */, 0 /* cornerRadius */, - null /* clipRect */); + null /* clipRect */, false /* isActivity */); } private void buildScreenshotAlphaAnimation(@NonNull ArrayList<Animator> animations, @NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor) { buildSurfaceAnimation(animations, mRotateAlphaAnimation, mAnimLeash, finishCallback, mTransactionPool, mainExecutor, null /* position */, 0 /* cornerRadius */, - null /* clipRect */); + null /* clipRect */, false /* isActivity */); } private void startColorAnimation(float animationScale, @NonNull ShellExecutor animExecutor) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/OWNERS index 4417209b85ed..3f828f547920 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/OWNERS +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/OWNERS @@ -1 +1,3 @@ jorgegil@google.com +mattsziklay@google.com +mdehaini@google.com diff --git a/libs/WindowManager/Shell/tests/OWNERS b/libs/WindowManager/Shell/tests/OWNERS index d1be12f6d1bb..65e50f86e8fe 100644 --- a/libs/WindowManager/Shell/tests/OWNERS +++ b/libs/WindowManager/Shell/tests/OWNERS @@ -18,3 +18,5 @@ peanutbutter@google.com pragyabajoria@google.com uysalorhan@google.com gsennton@google.com +mattsziklay@google.com +mdehaini@google.com diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/OWNERS b/libs/WindowManager/Shell/tests/e2e/desktopmode/OWNERS index 73a5a23909c5..73a5a23909c5 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/OWNERS +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/OWNERS diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/Android.bp b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/Android.bp new file mode 100644 index 000000000000..50581f7e01f3 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/Android.bp @@ -0,0 +1,38 @@ +// +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test { + name: "WMShellFlickerTestsDesktopMode", + defaults: ["WMShellFlickerTestsDefault"], + manifest: "AndroidManifest.xml", + test_config_template: "AndroidTestTemplate.xml", + srcs: ["src/**/*.kt"], + static_libs: [ + "WMShellFlickerTestsBase", + "WMShellScenariosDesktopMode", + "WMShellTestUtils", + ], + data: ["trace_config/*"], +} diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/AndroidManifest.xml b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/AndroidManifest.xml new file mode 100644 index 000000000000..1bbbefadaa03 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/AndroidManifest.xml @@ -0,0 +1,77 @@ +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + package="com.android.wm.shell.flicker"> + + <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29"/> + <!-- Read and write traces from external storage --> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> + <!-- Allow the test to write directly to /sdcard/ --> + <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" /> + <!-- Write secure settings --> + <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> + <!-- Capture screen contents --> + <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" /> + <!-- Enable / Disable tracing !--> + <uses-permission android:name="android.permission.DUMP" /> + <!-- Run layers trace --> + <uses-permission android:name="android.permission.HARDWARE_TEST"/> + <!-- Capture screen recording --> + <uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT"/> + <!-- Workaround grant runtime permission exception from b/152733071 --> + <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/> + <uses-permission android:name="android.permission.READ_LOGS"/> + <!-- Force-stop test apps --> + <uses-permission android:name="android.permission.FORCE_STOP_PACKAGES"/> + <!-- Control test app's media session --> + <uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL"/> + <!-- ATM.removeRootTasksWithActivityTypes() --> + <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS" /> + <!-- Enable bubble notification--> + <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" /> + <!-- Allow the test to connect to perfetto trace processor --> + <uses-permission android:name="android.permission.INTERNET"/> + + <!-- Allow the test to write directly to /sdcard/ and connect to trace processor --> + <application android:requestLegacyExternalStorage="true" + android:networkSecurityConfig="@xml/network_security_config" + android:largeHeap="true"> + <uses-library android:name="android.test.runner"/> + + <service android:name=".NotificationListener" + android:exported="true" + android:label="WMShellTestsNotificationListenerService" + android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"> + <intent-filter> + <action android:name="android.service.notification.NotificationListenerService" /> + </intent-filter> + </service> + + <!-- (b/197936012) Remove startup provider due to test timeout issue --> + <provider + android:name="androidx.startup.InitializationProvider" + android:authorities="${applicationId}.androidx-startup" + tools:node="remove" /> + </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.wm.shell.flicker" + android:label="WindowManager Shell Flicker Tests"> + </instrumentation> +</manifest> diff --git a/libs/WindowManager/Shell/tests/flicker/service/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/AndroidTestTemplate.xml index a66dfb4566f9..40dbbac32c7f 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/AndroidTestTemplate.xml +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/AndroidTestTemplate.xml @@ -59,13 +59,6 @@ <option name="test-file-name" value="{MODULE}.apk"/> <option name="test-file-name" value="FlickerTestApp.apk"/> </target_preparer> - <!-- Enable mocking GPS location by the test app --> - <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> - <option name="run-command" - value="appops set com.android.wm.shell.flicker.pip.apps android:mock_location allow"/> - <option name="teardown-command" - value="appops set com.android.wm.shell.flicker.pip.apps android:mock_location deny"/> - </target_preparer> <!-- Needed for pushing the trace config file --> <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/> @@ -97,7 +90,7 @@ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> <option name="pull-pattern-keys" value="perfetto_file_path"/> <option name="directory-keys" - value="/data/user/0/com.android.wm.shell.flicker.service/files"/> + value="/data/user/0/com.android.wm.shell.flicker/files"/> <option name="collect-on-run-ended-only" value="true"/> <option name="clean-up" value="true"/> </metrics_collector> diff --git a/libs/WindowManager/Shell/tests/flicker/service/res/xml/network_security_config.xml b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/res/xml/network_security_config.xml index 4bd9ca049f55..4bd9ca049f55 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/res/xml/network_security_config.xml +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/res/xml/network_security_config.xml diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/CloseAllAppWithAppHeaderExitLandscape.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/CloseAllAppWithAppHeaderExitLandscape.kt index 5563bb9fa934..b697d80fd500 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/CloseAllAppWithAppHeaderExitLandscape.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/CloseAllAppWithAppHeaderExitLandscape.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.desktopmode.flicker +package com.android.wm.shell.flicker import android.tools.Rotation import android.tools.flicker.FlickerConfig @@ -23,9 +23,9 @@ import android.tools.flicker.annotation.FlickerConfigProvider import android.tools.flicker.config.FlickerConfig import android.tools.flicker.config.FlickerServiceConfig import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner -import com.android.wm.shell.flicker.service.desktopmode.flicker.DesktopModeFlickerScenarios.Companion.CLOSE_APP -import com.android.wm.shell.flicker.service.desktopmode.flicker.DesktopModeFlickerScenarios.Companion.CLOSE_LAST_APP -import com.android.wm.shell.flicker.service.desktopmode.scenarios.CloseAllAppsWithAppHeaderExit +import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.CLOSE_APP +import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.CLOSE_LAST_APP +import com.android.wm.shell.scenarios.CloseAllAppsWithAppHeaderExit import org.junit.Test import org.junit.runner.RunWith diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/CloseAllAppWithAppHeaderExitPortrait.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/CloseAllAppWithAppHeaderExitPortrait.kt index 3d16d2219c78..a11e876c5bce 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/CloseAllAppWithAppHeaderExitPortrait.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/CloseAllAppWithAppHeaderExitPortrait.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.desktopmode.flicker +package com.android.wm.shell.flicker import android.tools.Rotation import android.tools.flicker.FlickerConfig @@ -23,9 +23,9 @@ import android.tools.flicker.annotation.FlickerConfigProvider import android.tools.flicker.config.FlickerConfig import android.tools.flicker.config.FlickerServiceConfig import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner -import com.android.wm.shell.flicker.service.desktopmode.flicker.DesktopModeFlickerScenarios.Companion.CLOSE_APP -import com.android.wm.shell.flicker.service.desktopmode.flicker.DesktopModeFlickerScenarios.Companion.CLOSE_LAST_APP -import com.android.wm.shell.flicker.service.desktopmode.scenarios.CloseAllAppsWithAppHeaderExit +import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.CLOSE_APP +import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.CLOSE_LAST_APP +import com.android.wm.shell.scenarios.CloseAllAppsWithAppHeaderExit import org.junit.Test import org.junit.runner.RunWith diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/DesktopModeFlickerScenarios.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt index 430f80b9a927..8584b599a96c 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/DesktopModeFlickerScenarios.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.desktopmode.flicker +package com.android.wm.shell.flicker import android.tools.flicker.AssertionInvocationGroup import android.tools.flicker.assertors.assertions.AppLayerIsInvisibleAtEnd diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/EnterDesktopWithDragLandscape.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/EnterDesktopWithDragLandscape.kt index 9dfafe958b0b..f7b25565271f 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/EnterDesktopWithDragLandscape.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/EnterDesktopWithDragLandscape.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.desktopmode.flicker +package com.android.wm.shell.flicker import android.tools.Rotation import android.tools.flicker.FlickerConfig @@ -23,8 +23,8 @@ import android.tools.flicker.annotation.FlickerConfigProvider import android.tools.flicker.config.FlickerConfig import android.tools.flicker.config.FlickerServiceConfig import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner -import com.android.wm.shell.flicker.service.desktopmode.flicker.DesktopModeFlickerScenarios.Companion.END_DRAG_TO_DESKTOP -import com.android.wm.shell.flicker.service.desktopmode.scenarios.EnterDesktopWithDrag +import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.END_DRAG_TO_DESKTOP +import com.android.wm.shell.scenarios.EnterDesktopWithDrag import org.junit.Test import org.junit.runner.RunWith diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/EnterDesktopWithDragPortrait.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/EnterDesktopWithDragPortrait.kt index 1c7d6237eb8a..f4bf0f97b042 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/EnterDesktopWithDragPortrait.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/EnterDesktopWithDragPortrait.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.desktopmode.flicker +package com.android.wm.shell.flicker import android.tools.Rotation import android.tools.flicker.FlickerConfig @@ -23,8 +23,8 @@ import android.tools.flicker.annotation.FlickerConfigProvider import android.tools.flicker.config.FlickerConfig import android.tools.flicker.config.FlickerServiceConfig import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner -import com.android.wm.shell.flicker.service.desktopmode.flicker.DesktopModeFlickerScenarios.Companion.END_DRAG_TO_DESKTOP -import com.android.wm.shell.flicker.service.desktopmode.scenarios.EnterDesktopWithDrag +import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.END_DRAG_TO_DESKTOP +import com.android.wm.shell.scenarios.EnterDesktopWithDrag import org.junit.Test import org.junit.runner.RunWith diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppToMinimumWindowSizeLandscape.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ResizeAppToMinimumWindowSizeLandscape.kt index 6319cf74ed8f..45e5fdc28b0e 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppToMinimumWindowSizeLandscape.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ResizeAppToMinimumWindowSizeLandscape.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.desktopmode.flicker +package com.android.wm.shell.flicker import android.tools.Rotation import android.tools.flicker.FlickerConfig @@ -23,8 +23,8 @@ import android.tools.flicker.annotation.FlickerConfigProvider import android.tools.flicker.config.FlickerConfig import android.tools.flicker.config.FlickerServiceConfig import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner -import com.android.wm.shell.flicker.service.desktopmode.flicker.DesktopModeFlickerScenarios.Companion.CORNER_RESIZE_TO_MINIMUM_SIZE -import com.android.wm.shell.flicker.service.desktopmode.scenarios.ResizeAppWithCornerResize +import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.CORNER_RESIZE_TO_MINIMUM_SIZE +import com.android.wm.shell.scenarios.ResizeAppWithCornerResize import org.junit.Test import org.junit.runner.RunWith diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppToMinimumWindowSizePortrait.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ResizeAppToMinimumWindowSizePortrait.kt index 431f6e3d3ea2..62a2571a2804 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppToMinimumWindowSizePortrait.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ResizeAppToMinimumWindowSizePortrait.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.desktopmode.flicker +package com.android.wm.shell.flicker import android.tools.flicker.FlickerConfig import android.tools.flicker.annotation.ExpectedScenarios @@ -22,8 +22,8 @@ import android.tools.flicker.annotation.FlickerConfigProvider import android.tools.flicker.config.FlickerConfig import android.tools.flicker.config.FlickerServiceConfig import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner -import com.android.wm.shell.flicker.service.desktopmode.flicker.DesktopModeFlickerScenarios.Companion.CORNER_RESIZE_TO_MINIMUM_SIZE -import com.android.wm.shell.flicker.service.desktopmode.scenarios.ResizeAppWithCornerResize +import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.CORNER_RESIZE_TO_MINIMUM_SIZE +import com.android.wm.shell.scenarios.ResizeAppWithCornerResize import org.junit.Test import org.junit.runner.RunWith diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppWithCornerResizeLandscape.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ResizeAppWithCornerResizeLandscape.kt index 8d1a53021683..ea8b10b28855 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppWithCornerResizeLandscape.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ResizeAppWithCornerResizeLandscape.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.desktopmode.flicker +package com.android.wm.shell.flicker import android.tools.Rotation import android.tools.flicker.FlickerConfig @@ -23,8 +23,8 @@ import android.tools.flicker.annotation.FlickerConfigProvider import android.tools.flicker.config.FlickerConfig import android.tools.flicker.config.FlickerServiceConfig import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner -import com.android.wm.shell.flicker.service.desktopmode.flicker.DesktopModeFlickerScenarios.Companion.CORNER_RESIZE -import com.android.wm.shell.flicker.service.desktopmode.scenarios.ResizeAppWithCornerResize +import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.CORNER_RESIZE +import com.android.wm.shell.scenarios.ResizeAppWithCornerResize import org.junit.Test import org.junit.runner.RunWith diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppWithCornerResizePortrait.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ResizeAppWithCornerResizePortrait.kt index 2d81c8c44799..d7bba6ec49d2 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppWithCornerResizePortrait.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ResizeAppWithCornerResizePortrait.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.desktopmode.flicker +package com.android.wm.shell.flicker import android.tools.Rotation import android.tools.flicker.FlickerConfig @@ -23,8 +23,8 @@ import android.tools.flicker.annotation.FlickerConfigProvider import android.tools.flicker.config.FlickerConfig import android.tools.flicker.config.FlickerServiceConfig import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner -import com.android.wm.shell.flicker.service.desktopmode.flicker.DesktopModeFlickerScenarios.Companion.CORNER_RESIZE -import com.android.wm.shell.flicker.service.desktopmode.scenarios.ResizeAppWithCornerResize +import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.CORNER_RESIZE +import com.android.wm.shell.scenarios.ResizeAppWithCornerResize import org.junit.Test import org.junit.runner.RunWith diff --git a/libs/WindowManager/Shell/tests/flicker/service/trace_config/trace_config.textproto b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/trace_config/trace_config.textproto index 9f2e49755fec..9f2e49755fec 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/trace_config/trace_config.textproto +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/trace_config/trace_config.textproto diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/Android.bp b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/Android.bp new file mode 100644 index 000000000000..4389f09b0e5d --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/Android.bp @@ -0,0 +1,46 @@ +// +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +java_library { + name: "WMShellScenariosDesktopMode", + platform_apis: true, + optimize: { + enabled: false, + }, + srcs: ["src/**/*.kt"], + static_libs: [ + "WMShellFlickerTestsBase", + "WMShellTestUtils", + "wm-shell-flicker-utils", + "androidx.test.ext.junit", + "flickertestapplib", + "flickerlib-helpers", + "flickerlib-trace_processor_shell", + "platform-test-annotations", + "wm-flicker-common-app-helpers", + "launcher-helper-lib", + "launcher-aosp-tapl", + ], +} diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/CloseAllAppsWithAppHeaderExit.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/CloseAllAppsWithAppHeaderExit.kt index e77a45729124..e9056f3c44d4 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/CloseAllAppsWithAppHeaderExit.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/CloseAllAppsWithAppHeaderExit.kt @@ -14,8 +14,9 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.desktopmode.scenarios +package com.android.wm.shell.scenarios +import android.platform.test.annotations.Postsubmit import android.app.Instrumentation import android.tools.NavBar import android.tools.Rotation @@ -28,16 +29,18 @@ import com.android.server.wm.flicker.helpers.MailAppHelper import com.android.server.wm.flicker.helpers.NonResizeableAppHelper import com.android.server.wm.flicker.helpers.SimpleAppHelper import com.android.window.flags.Flags -import com.android.wm.shell.flicker.service.common.Utils +import com.android.wm.shell.Utils import org.junit.After import org.junit.Assume import org.junit.Before -import org.junit.Ignore import org.junit.Rule import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner -@Ignore("Base Test Class") -abstract class CloseAllAppsWithAppHeaderExit +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +open class CloseAllAppsWithAppHeaderExit @JvmOverloads constructor(val rotation: Rotation = Rotation.ROTATION_0) { diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/DragAppWindowMultiWindow.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowMultiWindow.kt index bbf0ce5f8165..ca1dc1a7f658 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/DragAppWindowMultiWindow.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowMultiWindow.kt @@ -14,8 +14,9 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.desktopmode.scenarios +package com.android.wm.shell.scenarios +import android.platform.test.annotations.Postsubmit import com.android.server.wm.flicker.helpers.DesktopModeAppHelper import com.android.server.wm.flicker.helpers.ImeAppHelper import com.android.server.wm.flicker.helpers.MailAppHelper @@ -25,12 +26,13 @@ import com.android.window.flags.Flags import org.junit.After import org.junit.Assume import org.junit.Before -import org.junit.Ignore import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner -/** Base scenario test for window drag CUJ with multiple windows. */ -@Ignore("Base Test Class") -abstract class DragAppWindowMultiWindow : DragAppWindowScenarioTestBase() +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +open class DragAppWindowMultiWindow : DragAppWindowScenarioTestBase() { private val imeAppHelper = ImeAppHelper(instrumentation) private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation)) diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/DragAppWindowScenarioTestBase.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowScenarioTestBase.kt index a613ca1660ea..7219287d97d3 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/DragAppWindowScenarioTestBase.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowScenarioTestBase.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.desktopmode.scenarios +package com.android.wm.shell.scenarios import android.app.Instrumentation import android.tools.NavBar @@ -24,7 +24,7 @@ import android.tools.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation -import com.android.wm.shell.flicker.service.common.Utils +import com.android.wm.shell.Utils import org.junit.Ignore import org.junit.Rule import org.junit.Test diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/DragAppWindowSingleWindow.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowSingleWindow.kt index 0655620d58b7..91cfd17340fc 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/DragAppWindowSingleWindow.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowSingleWindow.kt @@ -14,20 +14,21 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.desktopmode.scenarios +package com.android.wm.shell.scenarios +import android.platform.test.annotations.Postsubmit import com.android.server.wm.flicker.helpers.DesktopModeAppHelper import com.android.server.wm.flicker.helpers.SimpleAppHelper import com.android.window.flags.Flags import org.junit.After import org.junit.Assume import org.junit.Before -import org.junit.Ignore import org.junit.Test - -/** Base scenario test for window drag CUJ with single window. */ -@Ignore("Base Test Class") -abstract class DragAppWindowSingleWindow : DragAppWindowScenarioTestBase() +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +open class DragAppWindowSingleWindow : DragAppWindowScenarioTestBase() { private val simpleAppHelper = SimpleAppHelper(instrumentation) private val testApp = DesktopModeAppHelper(simpleAppHelper) diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/EnterDesktopWithAppHandleMenu.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithAppHandleMenu.kt index 47a215a97c00..107305044d55 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/EnterDesktopWithAppHandleMenu.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithAppHandleMenu.kt @@ -14,8 +14,9 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.desktopmode.scenarios +package com.android.wm.shell.scenarios +import android.platform.test.annotations.Postsubmit import android.app.Instrumentation import android.tools.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry @@ -27,12 +28,12 @@ import com.android.window.flags.Flags import org.junit.After import org.junit.Assume import org.junit.Before -import org.junit.Ignore import org.junit.Test - -/** Base test class for enter desktop with app handle menu CUJ. */ -@Ignore("Base Test Class") -abstract class EnterDesktopWithAppHandleMenu { +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +open class EnterDesktopWithAppHandleMenu { private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() private val tapl = LauncherInstrumentation() diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/EnterDesktopWithDrag.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDrag.kt index fe139d2d24a0..0f0d2df4337c 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/EnterDesktopWithDrag.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDrag.kt @@ -14,8 +14,9 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.desktopmode.scenarios +package com.android.wm.shell.scenarios +import android.platform.test.annotations.Postsubmit import android.app.Instrumentation import android.tools.NavBar import android.tools.Rotation @@ -26,17 +27,18 @@ import com.android.launcher3.tapl.LauncherInstrumentation import com.android.server.wm.flicker.helpers.DesktopModeAppHelper import com.android.server.wm.flicker.helpers.SimpleAppHelper import com.android.window.flags.Flags -import com.android.wm.shell.flicker.service.common.Utils +import com.android.wm.shell.Utils import org.junit.After import org.junit.Assume import org.junit.Before -import org.junit.Ignore import org.junit.Rule import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner - -@Ignore("Base Test Class") -abstract class EnterDesktopWithDrag +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +open class EnterDesktopWithDrag @JvmOverloads constructor(val rotation: Rotation = Rotation.ROTATION_0) { diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/ExitDesktopWithDragToTopDragZone.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopWithDragToTopDragZone.kt index 0b6c9af17e7a..533be8895fb9 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/ExitDesktopWithDragToTopDragZone.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopWithDragToTopDragZone.kt @@ -14,8 +14,9 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.desktopmode.scenarios +package com.android.wm.shell.scenarios +import android.platform.test.annotations.Postsubmit import android.app.Instrumentation import android.tools.NavBar import android.tools.Rotation @@ -26,17 +27,18 @@ import com.android.launcher3.tapl.LauncherInstrumentation import com.android.server.wm.flicker.helpers.DesktopModeAppHelper import com.android.server.wm.flicker.helpers.SimpleAppHelper import com.android.window.flags.Flags -import com.android.wm.shell.flicker.service.common.Utils +import com.android.wm.shell.Utils import org.junit.After import org.junit.Assume import org.junit.Before -import org.junit.Ignore import org.junit.Rule import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner - -@Ignore("Base Test Class") -abstract class ExitDesktopWithDragToTopDragZone +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +open class ExitDesktopWithDragToTopDragZone @JvmOverloads constructor(val rotation: Rotation = Rotation.ROTATION_0) { diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/MaximizeAppWindow.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindow.kt index 20e2167c28f2..e3660fe13bc2 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/MaximizeAppWindow.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindow.kt @@ -14,8 +14,9 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.desktopmode.scenarios +package com.android.wm.shell.scenarios +import android.platform.test.annotations.Postsubmit import android.app.Instrumentation import android.tools.NavBar import android.tools.Rotation @@ -26,17 +27,17 @@ import com.android.launcher3.tapl.LauncherInstrumentation import com.android.server.wm.flicker.helpers.DesktopModeAppHelper import com.android.server.wm.flicker.helpers.SimpleAppHelper import com.android.window.flags.Flags -import com.android.wm.shell.flicker.service.common.Utils +import com.android.wm.shell.Utils import org.junit.After import org.junit.Assume import org.junit.Before -import org.junit.Ignore import org.junit.Rule import org.junit.Test - -/** Base scenario test for maximize app window CUJ in desktop mode. */ -@Ignore("Base Test Class") -abstract class MaximizeAppWindow +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +open class MaximizeAppWindow { private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() private val tapl = LauncherInstrumentation() diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/MinimizeWindowOnAppOpen.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeWindowOnAppOpen.kt index c8477102a7ad..b86765e23422 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/MinimizeWindowOnAppOpen.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeWindowOnAppOpen.kt @@ -14,8 +14,9 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.desktopmode.scenarios +package com.android.wm.shell.scenarios +import android.platform.test.annotations.Postsubmit import android.app.Instrumentation import android.tools.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry @@ -31,16 +32,17 @@ import com.android.window.flags.Flags import org.junit.After import org.junit.Assume import org.junit.Before -import org.junit.Ignore import org.junit.Test - +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner /** * Base scenario test for minimizing the least recently used window when a new window is opened * above the window limit. For tangor devices, which this test currently runs on, the window limit * is 4. */ -@Ignore("Base Test Class") -abstract class MinimizeWindowOnAppOpen() +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +open class MinimizeWindowOnAppOpen() { private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() private val tapl = LauncherInstrumentation() diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/ResizeAppWithCornerResize.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithCornerResize.kt index 136cf378aa09..63e7387f7a8a 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/ResizeAppWithCornerResize.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithCornerResize.kt @@ -14,8 +14,9 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.desktopmode.scenarios +package com.android.wm.shell.scenarios +import android.platform.test.annotations.Postsubmit import android.app.Instrumentation import android.tools.NavBar import android.tools.Rotation @@ -26,17 +27,18 @@ import com.android.launcher3.tapl.LauncherInstrumentation import com.android.server.wm.flicker.helpers.DesktopModeAppHelper import com.android.server.wm.flicker.helpers.SimpleAppHelper import com.android.window.flags.Flags -import com.android.wm.shell.flicker.service.common.Utils +import com.android.wm.shell.Utils import org.junit.After import org.junit.Assume import org.junit.Before -import org.junit.Ignore import org.junit.Rule import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner - -@Ignore("Base Test Class") -abstract class ResizeAppWithCornerResize +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +open class ResizeAppWithCornerResize @JvmOverloads constructor(val rotation: Rotation = Rotation.ROTATION_0, val horizontalChange: Int = 50, diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/SwitchToOverviewFromDesktop.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SwitchToOverviewFromDesktop.kt index b4cadf4f300b..53e36e23fd95 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/SwitchToOverviewFromDesktop.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SwitchToOverviewFromDesktop.kt @@ -14,8 +14,9 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.desktopmode.scenarios +package com.android.wm.shell.scenarios +import android.platform.test.annotations.Postsubmit import android.app.Instrumentation import android.tools.NavBar import android.tools.Rotation @@ -26,21 +27,23 @@ import com.android.launcher3.tapl.LauncherInstrumentation import com.android.server.wm.flicker.helpers.DesktopModeAppHelper import com.android.server.wm.flicker.helpers.SimpleAppHelper import com.android.window.flags.Flags -import com.android.wm.shell.flicker.service.common.Utils +import com.android.wm.shell.Utils import org.junit.After import org.junit.Assume import org.junit.Before -import org.junit.Ignore import org.junit.Rule import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner /** * Base test for opening recent apps overview from desktop mode. * * Navigation mode can be passed as a constructor parameter, by default it is set to gesture navigation. */ -@Ignore("Base Test Class") -abstract class SwitchToOverviewFromDesktop +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +open class SwitchToOverviewFromDesktop @JvmOverloads constructor(val navigationMode: NavBar = NavBar.MODE_GESTURAL) { diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/OWNERS b/libs/WindowManager/Shell/tests/e2e/splitscreen/OWNERS index 3ab6a1ee061d..3ab6a1ee061d 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/OWNERS +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/OWNERS diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/Android.bp b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/Android.bp index 35b2f56bca92..35b2f56bca92 100644 --- a/libs/WindowManager/Shell/tests/flicker/splitscreen/Android.bp +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/Android.bp diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/AndroidManifest.xml b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/AndroidManifest.xml index 9ff2161daa51..9ff2161daa51 100644 --- a/libs/WindowManager/Shell/tests/flicker/splitscreen/AndroidManifest.xml +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/AndroidManifest.xml diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/AndroidTestTemplate.xml index 85715db3d952..85715db3d952 100644 --- a/libs/WindowManager/Shell/tests/flicker/splitscreen/AndroidTestTemplate.xml +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/AndroidTestTemplate.xml diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/OWNERS b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/OWNERS index 3ab6a1ee061d..3ab6a1ee061d 100644 --- a/libs/WindowManager/Shell/tests/flicker/splitscreen/OWNERS +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/OWNERS diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/res/xml/network_security_config.xml b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/res/xml/network_security_config.xml index 4bd9ca049f55..4bd9ca049f55 100644 --- a/libs/WindowManager/Shell/tests/flicker/splitscreen/res/xml/network_security_config.xml +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/res/xml/network_security_config.xml diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt index 7f48499b0558..7f48499b0558 100644 --- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt index dd45f654d3bc..dd45f654d3bc 100644 --- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt index 6d396ea6e9d4..6d396ea6e9d4 100644 --- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt index 2ed916e56c67..2ed916e56c67 100644 --- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt index 1a455311b3b6..1a455311b3b6 100644 --- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt index 0cb1e4006c0d..0cb1e4006c0d 100644 --- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt index ff406b75b235..ff406b75b235 100644 --- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt index 2b817988a589..2b817988a589 100644 --- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt index 186af54fb57b..186af54fb57b 100644 --- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/MultipleShowImeRequestsInSplitScreen.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/MultipleShowImeRequestsInSplitScreen.kt index dad5db94d062..a9dba4a3178b 100644 --- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/MultipleShowImeRequestsInSplitScreen.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/MultipleShowImeRequestsInSplitScreen.kt @@ -17,11 +17,13 @@ package com.android.wm.shell.flicker.splitscreen import android.platform.test.annotations.Presubmit +import android.tools.NavBar import android.tools.Rotation +import android.tools.ScenarioBuilder import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest -import android.tools.flicker.legacy.LegacyFlickerTestFactory +import android.tools.traces.SERVICE_TRACE_CONFIG import android.tools.traces.component.ComponentNameMatcher import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.splitscreen.benchmark.MultipleShowImeRequestsInSplitScreenBenchmark @@ -35,7 +37,7 @@ import org.junit.runners.Parameterized /** * Test quick switch between two split pairs. * - * To run this test: `atest WMShellFlickerTestsSplitScreenGroup2:MultipleShowImeRequestsInSplitScreen` + * To run this test: `atest WMShellFlickerTestsSplitScreenGroupOther:MultipleShowImeRequestsInSplitScreen` */ @RequiresDevice @RunWith(Parameterized::class) @@ -58,10 +60,22 @@ class MultipleShowImeRequestsInSplitScreen(override val flicker: LegacyFlickerTe } companion object { + private fun createFlickerTest( + navBarMode: NavBar + ) = LegacyFlickerTest(ScenarioBuilder() + .withStartRotation(Rotation.ROTATION_0) + .withEndRotation(Rotation.ROTATION_0) + .withNavBarMode(navBarMode), resultReaderProvider = { scenario -> + android.tools.flicker.datastore.CachedResultReader( + scenario, SERVICE_TRACE_CONFIG + ) + }) + @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams() = LegacyFlickerTestFactory.nonRotationTests( - supportedRotations = listOf(Rotation.ROTATION_0) + fun getParams() = listOf( + createFlickerTest(NavBar.MODE_GESTURAL), + createFlickerTest(NavBar.MODE_3BUTTON) ) } -} +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt index 9dde49011ed0..9dde49011ed0 100644 --- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt index 5222b08240c6..5222b08240c6 100644 --- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt index a8a8ae88a9e7..a8a8ae88a9e7 100644 --- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt index 836f664ca544..836f664ca544 100644 --- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt index 3c4a1caecb8d..3c4a1caecb8d 100644 --- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairsNoPip.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairsNoPip.kt index a72b3d15eb9e..a72b3d15eb9e 100644 --- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairsNoPip.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairsNoPip.kt diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt index d34998815fca..3018b56b13b2 100644 --- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.splitscreen -import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import android.tools.NavBar import android.tools.flicker.junit.FlickerParametersRunnerFactory diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt index 7e8e50843b90..7e8e50843b90 100644 --- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt index c99fcc4129d5..c99fcc4129d5 100644 --- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt index ef3a87955bd6..ef3a87955bd6 100644 --- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt index 18550d7f0467..18550d7f0467 100644 --- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt index d16c5d77410c..d16c5d77410c 100644 --- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt index f8be6be08782..f8be6be08782 100644 --- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt index a99ef64e7bf5..a99ef64e7bf5 100644 --- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt index f58400966531..f58400966531 100644 --- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt index 7084f6aec1fb..7084f6aec1fb 100644 --- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/MultipleShowImeRequestsInSplitScreenBenchmark.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/MultipleShowImeRequestsInSplitScreenBenchmark.kt index 249253185607..249253185607 100644 --- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/MultipleShowImeRequestsInSplitScreenBenchmark.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/MultipleShowImeRequestsInSplitScreenBenchmark.kt diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SplitScreenBase.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/SplitScreenBase.kt index 51074f634e30..51074f634e30 100644 --- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SplitScreenBase.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/SplitScreenBase.kt diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt index 6a6aa1abc9f3..6a6aa1abc9f3 100644 --- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt index 3a2316f7a10c..3a2316f7a10c 100644 --- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt index ded0b0729998..ded0b0729998 100644 --- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt index 7b1397baa7a3..7b1397baa7a3 100644 --- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt index 457288f445df..457288f445df 100644 --- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt index 7493538fa2ba..7493538fa2ba 100644 --- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/trace_config/trace_config.textproto b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/trace_config/trace_config.textproto index 67316d2d7c0f..67316d2d7c0f 100644 --- a/libs/WindowManager/Shell/tests/flicker/splitscreen/trace_config/trace_config.textproto +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/trace_config/trace_config.textproto diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/Android.bp b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/Android.bp new file mode 100644 index 000000000000..dd0018aae058 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/Android.bp @@ -0,0 +1,41 @@ +// +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test { + name: "WMShellFlickerServiceTests", + defaults: ["WMShellFlickerTestsDefault"], + manifest: "AndroidManifest.xml", + test_config_template: "AndroidTestTemplate.xml", + srcs: ["src/**/*.kt"], + static_libs: [ + "WMShellFlickerTestsBase", + "WMShellScenariosSplitScreen", + "WMShellTestUtils", + ], + data: [ + ":FlickerTestApp", + "trace_config/*", + ], +} diff --git a/libs/WindowManager/Shell/tests/flicker/service/AndroidManifest.xml b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidManifest.xml index d54b6941d975..662e7f346cb3 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/AndroidManifest.xml +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidManifest.xml @@ -16,7 +16,7 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" - package="com.android.wm.shell.flicker.service"> + package="com.android.wm.shell"> <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29"/> <!-- Read and write traces from external storage --> @@ -71,7 +71,7 @@ </application> <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" - android:targetPackage="com.android.wm.shell.flicker.service" + android:targetPackage="com.android.wm.shell" android:label="WindowManager Flicker Service Tests"> </instrumentation> </manifest> diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidTestTemplate.xml new file mode 100644 index 000000000000..6c903a2e8c42 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidTestTemplate.xml @@ -0,0 +1,82 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<configuration description="WMShell Platinum Tests {MODULE}"> + <option name="test-tag" value="FlickerTests"/> + <!-- Needed for storing the perfetto trace files in the sdcard/test_results--> + <option name="isolated-storage" value="false"/> + + <target_preparer class="com.android.tradefed.targetprep.DeviceSetup"> + <!-- disable DeprecatedTargetSdk warning --> + <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/> + <!-- keeps the screen on during tests --> + <option name="screen-always-on" value="on"/> + <!-- prevents the phone from restarting --> + <option name="force-skip-system-props" value="true"/> + <!-- set WM tracing verbose level to all --> + <option name="run-command" value="cmd window tracing level all"/> + <!-- set WM tracing to frame (avoid incomplete states) --> + <option name="run-command" value="cmd window tracing frame"/> + <!-- disable betterbug as it's log collection dialogues cause flakes in e2e tests --> + <option name="run-command" value="pm disable com.google.android.internal.betterbug"/> + <!-- ensure lock screen mode is swipe --> + <option name="run-command" value="locksettings set-disabled false"/> + <!-- restart launcher to activate TAPL --> + <option name="run-command" + value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher"/> + <!-- Increase trace size: 20mb for WM and 80mb for SF --> + <option name="run-command" value="cmd window tracing size 20480"/> + <option name="run-command" value="su root service call SurfaceFlinger 1029 i32 81920"/> + </target_preparer> + <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> + <option name="test-user-token" value="%TEST_USER%"/> + <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/> + <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/> + <option name="run-command" value="settings put system show_touches 1"/> + <option name="run-command" value="settings put system pointer_location 1"/> + <option name="teardown-command" + value="settings delete secure show_ime_with_hard_keyboard"/> + <option name="teardown-command" value="settings delete system show_touches"/> + <option name="teardown-command" value="settings delete system pointer_location"/> + <option name="teardown-command" + value="cmd overlay enable com.android.internal.systemui.navbar.gestural"/> + </target_preparer> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true"/> + <option name="test-file-name" value="{MODULE}.apk"/> + <option name="test-file-name" value="FlickerTestApp.apk"/> + </target_preparer> + <!-- Needed for pushing the trace config file --> + <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/> + <test class="com.android.tradefed.testtype.AndroidJUnitTest"> + <option name="package" value="{PACKAGE}"/> + <option name="shell-timeout" value="6600s"/> + <option name="test-timeout" value="6000s"/> + <option name="hidden-api-checks" value="false"/> + <option name="device-listeners" value="android.tools.collectors.DefaultUITraceListener"/> + <!-- DefaultUITraceListener args --> + <option name="instrumentation-arg" key="per_run" value="true"/> + <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/> + </test> + <!-- Needed for pulling the collected trace config on to the host --> + <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> + <option name="pull-pattern-keys" value="perfetto_file_path"/> + <option name="directory-keys" + value="/data/user/0/com.android.wm.shell/files"/> + <option name="collect-on-run-ended-only" value="true"/> + <option name="clean-up" value="true"/> + </metrics_collector> +</configuration> diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/res/xml/network_security_config.xml b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/res/xml/network_security_config.xml new file mode 100644 index 000000000000..4bd9ca049f55 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/res/xml/network_security_config.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<network-security-config> + <domain-config cleartextTrafficPermitted="true"> + <domain includeSubdomains="true">localhost</domain> + </domain-config> +</network-security-config> diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/CopyContentInSplitGesturalNavLandscape.kt index 1684a26ac3d2..3cb9cf24d522 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/CopyContentInSplitGesturalNavLandscape.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.flicker +package com.android.wm.shell.flicker import android.tools.Rotation import android.tools.flicker.FlickerConfig @@ -23,7 +23,7 @@ import android.tools.flicker.annotation.FlickerConfigProvider import android.tools.flicker.config.FlickerConfig import android.tools.flicker.config.FlickerServiceConfig import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner -import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit +import com.android.wm.shell.scenarios.CopyContentInSplit import org.junit.Test import org.junit.runner.RunWith diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/CopyContentInSplitGesturalNavPortrait.kt index 3b5fad60d8ee..b27a8aedb5c1 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/CopyContentInSplitGesturalNavPortrait.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.flicker +package com.android.wm.shell.flicker import android.tools.Rotation import android.tools.flicker.FlickerConfig @@ -23,7 +23,7 @@ import android.tools.flicker.annotation.FlickerConfigProvider import android.tools.flicker.config.FlickerConfig import android.tools.flicker.config.FlickerServiceConfig import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner -import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit +import com.android.wm.shell.scenarios.CopyContentInSplit import org.junit.Test import org.junit.runner.RunWith diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt index 2b8a90305d90..9388114889c0 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.flicker +package com.android.wm.shell.flicker import android.tools.Rotation import android.tools.flicker.FlickerConfig @@ -23,7 +23,7 @@ import android.tools.flicker.annotation.FlickerConfigProvider import android.tools.flicker.config.FlickerConfig import android.tools.flicker.config.FlickerServiceConfig import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner -import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider +import com.android.wm.shell.scenarios.DismissSplitScreenByDivider import org.junit.Test import org.junit.runner.RunWith diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt index b284fe1caad5..30ef4927bfa0 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.flicker +package com.android.wm.shell.flicker import android.tools.Rotation import android.tools.flicker.FlickerConfig @@ -23,7 +23,7 @@ import android.tools.flicker.annotation.FlickerConfigProvider import android.tools.flicker.config.FlickerConfig import android.tools.flicker.config.FlickerServiceConfig import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner -import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider +import com.android.wm.shell.scenarios.DismissSplitScreenByDivider import org.junit.Test import org.junit.runner.RunWith diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt index a400ee44caa5..059f967fbb51 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.flicker +package com.android.wm.shell.flicker import android.tools.Rotation import android.tools.flicker.FlickerConfig @@ -23,7 +23,7 @@ import android.tools.flicker.annotation.FlickerConfigProvider import android.tools.flicker.config.FlickerConfig import android.tools.flicker.config.FlickerServiceConfig import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner -import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome +import com.android.wm.shell.scenarios.DismissSplitScreenByGoHome import org.junit.Test import org.junit.runner.RunWith diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt index 7f5ee4c2cdda..0c6d546cf8e5 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.flicker +package com.android.wm.shell.flicker import android.tools.Rotation import android.tools.flicker.FlickerConfig @@ -23,7 +23,7 @@ import android.tools.flicker.annotation.FlickerConfigProvider import android.tools.flicker.config.FlickerConfig import android.tools.flicker.config.FlickerServiceConfig import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner -import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome +import com.android.wm.shell.scenarios.DismissSplitScreenByGoHome import org.junit.Test import org.junit.runner.RunWith diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/DragDividerToResizeGesturalNavLandscape.kt index 1b075c498bc0..14fb72bc349a 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/DragDividerToResizeGesturalNavLandscape.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.flicker +package com.android.wm.shell.flicker import android.tools.Rotation import android.tools.flicker.FlickerConfig @@ -23,7 +23,7 @@ import android.tools.flicker.annotation.FlickerConfigProvider import android.tools.flicker.config.FlickerConfig import android.tools.flicker.config.FlickerServiceConfig import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner -import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize +import com.android.wm.shell.scenarios.DragDividerToResize import org.junit.Test import org.junit.runner.RunWith diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/DragDividerToResizeGesturalNavPortrait.kt index 6ca373714f8a..9be61a59f101 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/DragDividerToResizeGesturalNavPortrait.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.flicker +package com.android.wm.shell.flicker import android.tools.Rotation import android.tools.flicker.FlickerConfig @@ -23,7 +23,7 @@ import android.tools.flicker.annotation.FlickerConfigProvider import android.tools.flicker.config.FlickerConfig import android.tools.flicker.config.FlickerServiceConfig import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner -import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize +import com.android.wm.shell.scenarios.DragDividerToResize import org.junit.Test import org.junit.runner.RunWith diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt index f7d231f02935..c12d1990d3ed 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.flicker +package com.android.wm.shell.flicker import android.tools.Rotation import android.tools.flicker.FlickerConfig @@ -23,7 +23,7 @@ import android.tools.flicker.annotation.FlickerConfigProvider import android.tools.flicker.config.FlickerConfig import android.tools.flicker.config.FlickerServiceConfig import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner -import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps +import com.android.wm.shell.scenarios.EnterSplitScreenByDragFromAllApps import org.junit.Test import org.junit.runner.RunWith diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt index ab819fad292a..11cd38ad72a4 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.flicker +package com.android.wm.shell.flicker import android.tools.Rotation import android.tools.flicker.FlickerConfig @@ -23,7 +23,7 @@ import android.tools.flicker.annotation.FlickerConfigProvider import android.tools.flicker.config.FlickerConfig import android.tools.flicker.config.FlickerServiceConfig import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner -import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps +import com.android.wm.shell.scenarios.EnterSplitScreenByDragFromAllApps import org.junit.Test import org.junit.runner.RunWith diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt index a6b732c47ea2..66d4bfa977c4 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.flicker +package com.android.wm.shell.flicker import android.tools.Rotation import android.tools.flicker.FlickerConfig @@ -23,7 +23,7 @@ import android.tools.flicker.annotation.FlickerConfigProvider import android.tools.flicker.config.FlickerConfig import android.tools.flicker.config.FlickerServiceConfig import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner -import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification +import com.android.wm.shell.scenarios.EnterSplitScreenByDragFromNotification import org.junit.Test import org.junit.runner.RunWith diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt index 07e5f4b0b472..f3a11eb96101 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.flicker +package com.android.wm.shell.flicker import android.tools.Rotation import android.tools.flicker.FlickerConfig @@ -23,7 +23,7 @@ import android.tools.flicker.annotation.FlickerConfigProvider import android.tools.flicker.config.FlickerConfig import android.tools.flicker.config.FlickerServiceConfig import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner -import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification +import com.android.wm.shell.scenarios.EnterSplitScreenByDragFromNotification import org.junit.Test import org.junit.runner.RunWith diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt index 272569456d7b..327ecc34a01e 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.flicker +package com.android.wm.shell.flicker import android.tools.Rotation import android.tools.flicker.FlickerConfig @@ -23,7 +23,7 @@ import android.tools.flicker.annotation.FlickerConfigProvider import android.tools.flicker.config.FlickerConfig import android.tools.flicker.config.FlickerServiceConfig import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner -import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut +import com.android.wm.shell.scenarios.EnterSplitScreenByDragFromShortcut import org.junit.Test import org.junit.runner.RunWith diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt index 58cc4d70fde4..dd5a3950537c 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.flicker +package com.android.wm.shell.flicker import android.tools.Rotation import android.tools.flicker.FlickerConfig @@ -23,7 +23,7 @@ import android.tools.flicker.annotation.FlickerConfigProvider import android.tools.flicker.config.FlickerConfig import android.tools.flicker.config.FlickerServiceConfig import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner -import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut +import com.android.wm.shell.scenarios.EnterSplitScreenByDragFromShortcut import org.junit.Test import org.junit.runner.RunWith diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt index 85897a136e33..8e7cf309c911 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.flicker +package com.android.wm.shell.flicker import android.tools.Rotation import android.tools.flicker.FlickerConfig @@ -23,7 +23,7 @@ import android.tools.flicker.annotation.FlickerConfigProvider import android.tools.flicker.config.FlickerConfig import android.tools.flicker.config.FlickerServiceConfig import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner -import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar +import com.android.wm.shell.scenarios.EnterSplitScreenByDragFromTaskbar import org.junit.Test import org.junit.runner.RunWith diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt index 891b6df89b45..0324dac44b3a 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.flicker +package com.android.wm.shell.flicker import android.tools.Rotation import android.tools.flicker.FlickerConfig @@ -23,7 +23,7 @@ import android.tools.flicker.annotation.FlickerConfigProvider import android.tools.flicker.config.FlickerConfig import android.tools.flicker.config.FlickerServiceConfig import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner -import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar +import com.android.wm.shell.scenarios.EnterSplitScreenByDragFromTaskbar import org.junit.Test import org.junit.runner.RunWith diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt index 798365218b04..2fa141eb9a91 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.flicker +package com.android.wm.shell.flicker import android.tools.Rotation import android.tools.flicker.FlickerConfig @@ -23,7 +23,7 @@ import android.tools.flicker.annotation.FlickerConfigProvider import android.tools.flicker.config.FlickerConfig import android.tools.flicker.config.FlickerServiceConfig import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner -import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview +import com.android.wm.shell.scenarios.EnterSplitScreenFromOverview import org.junit.Test import org.junit.runner.RunWith diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt index 1bdea66fc596..01769138ddff 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.flicker +package com.android.wm.shell.flicker import android.tools.Rotation import android.tools.flicker.FlickerConfig @@ -23,7 +23,7 @@ import android.tools.flicker.annotation.FlickerConfigProvider import android.tools.flicker.config.FlickerConfig import android.tools.flicker.config.FlickerServiceConfig import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner -import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview +import com.android.wm.shell.scenarios.EnterSplitScreenFromOverview import org.junit.Test import org.junit.runner.RunWith diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt index bab0c0aa1e6a..1db28dcc4f09 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.flicker +package com.android.wm.shell.flicker import android.tools.Rotation import android.tools.flicker.FlickerConfig @@ -23,7 +23,7 @@ import android.tools.flicker.annotation.FlickerConfigProvider import android.tools.flicker.config.FlickerConfig import android.tools.flicker.config.FlickerServiceConfig import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner -import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider +import com.android.wm.shell.scenarios.SwitchAppByDoubleTapDivider import org.junit.Test import org.junit.runner.RunWith diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt index 17a59ab8a173..c69167bcf67f 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.flicker +package com.android.wm.shell.flicker import android.tools.Rotation import android.tools.flicker.FlickerConfig @@ -23,7 +23,7 @@ import android.tools.flicker.annotation.FlickerConfigProvider import android.tools.flicker.config.FlickerConfig import android.tools.flicker.config.FlickerServiceConfig import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner -import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider +import com.android.wm.shell.scenarios.SwitchAppByDoubleTapDivider import org.junit.Test import org.junit.runner.RunWith diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt index 2c36d647b719..602283a136b3 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.flicker +package com.android.wm.shell.flicker import android.tools.Rotation import android.tools.flicker.FlickerConfig @@ -23,7 +23,7 @@ import android.tools.flicker.annotation.FlickerConfigProvider import android.tools.flicker.config.FlickerConfig import android.tools.flicker.config.FlickerServiceConfig import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner -import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp +import com.android.wm.shell.scenarios.SwitchBackToSplitFromAnotherApp import org.junit.Test import org.junit.runner.RunWith diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt index 6e91d047e64b..7cc14e091540 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.flicker +package com.android.wm.shell.flicker import android.tools.Rotation import android.tools.flicker.FlickerConfig @@ -23,7 +23,7 @@ import android.tools.flicker.annotation.FlickerConfigProvider import android.tools.flicker.config.FlickerConfig import android.tools.flicker.config.FlickerServiceConfig import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner -import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp +import com.android.wm.shell.scenarios.SwitchBackToSplitFromAnotherApp import org.junit.Test import org.junit.runner.RunWith diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt index a921b4663d09..daf6547673af 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.flicker +package com.android.wm.shell.flicker import android.tools.Rotation import android.tools.flicker.FlickerConfig @@ -23,7 +23,7 @@ import android.tools.flicker.annotation.FlickerConfigProvider import android.tools.flicker.config.FlickerConfig import android.tools.flicker.config.FlickerServiceConfig import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner -import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome +import com.android.wm.shell.scenarios.SwitchBackToSplitFromHome import org.junit.Test import org.junit.runner.RunWith diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt index 05f8912f6f47..b0f5e6564b97 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.flicker +package com.android.wm.shell.flicker import android.tools.Rotation import android.tools.flicker.FlickerConfig @@ -23,7 +23,7 @@ import android.tools.flicker.annotation.FlickerConfigProvider import android.tools.flicker.config.FlickerConfig import android.tools.flicker.config.FlickerServiceConfig import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner -import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome +import com.android.wm.shell.scenarios.SwitchBackToSplitFromHome import org.junit.Test import org.junit.runner.RunWith diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt index 1ae1f53b9bc1..88fa783a679d 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.flicker +package com.android.wm.shell.flicker import android.tools.Rotation import android.tools.flicker.FlickerConfig @@ -23,7 +23,7 @@ import android.tools.flicker.annotation.FlickerConfigProvider import android.tools.flicker.config.FlickerConfig import android.tools.flicker.config.FlickerServiceConfig import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner -import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent +import com.android.wm.shell.scenarios.SwitchBackToSplitFromRecent import org.junit.Test import org.junit.runner.RunWith diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt index e14ca550c84d..aa36f44fd499 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.flicker +package com.android.wm.shell.flicker import android.tools.Rotation import android.tools.flicker.FlickerConfig @@ -23,7 +23,7 @@ import android.tools.flicker.annotation.FlickerConfigProvider import android.tools.flicker.config.FlickerConfig import android.tools.flicker.config.FlickerServiceConfig import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner -import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent +import com.android.wm.shell.scenarios.SwitchBackToSplitFromRecent import org.junit.Test import org.junit.runner.RunWith diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt index ce0c4c456587..292f413e0037 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.flicker +package com.android.wm.shell.flicker import android.tools.Rotation import android.tools.flicker.FlickerConfig @@ -23,7 +23,7 @@ import android.tools.flicker.annotation.FlickerConfigProvider import android.tools.flicker.config.FlickerConfig import android.tools.flicker.config.FlickerServiceConfig import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner -import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs +import com.android.wm.shell.scenarios.SwitchBetweenSplitPairs import org.junit.Test import org.junit.runner.RunWith diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt index 5a8d2d51bec4..865958fe82b4 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.flicker +package com.android.wm.shell.flicker import android.tools.Rotation import android.tools.flicker.FlickerConfig @@ -23,7 +23,7 @@ import android.tools.flicker.annotation.FlickerConfigProvider import android.tools.flicker.config.FlickerConfig import android.tools.flicker.config.FlickerServiceConfig import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner -import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs +import com.android.wm.shell.scenarios.SwitchBetweenSplitPairs import org.junit.Test import org.junit.runner.RunWith diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt index d44261549544..6c36e8476cc3 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.flicker +package com.android.wm.shell.flicker import android.tools.flicker.FlickerConfig import android.tools.flicker.annotation.ExpectedScenarios @@ -22,7 +22,7 @@ import android.tools.flicker.annotation.FlickerConfigProvider import android.tools.flicker.config.FlickerConfig import android.tools.flicker.config.FlickerServiceConfig import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner -import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen +import com.android.wm.shell.scenarios.UnlockKeyguardToSplitScreen import org.junit.Test import org.junit.runner.RunWith diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt index ddc8a0697beb..61ccd36dd106 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/src/com/android/server/wm/shell/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.flicker +package com.android.wm.shell.flicker import android.tools.flicker.FlickerConfig import android.tools.flicker.annotation.ExpectedScenarios @@ -22,7 +22,7 @@ import android.tools.flicker.annotation.FlickerConfigProvider import android.tools.flicker.config.FlickerConfig import android.tools.flicker.config.FlickerServiceConfig import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner -import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen +import com.android.wm.shell.scenarios.UnlockKeyguardToSplitScreen import org.junit.Test import org.junit.runner.RunWith diff --git a/libs/WindowManager/Shell/tests/flicker/service/Android.bp b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/Android.bp index a5bc26152d16..90210b1262c7 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/Android.bp +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/Android.bp @@ -26,9 +26,7 @@ package { filegroup { name: "WMShellFlickerServicePlatinumTests-src", srcs: [ - "src/**/platinum/*.kt", - "src/**/scenarios/*.kt", - "src/**/common/*.kt", + "src/**/*.kt", ], } @@ -43,33 +41,42 @@ java_library { ], static_libs: [ "wm-shell-flicker-utils", + "WMShellScenariosSplitScreen", ], } android_test { - name: "WMShellFlickerServiceTests", - defaults: ["WMShellFlickerTestsDefault"], - manifest: "AndroidManifest.xml", - package_name: "com.android.wm.shell.flicker.service", - instrumentation_target_package: "com.android.wm.shell.flicker.service", - test_config_template: "AndroidTestTemplate.xml", - srcs: ["src/**/*.kt"], - static_libs: ["WMShellFlickerTestsBase"], - data: ["trace_config/*"], -} - -android_test { name: "WMShellFlickerServicePlatinumTests", - defaults: ["WMShellFlickerTestsDefault"], + platform_apis: true, + certificate: "platform", + optimize: { + enabled: false, + }, manifest: "AndroidManifest.xml", - package_name: "com.android.wm.shell.flicker.service", - instrumentation_target_package: "com.android.wm.shell.flicker.service", test_config_template: "AndroidTestTemplate.xml", test_suites: [ "device-tests", "device-platinum-tests", ], srcs: [":WMShellFlickerServicePlatinumTests-src"], - static_libs: ["WMShellFlickerTestsBase"], - data: ["trace_config/*"], + static_libs: [ + "WMShellFlickerTestsBase", + "WMShellScenariosSplitScreen", + "WMShellTestUtils", + "ui-trace-collector", + "collector-device-lib", + "wm-shell-flicker-utils", + "androidx.test.ext.junit", + "flickertestapplib", + "flickerlib-helpers", + "flickerlib-trace_processor_shell", + "platform-test-annotations", + "wm-flicker-common-app-helpers", + "launcher-helper-lib", + "launcher-aosp-tapl", + ], + data: [ + ":FlickerTestApp", + "trace_config/*", + ], } diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/AndroidManifest.xml b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/AndroidManifest.xml new file mode 100644 index 000000000000..662e7f346cb3 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/AndroidManifest.xml @@ -0,0 +1,77 @@ +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + package="com.android.wm.shell"> + + <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29"/> + <!-- Read and write traces from external storage --> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> + <!-- Allow the test to write directly to /sdcard/ --> + <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" /> + <!-- Write secure settings --> + <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> + <!-- Capture screen contents --> + <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" /> + <!-- Enable / Disable tracing !--> + <uses-permission android:name="android.permission.DUMP" /> + <!-- Run layers trace --> + <uses-permission android:name="android.permission.HARDWARE_TEST"/> + <!-- Capture screen recording --> + <uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT"/> + <!-- Workaround grant runtime permission exception from b/152733071 --> + <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/> + <uses-permission android:name="android.permission.READ_LOGS"/> + <!-- Force-stop test apps --> + <uses-permission android:name="android.permission.FORCE_STOP_PACKAGES"/> + <!-- Control test app's media session --> + <uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL"/> + <!-- ATM.removeRootTasksWithActivityTypes() --> + <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS" /> + <!-- Enable bubble notification--> + <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" /> + <!-- Allow the test to connect to perfetto trace processor --> + <uses-permission android:name="android.permission.INTERNET"/> + + <!-- Allow the test to write directly to /sdcard/ and connect to trace processor --> + <application android:requestLegacyExternalStorage="true" + android:networkSecurityConfig="@xml/network_security_config" + android:largeHeap="true"> + <uses-library android:name="android.test.runner"/> + + <service android:name=".NotificationListener" + android:exported="true" + android:label="WMShellTestsNotificationListenerService" + android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"> + <intent-filter> + <action android:name="android.service.notification.NotificationListenerService" /> + </intent-filter> + </service> + + <!-- (b/197936012) Remove startup provider due to test timeout issue --> + <provider + android:name="androidx.startup.InitializationProvider" + android:authorities="${applicationId}.androidx-startup" + tools:node="remove" /> + </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.wm.shell" + android:label="WindowManager Flicker Service Tests"> + </instrumentation> +</manifest> diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/AndroidTestTemplate.xml new file mode 100644 index 000000000000..6c903a2e8c42 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/AndroidTestTemplate.xml @@ -0,0 +1,82 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<configuration description="WMShell Platinum Tests {MODULE}"> + <option name="test-tag" value="FlickerTests"/> + <!-- Needed for storing the perfetto trace files in the sdcard/test_results--> + <option name="isolated-storage" value="false"/> + + <target_preparer class="com.android.tradefed.targetprep.DeviceSetup"> + <!-- disable DeprecatedTargetSdk warning --> + <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/> + <!-- keeps the screen on during tests --> + <option name="screen-always-on" value="on"/> + <!-- prevents the phone from restarting --> + <option name="force-skip-system-props" value="true"/> + <!-- set WM tracing verbose level to all --> + <option name="run-command" value="cmd window tracing level all"/> + <!-- set WM tracing to frame (avoid incomplete states) --> + <option name="run-command" value="cmd window tracing frame"/> + <!-- disable betterbug as it's log collection dialogues cause flakes in e2e tests --> + <option name="run-command" value="pm disable com.google.android.internal.betterbug"/> + <!-- ensure lock screen mode is swipe --> + <option name="run-command" value="locksettings set-disabled false"/> + <!-- restart launcher to activate TAPL --> + <option name="run-command" + value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher"/> + <!-- Increase trace size: 20mb for WM and 80mb for SF --> + <option name="run-command" value="cmd window tracing size 20480"/> + <option name="run-command" value="su root service call SurfaceFlinger 1029 i32 81920"/> + </target_preparer> + <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> + <option name="test-user-token" value="%TEST_USER%"/> + <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/> + <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/> + <option name="run-command" value="settings put system show_touches 1"/> + <option name="run-command" value="settings put system pointer_location 1"/> + <option name="teardown-command" + value="settings delete secure show_ime_with_hard_keyboard"/> + <option name="teardown-command" value="settings delete system show_touches"/> + <option name="teardown-command" value="settings delete system pointer_location"/> + <option name="teardown-command" + value="cmd overlay enable com.android.internal.systemui.navbar.gestural"/> + </target_preparer> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true"/> + <option name="test-file-name" value="{MODULE}.apk"/> + <option name="test-file-name" value="FlickerTestApp.apk"/> + </target_preparer> + <!-- Needed for pushing the trace config file --> + <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/> + <test class="com.android.tradefed.testtype.AndroidJUnitTest"> + <option name="package" value="{PACKAGE}"/> + <option name="shell-timeout" value="6600s"/> + <option name="test-timeout" value="6000s"/> + <option name="hidden-api-checks" value="false"/> + <option name="device-listeners" value="android.tools.collectors.DefaultUITraceListener"/> + <!-- DefaultUITraceListener args --> + <option name="instrumentation-arg" key="per_run" value="true"/> + <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/> + </test> + <!-- Needed for pulling the collected trace config on to the host --> + <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> + <option name="pull-pattern-keys" value="perfetto_file_path"/> + <option name="directory-keys" + value="/data/user/0/com.android.wm.shell/files"/> + <option name="collect-on-run-ended-only" value="true"/> + <option name="clean-up" value="true"/> + </metrics_collector> +</configuration> diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/res/xml/network_security_config.xml b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/res/xml/network_security_config.xml new file mode 100644 index 000000000000..4bd9ca049f55 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/res/xml/network_security_config.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<network-security-config> + <domain-config cleartextTrafficPermitted="true"> + <domain includeSubdomains="true">localhost</domain> + </domain-config> +</network-security-config> diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/CopyContentInSplitGesturalNavLandscape.kt index 64293b288a2e..4c2ca6763fc2 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/CopyContentInSplitGesturalNavLandscape.kt @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.platinum +package com.android.wm.shell import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.Rotation -import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit +import com.android.wm.shell.scenarios.CopyContentInSplit import org.junit.Test open class CopyContentInSplitGesturalNavLandscape : CopyContentInSplit(Rotation.ROTATION_90) { diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/CopyContentInSplitGesturalNavPortrait.kt index 517ba2dfc164..0cca31002722 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/CopyContentInSplitGesturalNavPortrait.kt @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.platinum +package com.android.wm.shell import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.Rotation -import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit +import com.android.wm.shell.scenarios.CopyContentInSplit import org.junit.Test open class CopyContentInSplitGesturalNavPortrait : CopyContentInSplit(Rotation.ROTATION_0) { diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/DismissSplitScreenByDividerGesturalNavLandscape.kt index 1bafe3b0898c..7aa62cf906e4 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/DismissSplitScreenByDividerGesturalNavLandscape.kt @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.platinum +package com.android.wm.shell import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.Rotation -import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider +import com.android.wm.shell.scenarios.DismissSplitScreenByDivider import org.junit.Test open class DismissSplitScreenByDividerGesturalNavLandscape : diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/DismissSplitScreenByDividerGesturalNavPortrait.kt index fd0100fd6c21..de11fc6774a2 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/DismissSplitScreenByDividerGesturalNavPortrait.kt @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.platinum +package com.android.wm.shell import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.Rotation -import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider +import com.android.wm.shell.scenarios.DismissSplitScreenByDivider import org.junit.Test open class DismissSplitScreenByDividerGesturalNavPortrait : diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/DismissSplitScreenByGoHomeGesturalNavLandscape.kt index 850b3d8f9867..daa6aac30285 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/DismissSplitScreenByGoHomeGesturalNavLandscape.kt @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.platinum +package com.android.wm.shell import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.Rotation -import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome +import com.android.wm.shell.scenarios.DismissSplitScreenByGoHome import org.junit.Test open class DismissSplitScreenByGoHomeGesturalNavLandscape : diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/DismissSplitScreenByGoHomeGesturalNavPortrait.kt index 0b752bf7f58e..ff57d0057039 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/DismissSplitScreenByGoHomeGesturalNavPortrait.kt @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.platinum +package com.android.wm.shell import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.Rotation -import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome +import com.android.wm.shell.scenarios.DismissSplitScreenByGoHome import org.junit.Test open class DismissSplitScreenByGoHomeGesturalNavPortrait : diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/DragDividerToResizeGesturalNavLandscape.kt index 3c52aa71eb9d..0ac19c8d8452 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/DragDividerToResizeGesturalNavLandscape.kt @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.platinum +package com.android.wm.shell import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.Rotation -import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize +import com.android.wm.shell.scenarios.DragDividerToResize import org.junit.Test open class DragDividerToResizeGesturalNavLandscape : DragDividerToResize(Rotation.ROTATION_90) { diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/DragDividerToResizeGesturalNavPortrait.kt index c2e21b89a480..5713602e7136 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/DragDividerToResizeGesturalNavPortrait.kt @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.platinum +package com.android.wm.shell import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.Rotation -import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize +import com.android.wm.shell.scenarios.DragDividerToResize import org.junit.Test open class DragDividerToResizeGesturalNavPortrait : DragDividerToResize(Rotation.ROTATION_0) { diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt index bf85ab44df5e..d7333f1a4eda 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.platinum +package com.android.wm.shell import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.Rotation -import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps +import com.android.wm.shell.scenarios.EnterSplitScreenByDragFromAllApps import org.junit.Test open class EnterSplitScreenByDragFromAllAppsGesturalNavLandscape : diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt index 0ac4ca20e303..e29a140e4bdf 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.platinum +package com.android.wm.shell import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.Rotation -import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps +import com.android.wm.shell.scenarios.EnterSplitScreenByDragFromAllApps import org.junit.Test open class EnterSplitScreenByDragFromAllAppsGesturalNavPortrait : diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt index 80bd088a192f..9ccccb1da273 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.platinum +package com.android.wm.shell import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.Rotation -import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification +import com.android.wm.shell.scenarios.EnterSplitScreenByDragFromNotification import org.junit.Test open class EnterSplitScreenByDragFromNotificationGesturalNavLandscape : diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt index 0dffb4af8d41..87a4d0847b96 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.platinum +package com.android.wm.shell import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.Rotation -import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification +import com.android.wm.shell.scenarios.EnterSplitScreenByDragFromNotification import org.junit.Test open class EnterSplitScreenByDragFromNotificationGesturalNavPortrait : diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt index b721f2fe294a..559652c1e8a5 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.platinum +package com.android.wm.shell import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.Rotation -import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut +import com.android.wm.shell.scenarios.EnterSplitScreenByDragFromShortcut import org.junit.Test open class EnterSplitScreenByDragFromShortcutGesturalNavLandscape : diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt index 22cbc77d024b..bcb8e0c698af 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.platinum +package com.android.wm.shell import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.Rotation -import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut +import com.android.wm.shell.scenarios.EnterSplitScreenByDragFromShortcut import org.junit.Test open class EnterSplitScreenByDragFromShortcutGesturalNavPortrait : diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt index ac0f9e25285d..39e0fede7fae 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.platinum +package com.android.wm.shell import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.Rotation -import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar +import com.android.wm.shell.scenarios.EnterSplitScreenByDragFromTaskbar import org.junit.Test open class EnterSplitScreenByDragFromTaskbarGesturalNavLandscape : diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt index f7a229d5ff16..643162926f79 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.platinum +package com.android.wm.shell import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.Rotation -import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar +import com.android.wm.shell.scenarios.EnterSplitScreenByDragFromTaskbar import org.junit.Test open class EnterSplitScreenByDragFromTaskbarGesturalNavPortrait : diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/EnterSplitScreenFromOverviewGesturalNavLandscape.kt index 6dbbcb0fcfb5..2093424ea1de 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/EnterSplitScreenFromOverviewGesturalNavLandscape.kt @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.platinum +package com.android.wm.shell import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.Rotation -import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview +import com.android.wm.shell.scenarios.EnterSplitScreenFromOverview import org.junit.Test open class EnterSplitScreenFromOverviewGesturalNavLandscape : diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/EnterSplitScreenFromOverviewGesturalNavPortrait.kt index bd69ea98a67b..f89259d496b2 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/EnterSplitScreenFromOverviewGesturalNavPortrait.kt @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.platinum +package com.android.wm.shell import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.Rotation -import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview +import com.android.wm.shell.scenarios.EnterSplitScreenFromOverview import org.junit.Test open class EnterSplitScreenFromOverviewGesturalNavPortrait : diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt index 404b96fafd24..e5aff0c0800f 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.platinum +package com.android.wm.shell import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.Rotation -import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider +import com.android.wm.shell.scenarios.SwitchAppByDoubleTapDivider import org.junit.Test open class SwitchAppByDoubleTapDividerGesturalNavLandscape : diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt index a79687ddf68f..defade909913 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.platinum +package com.android.wm.shell import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.Rotation -import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider +import com.android.wm.shell.scenarios.SwitchAppByDoubleTapDivider import org.junit.Test open class SwitchAppByDoubleTapDividerGesturalNavPortrait : diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt index b52eb4cd6533..e28deca37b24 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.platinum +package com.android.wm.shell import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.Rotation -import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp +import com.android.wm.shell.scenarios.SwitchBackToSplitFromAnotherApp import org.junit.Test open class SwitchBackToSplitFromAnotherAppGesturalNavLandscape : diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt index d79620c73132..99fb06c20a0b 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.platinum +package com.android.wm.shell import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.Rotation -import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp +import com.android.wm.shell.scenarios.SwitchBackToSplitFromAnotherApp import org.junit.Test open class SwitchBackToSplitFromAnotherAppGesturalNavPortrait : diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/SwitchBackToSplitFromHomeGesturalNavLandscape.kt index d27bfa1a22c9..7045e660b8b8 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/SwitchBackToSplitFromHomeGesturalNavLandscape.kt @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.platinum +package com.android.wm.shell import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.Rotation -import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome +import com.android.wm.shell.scenarios.SwitchBackToSplitFromHome import org.junit.Test open class SwitchBackToSplitFromHomeGesturalNavLandscape : diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/SwitchBackToSplitFromHomeGesturalNavPortrait.kt index 3c7d4d4806cf..b2da052c6209 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/SwitchBackToSplitFromHomeGesturalNavPortrait.kt @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.platinum +package com.android.wm.shell import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.Rotation -import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome +import com.android.wm.shell.scenarios.SwitchBackToSplitFromHome import org.junit.Test open class SwitchBackToSplitFromHomeGesturalNavPortrait : diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/SwitchBackToSplitFromRecentGesturalNavLandscape.kt index 26a2034f16d9..04d7f62ecf53 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/SwitchBackToSplitFromRecentGesturalNavLandscape.kt @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.platinum +package com.android.wm.shell import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.Rotation -import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent +import com.android.wm.shell.scenarios.SwitchBackToSplitFromRecent import org.junit.Test open class SwitchBackToSplitFromRecentGesturalNavLandscape : diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/SwitchBackToSplitFromRecentGesturalNavPortrait.kt index 5154b35ed0e6..bc36fb748226 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/SwitchBackToSplitFromRecentGesturalNavPortrait.kt @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.platinum +package com.android.wm.shell import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.Rotation -import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent +import com.android.wm.shell.scenarios.SwitchBackToSplitFromRecent import org.junit.Test open class SwitchBackToSplitFromRecentGesturalNavPortrait : diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/SwitchBetweenSplitPairsGesturalNavLandscape.kt index 86451c524ee6..ceda4da6a94f 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/SwitchBetweenSplitPairsGesturalNavLandscape.kt @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.platinum +package com.android.wm.shell import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.Rotation -import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs +import com.android.wm.shell.scenarios.SwitchBetweenSplitPairs import org.junit.Test open class SwitchBetweenSplitPairsGesturalNavLandscape : diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/SwitchBetweenSplitPairsGesturalNavPortrait.kt index baf72b40d6d4..365c5cc34d11 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/SwitchBetweenSplitPairsGesturalNavPortrait.kt @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.platinum +package com.android.wm.shell import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.Rotation -import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs +import com.android.wm.shell.scenarios.SwitchBetweenSplitPairs import org.junit.Test open class SwitchBetweenSplitPairsGesturalNavPortrait : diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt index 9caab9b5182a..a8662979407e 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt @@ -14,11 +14,11 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.platinum +package com.android.wm.shell import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit -import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen +import com.android.wm.shell.scenarios.UnlockKeyguardToSplitScreen import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.BlockJUnit4ClassRunner diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt index bf484e5cef98..6d59001374e9 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/src/com/android/wm/shell/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt @@ -14,11 +14,11 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.platinum +package com.android.wm.shell import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit -import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen +import com.android.wm.shell.scenarios.UnlockKeyguardToSplitScreen import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.BlockJUnit4ClassRunner diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/Android.bp b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/Android.bp new file mode 100644 index 000000000000..60c7de7b3931 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/Android.bp @@ -0,0 +1,47 @@ +// +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +java_library { + name: "WMShellScenariosSplitScreen", + platform_apis: true, + optimize: { + enabled: false, + }, + srcs: ["src/**/*.kt"], + static_libs: [ + "WMShellFlickerTestsBase", + "WMShellTestUtils", + "wm-shell-flicker-utils", + "androidx.test.ext.junit", + "flickertestapplib", + "flickerlib-helpers", + "flickerlib-trace_processor_shell", + "platform-test-annotations", + "wm-flicker-common-app-helpers", + "wm-flicker-common-assertions", + "launcher-helper-lib", + "launcher-aosp-tapl", + ], +} diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/CopyContentInSplit.kt index 61710742abb4..ba4654260864 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/CopyContentInSplit.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.scenarios +package com.android.wm.shell.scenarios import android.app.Instrumentation import android.tools.NavBar @@ -23,7 +23,7 @@ import android.tools.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation -import com.android.wm.shell.flicker.service.common.Utils +import com.android.wm.shell.Utils import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.After import org.junit.Before diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DismissSplitScreenByDivider.kt index c1a8ee714abd..d774a31220da 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DismissSplitScreenByDivider.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.scenarios +package com.android.wm.shell.scenarios import android.app.Instrumentation import android.tools.NavBar @@ -23,7 +23,7 @@ import android.tools.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation -import com.android.wm.shell.flicker.service.common.Utils +import com.android.wm.shell.Utils import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.After import org.junit.Before diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DismissSplitScreenByGoHome.kt index 600855a8ab38..5aa161911a9a 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DismissSplitScreenByGoHome.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.scenarios +package com.android.wm.shell.scenarios import android.app.Instrumentation import android.tools.NavBar @@ -23,7 +23,7 @@ import android.tools.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation -import com.android.wm.shell.flicker.service.common.Utils +import com.android.wm.shell.Utils import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.After import org.junit.Before diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DragDividerToResize.kt index b5a6d83afd05..668f3678bb38 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DragDividerToResize.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.scenarios +package com.android.wm.shell.scenarios import android.app.Instrumentation import android.tools.NavBar @@ -23,7 +23,7 @@ import android.tools.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation -import com.android.wm.shell.flicker.service.common.Utils +import com.android.wm.shell.Utils import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.After import org.junit.Before diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/EnterSplitScreenByDragFromAllApps.kt index a189325d52ea..06c7b9bc384d 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/EnterSplitScreenByDragFromAllApps.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.scenarios +package com.android.wm.shell.scenarios import android.app.Instrumentation import android.tools.NavBar @@ -24,7 +24,7 @@ import android.tools.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation -import com.android.wm.shell.flicker.service.common.Utils +import com.android.wm.shell.Utils import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.After import org.junit.Assume diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/EnterSplitScreenByDragFromNotification.kt index bcd0f126daef..96b22bf86742 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/EnterSplitScreenByDragFromNotification.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.scenarios +package com.android.wm.shell.scenarios import android.app.Instrumentation import android.tools.NavBar @@ -25,7 +25,7 @@ import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation import com.android.server.wm.flicker.helpers.MultiWindowUtils -import com.android.wm.shell.flicker.service.common.Utils +import com.android.wm.shell.Utils import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.After import org.junit.Assume diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/EnterSplitScreenByDragFromShortcut.kt index 3f07be083041..9e05b630d840 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/EnterSplitScreenByDragFromShortcut.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.scenarios +package com.android.wm.shell.scenarios import android.app.Instrumentation import android.tools.NavBar @@ -24,7 +24,7 @@ import android.tools.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation -import com.android.wm.shell.flicker.service.common.Utils +import com.android.wm.shell.Utils import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.After import org.junit.Assume diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/EnterSplitScreenByDragFromTaskbar.kt index 532801357d60..90900557e582 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/EnterSplitScreenByDragFromTaskbar.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.scenarios +package com.android.wm.shell.scenarios import android.app.Instrumentation import android.tools.NavBar @@ -23,7 +23,7 @@ import android.tools.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation -import com.android.wm.shell.flicker.service.common.Utils +import com.android.wm.shell.Utils import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.After import org.junit.Assume diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/EnterSplitScreenFromOverview.kt index be4035d6af7f..d5cc92e5d268 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/EnterSplitScreenFromOverview.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.scenarios +package com.android.wm.shell.scenarios import android.app.Instrumentation import android.tools.NavBar @@ -23,7 +23,7 @@ import android.tools.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation -import com.android.wm.shell.flicker.service.common.Utils +import com.android.wm.shell.Utils import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.After import org.junit.Before diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchAppByDoubleTapDivider.kt index 2406bdeebdf2..26203d4afccd 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchAppByDoubleTapDivider.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.scenarios +package com.android.wm.shell.scenarios import android.app.Instrumentation import android.graphics.Point @@ -25,7 +25,7 @@ import android.tools.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation -import com.android.wm.shell.flicker.service.common.Utils +import com.android.wm.shell.Utils import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.After import org.junit.Before diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBackToSplitFromAnotherApp.kt index de26982501a3..2ccffa85b5c1 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBackToSplitFromAnotherApp.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.scenarios +package com.android.wm.shell.scenarios import android.app.Instrumentation import android.tools.NavBar @@ -23,7 +23,7 @@ import android.tools.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation -import com.android.wm.shell.flicker.service.common.Utils +import com.android.wm.shell.Utils import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.After import org.junit.Before diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBackToSplitFromHome.kt index 873b0199f0e8..8673c464ad19 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBackToSplitFromHome.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.scenarios +package com.android.wm.shell.scenarios import android.app.Instrumentation import android.tools.NavBar @@ -23,7 +23,7 @@ import android.tools.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation -import com.android.wm.shell.flicker.service.common.Utils +import com.android.wm.shell.Utils import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.After import org.junit.Before diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBackToSplitFromRecent.kt index 15934d0f3944..c7cbc3e44553 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBackToSplitFromRecent.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.scenarios +package com.android.wm.shell.scenarios import android.app.Instrumentation import android.tools.NavBar @@ -23,7 +23,7 @@ import android.tools.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation -import com.android.wm.shell.flicker.service.common.Utils +import com.android.wm.shell.Utils import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.After import org.junit.Before diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBetweenSplitPairs.kt index 79e69ae084f4..4ded148f6113 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBetweenSplitPairs.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.scenarios +package com.android.wm.shell.scenarios import android.app.Instrumentation import android.tools.NavBar @@ -23,7 +23,7 @@ import android.tools.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation -import com.android.wm.shell.flicker.service.common.Utils +import com.android.wm.shell.Utils import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.After import org.junit.Before diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/UnlockKeyguardToSplitScreen.kt index 0f932d46d3d3..7b062fcc6b5a 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/UnlockKeyguardToSplitScreen.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.splitscreen.scenarios +package com.android.wm.shell.scenarios import android.app.Instrumentation import android.tools.NavBar @@ -23,7 +23,7 @@ import android.tools.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation -import com.android.wm.shell.flicker.service.common.Utils +import com.android.wm.shell.Utils import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.After import org.junit.Before diff --git a/libs/WindowManager/Shell/tests/e2e/utils/Android.bp b/libs/WindowManager/Shell/tests/e2e/utils/Android.bp new file mode 100644 index 000000000000..51d9c401dfba --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/utils/Android.bp @@ -0,0 +1,31 @@ +// +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_team: "trendy_team_windowing_tools", + default_applicable_licenses: ["frameworks_base_license"], +} + +java_library { + name: "WMShellTestUtils", + srcs: ["src/**/*.kt"], + static_libs: ["WMShellFlickerTestsBase"], +} diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/common/Utils.kt b/libs/WindowManager/Shell/tests/e2e/utils/src/com/android/wm/shell/Utils.kt index 4c6c6cce0105..dee67f3f2e0e 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/common/Utils.kt +++ b/libs/WindowManager/Shell/tests/e2e/utils/src/com/android/wm/shell/Utils.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.common +package com.android.wm.shell import android.app.Instrumentation import android.platform.test.rule.NavigationModeRule diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt index 2ee53f4fce66..d7ea9f3a8fa4 100644 --- a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt @@ -100,14 +100,14 @@ class OpenActivityFromBubbleOnLocksreenTest(flicker: LegacyFlickerTest) : @Postsubmit @Test fun navBarLayerIsVisibleAtEnd() { - Assume.assumeFalse(flicker.scenario.isTablet) + Assume.assumeFalse(usesTaskbar) flicker.navBarLayerIsVisibleAtEnd() } @Postsubmit @Test fun navBarLayerPositionAtEnd() { - Assume.assumeFalse(flicker.scenario.isTablet) + Assume.assumeFalse(usesTaskbar) flicker.navBarLayerPositionAtEnd() } @@ -154,7 +154,7 @@ class OpenActivityFromBubbleOnLocksreenTest(flicker: LegacyFlickerTest) : @Postsubmit @Test fun taskBarLayerIsVisibleAtEnd() { - Assume.assumeTrue(flicker.scenario.isTablet) + Assume.assumeTrue(usesTaskbar) flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.TASK_BAR) } } } diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt index a5e0550d9c79..3ffc9d7b87f6 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt @@ -21,6 +21,7 @@ import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest import android.tools.traces.component.ComponentNameMatcher +import com.android.wm.shell.Flags import com.android.wm.shell.flicker.pip.common.ClosePipTransition import org.junit.FixMethodOrder import org.junit.Test @@ -60,7 +61,7 @@ class ClosePipBySwipingDownTest(flicker: LegacyFlickerTest) : ClosePipTransition val pipCenterY = pipRegion.centerY() val displayCenterX = device.displayWidth / 2 val barComponent = - if (flicker.scenario.isTablet) { + if (flicker.scenario.isTablet || Flags.enableTaskbarOnPhones()) { ComponentNameMatcher.TASK_BAR } else { ComponentNameMatcher.NAV_BAR diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt index 3a0eeb67995b..68fa7c7af740 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt @@ -103,7 +103,7 @@ open class NetflixEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransit @Postsubmit @Test override fun taskBarLayerIsVisibleAtStartAndEnd() { - Assume.assumeTrue(flicker.scenario.isTablet) + Assume.assumeTrue(usesTaskbar) // Netflix starts in immersive fullscreen mode, so taskbar bar is not visible at start flicker.assertLayersStart { this.isInvisible(ComponentNameMatcher.TASK_BAR) } flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.TASK_BAR) } diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt index 5c539a5e8a03..72be3d85ec8b 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt @@ -88,7 +88,7 @@ open class YouTubeEnterPipToOtherOrientationTest(flicker: LegacyFlickerTest) : @Postsubmit @Test override fun taskBarLayerIsVisibleAtStartAndEnd() { - Assume.assumeTrue(flicker.scenario.isTablet) + Assume.assumeTrue(usesTaskbar) // YouTube starts in immersive fullscreen mode, so taskbar bar is not visible at start flicker.assertLayersStart { this.isInvisible(ComponentNameMatcher.TASK_BAR) } flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.TASK_BAR) } diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/CloseAllAppsWithAppHeaderExitTest.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/CloseAllAppsWithAppHeaderExitTest.kt deleted file mode 100644 index 9ba3a45fb5b7..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/CloseAllAppsWithAppHeaderExitTest.kt +++ /dev/null @@ -1,34 +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.flicker.service.desktopmode.functional - -import android.platform.test.annotations.Postsubmit -import com.android.wm.shell.flicker.service.desktopmode.scenarios.CloseAllAppsWithAppHeaderExit -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.BlockJUnit4ClassRunner - -/** Functional test for [CloseAllAppsWithAppHeaderExit]. */ -@RunWith(BlockJUnit4ClassRunner::class) -@Postsubmit -open class CloseAllAppsWithAppHeaderExitTest : CloseAllAppsWithAppHeaderExit() { - - @Test - override fun closeAllAppsInDesktop() { - super.closeAllAppsInDesktop() - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/DragAppWindowSingleWindowTest.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/DragAppWindowSingleWindowTest.kt deleted file mode 100644 index d8b93482854a..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/DragAppWindowSingleWindowTest.kt +++ /dev/null @@ -1,34 +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.flicker.service.desktopmode.functional - -import android.platform.test.annotations.Postsubmit -import com.android.wm.shell.flicker.service.desktopmode.scenarios.DragAppWindowSingleWindow -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.BlockJUnit4ClassRunner - -/** Functional test for [DragAppWindowSingleWindow]. */ -@RunWith(BlockJUnit4ClassRunner::class) -@Postsubmit -open class DragAppWindowSingleWindowTest : DragAppWindowSingleWindow() -{ - @Test - override fun dragAppWindow() { - super.dragAppWindow() - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/EnterDesktopWithAppHandleMenuTest.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/EnterDesktopWithAppHandleMenuTest.kt deleted file mode 100644 index 546ce2d10a7d..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/EnterDesktopWithAppHandleMenuTest.kt +++ /dev/null @@ -1,33 +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.flicker.service.desktopmode.functional - -import android.platform.test.annotations.Postsubmit -import com.android.wm.shell.flicker.service.desktopmode.scenarios.EnterDesktopWithAppHandleMenu -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.BlockJUnit4ClassRunner - -/** Functional test for [EnterDesktopWithAppHandleMenu]. */ -@RunWith(BlockJUnit4ClassRunner::class) -@Postsubmit -open class EnterDesktopWithAppHandleMenuTest : EnterDesktopWithAppHandleMenu() { - @Test - override fun enterDesktopWithAppHandleMenu() { - super.enterDesktopWithAppHandleMenu() - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/EnterDesktopWithDragTest.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/EnterDesktopWithDragTest.kt deleted file mode 100644 index b5fdb168c8ed..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/EnterDesktopWithDragTest.kt +++ /dev/null @@ -1,35 +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.flicker.service.desktopmode.functional - -import android.platform.test.annotations.Postsubmit -import android.tools.Rotation -import com.android.wm.shell.flicker.service.desktopmode.scenarios.EnterDesktopWithDrag -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.BlockJUnit4ClassRunner - -/** Functional test for [EnterDesktopWithDrag]. */ -@RunWith(BlockJUnit4ClassRunner::class) -@Postsubmit -open class EnterDesktopWithDragTest : EnterDesktopWithDrag(Rotation.ROTATION_0) { - - @Test - override fun enterDesktopWithDrag() { - super.enterDesktopWithDrag() - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/ExitDesktopWithDragToTopDragZoneTest.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/ExitDesktopWithDragToTopDragZoneTest.kt deleted file mode 100644 index 8f802d245275..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/ExitDesktopWithDragToTopDragZoneTest.kt +++ /dev/null @@ -1,35 +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.flicker.service.desktopmode.functional - -import android.platform.test.annotations.Postsubmit -import android.tools.Rotation -import com.android.wm.shell.flicker.service.desktopmode.scenarios.ExitDesktopWithDragToTopDragZone -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.BlockJUnit4ClassRunner - -/** Functional test for [ExitDesktopWithDragToTopDragZone]. */ -@RunWith(BlockJUnit4ClassRunner::class) -@Postsubmit -open class ExitDesktopWithDragToTopDragZoneTest : - ExitDesktopWithDragToTopDragZone(Rotation.ROTATION_0) { - @Test - override fun exitDesktopWithDragToTopDragZone() { - super.exitDesktopWithDragToTopDragZone() - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/MinimizeWindowOnAppOpenTest.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/MinimizeWindowOnAppOpenTest.kt deleted file mode 100644 index 63c428a0451f..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/MinimizeWindowOnAppOpenTest.kt +++ /dev/null @@ -1,36 +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.flicker.service.desktopmode.functional - -import android.platform.test.annotations.Postsubmit -import com.android.wm.shell.flicker.service.desktopmode.scenarios.MinimizeWindowOnAppOpen -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.BlockJUnit4ClassRunner - -/** Functional test for [MinimizeWindowOnAppOpen]. */ -@RunWith(BlockJUnit4ClassRunner::class) -@Postsubmit -open class MinimizeWindowOnAppOpenTest : MinimizeWindowOnAppOpen() -{ - @Test - override fun openAppToMinimizeWindow() { - // Launch a new app while 4 apps are already open on desktop. This should result in the - // first app we opened to be minimized. - super.openAppToMinimizeWindow() - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/ResizeAppWithCornerResizeTest.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/ResizeAppWithCornerResizeTest.kt deleted file mode 100644 index 4797aaf553af..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/ResizeAppWithCornerResizeTest.kt +++ /dev/null @@ -1,34 +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.flicker.service.desktopmode.functional - -import android.platform.test.annotations.Postsubmit -import android.tools.Rotation -import com.android.wm.shell.flicker.service.desktopmode.scenarios.ResizeAppWithCornerResize -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.BlockJUnit4ClassRunner - -/** Functional test for [ResizeAppWithCornerResize]. */ -@RunWith(BlockJUnit4ClassRunner::class) -@Postsubmit -open class ResizeAppWithCornerResizeTest : ResizeAppWithCornerResize(Rotation.ROTATION_0) { - @Test - override fun resizeAppWithCornerResize() { - super.resizeAppWithCornerResize() - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/SwitchToOverviewFromDesktopTest.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/SwitchToOverviewFromDesktopTest.kt deleted file mode 100644 index 9a7136103cd4..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/SwitchToOverviewFromDesktopTest.kt +++ /dev/null @@ -1,33 +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.flicker.service.desktopmode.functional - -import android.platform.test.annotations.Postsubmit -import com.android.wm.shell.flicker.service.desktopmode.scenarios.SwitchToOverviewFromDesktop -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.BlockJUnit4ClassRunner - -/** Functional test for [SwitchToOverviewFromDesktop]. */ -@RunWith(BlockJUnit4ClassRunner::class) -@Postsubmit -open class SwitchToOverviewFromDesktopTest : SwitchToOverviewFromDesktop() { - @Test - override fun switchToOverview() { - super.switchToOverview() - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/ICommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/ICommonAssertions.kt index 4465a16a8e0f..acaf021981ed 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/ICommonAssertions.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/ICommonAssertions.kt @@ -28,12 +28,16 @@ import com.android.server.wm.flicker.statusBarLayerPositionAtStartAndEnd import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible import com.android.server.wm.flicker.taskBarLayerIsVisibleAtStartAndEnd import com.android.server.wm.flicker.taskBarWindowIsAlwaysVisible +import com.android.wm.shell.Flags import org.junit.Assume import org.junit.Test interface ICommonAssertions { val flicker: LegacyFlickerTest + val usesTaskbar: Boolean + get() = flicker.scenario.isTablet || Flags.enableTaskbarOnPhones() + /** Checks that all parts of the screen are covered during the transition */ @Presubmit @Test fun entireScreenCovered() = flicker.entireScreenCovered() @@ -43,7 +47,7 @@ interface ICommonAssertions { @Presubmit @Test fun navBarLayerIsVisibleAtStartAndEnd() { - Assume.assumeFalse(flicker.scenario.isTablet) + Assume.assumeFalse(usesTaskbar) flicker.navBarLayerIsVisibleAtStartAndEnd() } @@ -54,7 +58,7 @@ interface ICommonAssertions { @Presubmit @Test fun navBarLayerPositionAtStartAndEnd() { - Assume.assumeFalse(flicker.scenario.isTablet) + Assume.assumeFalse(usesTaskbar) flicker.navBarLayerPositionAtStartAndEnd() } @@ -66,7 +70,7 @@ interface ICommonAssertions { @Presubmit @Test fun navBarWindowIsAlwaysVisible() { - Assume.assumeFalse(flicker.scenario.isTablet) + Assume.assumeFalse(usesTaskbar) flicker.navBarWindowIsAlwaysVisible() } @@ -76,7 +80,7 @@ interface ICommonAssertions { @Presubmit @Test fun taskBarLayerIsVisibleAtStartAndEnd() { - Assume.assumeTrue(flicker.scenario.isTablet) + Assume.assumeTrue(usesTaskbar) flicker.taskBarLayerIsVisibleAtStartAndEnd() } @@ -88,7 +92,7 @@ interface ICommonAssertions { @Presubmit @Test fun taskBarWindowIsAlwaysVisible() { - Assume.assumeTrue(flicker.scenario.isTablet) + Assume.assumeTrue(usesTaskbar) flicker.taskBarWindowIsAlwaysVisible() } diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp index a0408652a29b..4d761e18b990 100644 --- a/libs/WindowManager/Shell/tests/unittest/Android.bp +++ b/libs/WindowManager/Shell/tests/unittest/Android.bp @@ -44,6 +44,8 @@ android_test { "androidx.test.runner", "androidx.test.rules", "androidx.test.ext.junit", + "androidx.datastore_datastore", + "kotlinx_coroutines_test", "androidx.dynamicanimation_dynamicanimation", "dagger2", "frameworks-base-testutils", diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationDatastoreRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationDatastoreRepositoryTest.kt new file mode 100644 index 000000000000..4d407387d323 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationDatastoreRepositoryTest.kt @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.desktopmode.education + +import android.content.Context +import android.testing.AndroidTestingRunner +import androidx.datastore.core.DataStore +import androidx.datastore.core.DataStoreFactory +import androidx.datastore.dataStoreFile +import androidx.test.core.app.ApplicationProvider +import androidx.test.filters.SmallTest +import androidx.test.platform.app.InstrumentationRegistry +import com.android.wm.shell.desktopmode.education.data.AppHandleEducationDatastoreRepository +import com.android.wm.shell.desktopmode.education.data.WindowingEducationProto +import com.google.common.truth.Truth.assertThat +import java.io.File +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.test.setMain +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@ExperimentalCoroutinesApi +class AppHandleEducationDatastoreRepositoryTest { + private val testContext: Context = InstrumentationRegistry.getInstrumentation().targetContext + private lateinit var testDatastore: DataStore<WindowingEducationProto> + private lateinit var datastoreRepository: AppHandleEducationDatastoreRepository + private lateinit var datastoreScope: CoroutineScope + + @Before + fun setUp() { + Dispatchers.setMain(StandardTestDispatcher()) + datastoreScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob()) + testDatastore = + DataStoreFactory.create( + serializer = + AppHandleEducationDatastoreRepository.Companion.WindowingEducationProtoSerializer, + scope = datastoreScope) { + testContext.dataStoreFile(APP_HANDLE_EDUCATION_DATASTORE_TEST_FILE) + } + datastoreRepository = AppHandleEducationDatastoreRepository(testDatastore) + } + + @After + fun tearDown() { + File(ApplicationProvider.getApplicationContext<Context>().filesDir, "datastore") + .deleteRecursively() + + datastoreScope.cancel() + } + + @Test + fun getWindowingEducationProto_returnsCorrectProto() = + runTest(StandardTestDispatcher()) { + val windowingEducationProto = + createWindowingEducationProto( + educationViewedTimestampMillis = 123L, + featureUsedTimestampMillis = 124L, + appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 2), + appUsageStatsLastUpdateTimestampMillis = 125L) + testDatastore.updateData { windowingEducationProto } + + val resultProto = datastoreRepository.windowingEducationProto() + + assertThat(resultProto).isEqualTo(windowingEducationProto) + } + + private fun createWindowingEducationProto( + educationViewedTimestampMillis: Long? = null, + featureUsedTimestampMillis: Long? = null, + appUsageStats: Map<String, Int>? = null, + appUsageStatsLastUpdateTimestampMillis: Long? = null + ): WindowingEducationProto = + WindowingEducationProto.newBuilder() + .apply { + if (educationViewedTimestampMillis != null) + setEducationViewedTimestampMillis(educationViewedTimestampMillis) + if (featureUsedTimestampMillis != null) + setFeatureUsedTimestampMillis(featureUsedTimestampMillis) + setAppHandleEducation( + createAppHandleEducationProto( + appUsageStats, appUsageStatsLastUpdateTimestampMillis)) + } + .build() + + private fun createAppHandleEducationProto( + appUsageStats: Map<String, Int>? = null, + appUsageStatsLastUpdateTimestampMillis: Long? = null + ): WindowingEducationProto.AppHandleEducation = + WindowingEducationProto.AppHandleEducation.newBuilder() + .apply { + if (appUsageStats != null) putAllAppUsageStats(appUsageStats) + if (appUsageStatsLastUpdateTimestampMillis != null) + setAppUsageStatsLastUpdateTimestampMillis(appUsageStatsLastUpdateTimestampMillis) + } + .build() + + companion object { + private const val GMAIL_PACKAGE_NAME = "com.google.android.gm" + private const val APP_HANDLE_EDUCATION_DATASTORE_TEST_FILE = "app_handle_education_test.pb" + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java index 22b408cda0af..1990fe7946e9 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java @@ -219,7 +219,7 @@ public class SplitTransitionTests extends ShellTestCase { @Test @UiThreadTest - public void testRemoteTransitionConsumed() { + public void testRemoteTransitionConsumedForStartAnimation() { // Omit side child change TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0) .addChange(TRANSIT_OPEN, mMainChild) @@ -238,7 +238,30 @@ public class SplitTransitionTests extends ShellTestCase { assertTrue(accepted); assertTrue(testRemote.isConsumed()); + } + + @Test + @UiThreadTest + public void testRemoteTransitionConsumed() { + // Omit side child change + TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0) + .addChange(TRANSIT_OPEN, mMainChild) + .build(); + TestRemoteTransition testRemote = new TestRemoteTransition(); + IBinder transition = mSplitScreenTransitions.startEnterTransition( + TRANSIT_OPEN, new WindowContainerTransaction(), + new RemoteTransition(testRemote, "Test"), mStageCoordinator, + TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false); + mMainStage.onTaskAppeared(mMainChild, createMockSurface()); + mStageCoordinator.startAnimation(transition, info, + mock(SurfaceControl.Transaction.class), + mock(SurfaceControl.Transaction.class), + mock(Transitions.TransitionFinishCallback.class)); + mStageCoordinator.onTransitionConsumed(transition, false /*aborted*/, + mock(SurfaceControl.Transaction.class)); + + assertTrue(testRemote.isConsumed()); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt index 6d68797b4430..fa905e2e5c37 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt @@ -57,6 +57,7 @@ import android.view.SurfaceView import android.view.View import android.view.WindowInsets.Type.statusBars import android.window.WindowContainerTransaction +import android.window.WindowContainerTransaction.HierarchyOp import androidx.test.filters.SmallTest import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession @@ -356,6 +357,36 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { } @Test + fun testCloseButtonInFreeform() { + val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM) + val windowDecor = setUpMockDecorationForTask(task) + + onTaskOpening(task) + val onClickListenerCaptor = argumentCaptor<View.OnClickListener>() + verify(windowDecor).setCaptionListeners( + onClickListenerCaptor.capture(), any(), any(), any()) + + val onClickListener = onClickListenerCaptor.firstValue + val view = mock(View::class.java) + whenever(view.id).thenReturn(R.id.close_window) + + val freeformTaskTransitionStarter = mock(FreeformTaskTransitionStarter::class.java) + desktopModeWindowDecorViewModel + .setFreeformTaskTransitionStarter(freeformTaskTransitionStarter) + + onClickListener.onClick(view) + + val transactionCaptor = argumentCaptor<WindowContainerTransaction>() + verify(freeformTaskTransitionStarter).startRemoveTransition(transactionCaptor.capture()) + val wct = transactionCaptor.firstValue + + assertEquals(1, wct.getHierarchyOps().size) + assertEquals(HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_TASK, + wct.getHierarchyOps().get(0).getType()) + assertEquals(task.token.asBinder(), wct.getHierarchyOps().get(0).getContainer()) + } + + @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) fun testDecorationIsCreatedForTopTranslucentActivitiesWithStyleFloating() { val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true).apply { diff --git a/libs/androidfw/BigBuffer.cpp b/libs/androidfw/BigBuffer.cpp index bedfc49a1b0d..43b56c32fb79 100644 --- a/libs/androidfw/BigBuffer.cpp +++ b/libs/androidfw/BigBuffer.cpp @@ -17,8 +17,8 @@ #include <androidfw/BigBuffer.h> #include <algorithm> +#include <iterator> #include <memory> -#include <vector> #include "android-base/logging.h" @@ -78,10 +78,27 @@ void* BigBuffer::NextBlock(size_t* out_size) { std::string BigBuffer::to_string() const { std::string result; + result.reserve(size_); for (const Block& block : blocks_) { result.append(block.buffer.get(), block.buffer.get() + block.size); } return result; } +void BigBuffer::AppendBuffer(BigBuffer&& buffer) { + std::move(buffer.blocks_.begin(), buffer.blocks_.end(), std::back_inserter(blocks_)); + size_ += buffer.size_; + buffer.blocks_.clear(); + buffer.size_ = 0; +} + +void BigBuffer::BackUp(size_t count) { + Block& block = blocks_.back(); + block.size -= count; + size_ -= count; + // BigBuffer is supposed to always give zeroed memory, but backing up usually means + // something has been already written into the block. Erase it. + std::fill_n(block.buffer.get() + block.size, count, 0); +} + } // namespace android diff --git a/libs/androidfw/include/androidfw/BigBuffer.h b/libs/androidfw/include/androidfw/BigBuffer.h index b99a4edf9d88..c4cd7c576542 100644 --- a/libs/androidfw/include/androidfw/BigBuffer.h +++ b/libs/androidfw/include/androidfw/BigBuffer.h @@ -14,13 +14,12 @@ * limitations under the License. */ -#ifndef _ANDROID_BIG_BUFFER_H -#define _ANDROID_BIG_BUFFER_H +#pragma once -#include <cstring> #include <memory> #include <string> #include <type_traits> +#include <utility> #include <vector> #include "android-base/logging.h" @@ -150,24 +149,11 @@ inline size_t BigBuffer::block_size() const { template <typename T> inline T* BigBuffer::NextBlock(size_t count) { - static_assert(std::is_standard_layout<T>::value, "T must be standard_layout type"); + static_assert(std::is_standard_layout_v<T>, "T must be standard_layout type"); CHECK(count != 0); return reinterpret_cast<T*>(NextBlockImpl(sizeof(T) * count)); } -inline void BigBuffer::BackUp(size_t count) { - Block& block = blocks_.back(); - block.size -= count; - size_ -= count; -} - -inline void BigBuffer::AppendBuffer(BigBuffer&& buffer) { - std::move(buffer.blocks_.begin(), buffer.blocks_.end(), std::back_inserter(blocks_)); - size_ += buffer.size_; - buffer.blocks_.clear(); - buffer.size_ = 0; -} - inline void BigBuffer::Pad(size_t bytes) { NextBlock<char>(bytes); } @@ -188,5 +174,3 @@ inline BigBuffer::const_iterator BigBuffer::end() const { } } // namespace android - -#endif // _ANDROID_BIG_BUFFER_H diff --git a/libs/androidfw/tests/BigBuffer_test.cpp b/libs/androidfw/tests/BigBuffer_test.cpp index 382d21e20846..7e38f1758057 100644 --- a/libs/androidfw/tests/BigBuffer_test.cpp +++ b/libs/androidfw/tests/BigBuffer_test.cpp @@ -98,4 +98,20 @@ TEST(BigBufferTest, PadAndAlignProperly) { ASSERT_EQ(8u, buffer.size()); } +TEST(BigBufferTest, BackUpZeroed) { + BigBuffer buffer(16); + + auto block = buffer.NextBlock<char>(2); + ASSERT_TRUE(block != nullptr); + ASSERT_EQ(2u, buffer.size()); + block[0] = 0x01; + block[1] = 0x02; + buffer.BackUp(1); + ASSERT_EQ(1u, buffer.size()); + auto new_block = buffer.NextBlock<char>(1); + ASSERT_TRUE(new_block != nullptr); + ASSERT_EQ(2u, buffer.size()); + ASSERT_EQ(0, *new_block); +} + } // namespace android diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index d71f3b6884ae..23cd3ce965ff 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -143,6 +143,7 @@ cc_defaults { "aconfig_text_flags_c_lib", "server_configurable_flags", "libaconfig_storage_read_api_cc", + "libgraphicsenv", ], static_libs: [ "libEGL_blobCache", diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp index 1217b47664dd..b6476c9d466f 100644 --- a/libs/hwui/Properties.cpp +++ b/libs/hwui/Properties.cpp @@ -42,6 +42,11 @@ constexpr bool hdr_10bit_plus() { constexpr bool initialize_gl_always() { return false; } + +constexpr bool skip_eglmanager_telemetry() { + return false; +} + constexpr bool resample_gainmap_regions() { return false; } @@ -103,6 +108,7 @@ float Properties::maxHdrHeadroomOn8bit = 5.f; // TODO: Refine this number bool Properties::clipSurfaceViews = false; bool Properties::hdr10bitPlus = false; +bool Properties::skipTelemetry = false; bool Properties::resampleGainmapRegions = false; int Properties::timeoutMultiplier = 1; @@ -183,6 +189,8 @@ bool Properties::load() { hwui_flags::resample_gainmap_regions()); timeoutMultiplier = android::base::GetIntProperty("ro.hw_timeout_multiplier", 1); + skipTelemetry = base::GetBoolProperty(PROPERTY_SKIP_EGLMANAGER_TELEMETRY, + hwui_flags::skip_eglmanager_telemetry()); return (prevDebugLayersUpdates != debugLayersUpdates) || (prevDebugOverdraw != debugOverdraw); } diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h index 73e80ce4afd0..db471527b861 100644 --- a/libs/hwui/Properties.h +++ b/libs/hwui/Properties.h @@ -234,6 +234,8 @@ enum DebugLevel { */ #define PROPERTY_INITIALIZE_GL_ALWAYS "debug.hwui.initialize_gl_always" +#define PROPERTY_SKIP_EGLMANAGER_TELEMETRY "debug.hwui.skip_eglmanager_telemetry" + /////////////////////////////////////////////////////////////////////////////// // Misc /////////////////////////////////////////////////////////////////////////////// @@ -342,6 +344,7 @@ public: static bool clipSurfaceViews; static bool hdr10bitPlus; + static bool skipTelemetry; static bool resampleGainmapRegions; static int timeoutMultiplier; diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig index 13c0b00daa21..a1f51687b077 100644 --- a/libs/hwui/aconfig/hwui_flags.aconfig +++ b/libs/hwui/aconfig/hwui_flags.aconfig @@ -99,6 +99,13 @@ flag { } flag { + name: "skip_eglmanager_telemetry" + namespace: "core_graphics" + description: "Skip telemetry in EglManager's calls to eglCreateContext to avoid polluting telemetry" + bug: "347911216" +} + +flag { name: "resample_gainmap_regions" namespace: "core_graphics" description: "Resample gainmaps when decoding regions, to improve visual quality" diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp index 708b0113e13e..60104452f4c0 100644 --- a/libs/hwui/renderthread/EglManager.cpp +++ b/libs/hwui/renderthread/EglManager.cpp @@ -19,6 +19,7 @@ #include <EGL/eglext.h> #include <GLES/gl.h> #include <cutils/properties.h> +#include <graphicsenv/GpuStatsInfo.h> #include <log/log.h> #include <sync/sync.h> #include <utils/Trace.h> @@ -366,6 +367,10 @@ void EglManager::createContext() { contextAttributes.push_back(EGL_CONTEXT_PRIORITY_LEVEL_IMG); contextAttributes.push_back(Properties::contextPriority); } + if (Properties::skipTelemetry) { + contextAttributes.push_back(EGL_TELEMETRY_HINT_ANDROID); + contextAttributes.push_back(android::GpuStatsInfo::SKIP_TELEMETRY); + } contextAttributes.push_back(EGL_NONE); mEglContext = eglCreateContext( mEglDisplay, EglExtensions.noConfigContext ? ((EGLConfig) nullptr) : mEglConfig, diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java index f28c2c17167f..2c71ee01b3f1 100644 --- a/media/java/android/media/tv/tuner/Tuner.java +++ b/media/java/android/media/tv/tuner/Tuner.java @@ -288,6 +288,7 @@ public class Tuner implements AutoCloseable { private static int sTunerVersion = TunerVersionChecker.TUNER_VERSION_UNKNOWN; private DemuxInfo mDesiredDemuxInfo = new DemuxInfo(Filter.TYPE_UNDEFINED); + private boolean mClosed = false; private Frontend mFrontend; private EventHandler mHandler; @Nullable @@ -813,6 +814,9 @@ public class Tuner implements AutoCloseable { */ @Override public void close() { + if (mClosed) { + return; + } acquireTRMSLock("close()"); try { releaseAll(); @@ -820,6 +824,7 @@ public class Tuner implements AutoCloseable { TunerUtils.throwExceptionForResult(nativeClose(), "failed to close tuner"); } finally { releaseTRMSLock(); + mClosed = true; } } @@ -888,19 +893,14 @@ public class Tuner implements AutoCloseable { try { if (mFrontendCiCamHandle != null) { if (DEBUG) { - Log.d(TAG, "unlinking CiCam : " + mFrontendCiCamHandle + " for " + mClientId); - } - int result = nativeUnlinkCiCam(mFrontendCiCamId); - if (result == RESULT_SUCCESS) { - mTunerResourceManager.releaseCiCam(mFrontendCiCamHandle, mClientId); - replicateCiCamSettings(null); - } else { - Log.e(TAG, "nativeUnlinkCiCam(" + mFrontendCiCamHandle + ") for mClientId:" - + mClientId + "failed with result:" + result); + Log.d(TAG, "releasing CiCam : " + mFrontendCiCamHandle + " for " + mClientId); } + nativeUnlinkCiCam(mFrontendCiCamId); + mTunerResourceManager.releaseCiCam(mFrontendCiCamHandle, mClientId); + replicateCiCamSettings(null); } else { if (DEBUG) { - Log.d(TAG, "NOT unlinking CiCam : " + mClientId); + Log.d(TAG, "NOT releasing CiCam : " + mClientId); } } } finally { @@ -1665,11 +1665,9 @@ public class Tuner implements AutoCloseable { if (mFrontendCiCamHandle != null && mFrontendCiCamId != null && mFrontendCiCamId == ciCamId) { int result = nativeUnlinkCiCam(ciCamId); - if (result == RESULT_SUCCESS) { - mTunerResourceManager.releaseCiCam(mFrontendCiCamHandle, mClientId); - mFrontendCiCamId = null; - mFrontendCiCamHandle = null; - } + mTunerResourceManager.releaseCiCam(mFrontendCiCamHandle, mClientId); + mFrontendCiCamId = null; + mFrontendCiCamHandle = null; return result; } } diff --git a/nfc/api/current.txt b/nfc/api/current.txt index cf7aea405756..b0d1f71e749f 100644 --- a/nfc/api/current.txt +++ b/nfc/api/current.txt @@ -232,6 +232,8 @@ package android.nfc.cardemulation { method public final void notifyUnhandled(); method public final android.os.IBinder onBind(android.content.Intent); method public abstract void onDeactivated(int); + method @FlaggedApi("android.nfc.nfc_event_listener") public void onObserveModeStateChanged(boolean); + method @FlaggedApi("android.nfc.nfc_event_listener") public void onPreferredServiceChanged(boolean); method public abstract byte[] processCommandApdu(byte[], android.os.Bundle); method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void processPollingFrames(@NonNull java.util.List<android.nfc.cardemulation.PollingFrame>); method public final void sendResponseApdu(byte[]); diff --git a/nfc/java/android/nfc/cardemulation/HostApduService.java b/nfc/java/android/nfc/cardemulation/HostApduService.java index c3c74a6fd265..cd8e19c54565 100644 --- a/nfc/java/android/nfc/cardemulation/HostApduService.java +++ b/nfc/java/android/nfc/cardemulation/HostApduService.java @@ -242,6 +242,16 @@ public abstract class HostApduService extends Service { /** * @hide */ + public static final int MSG_OBSERVE_MODE_CHANGE = 5; + + /** + * @hide + */ + public static final int MSG_PREFERRED_SERVICE_CHANGED = 6; + + /** + * @hide + */ public static final String KEY_DATA = "data"; /** @@ -333,7 +343,17 @@ public abstract class HostApduService extends Service { processPollingFrames(pollingFrames); } break; - default: + case MSG_OBSERVE_MODE_CHANGE: + if (android.nfc.Flags.nfcEventListener()) { + onObserveModeStateChanged(msg.arg1 == 1); + } + break; + case MSG_PREFERRED_SERVICE_CHANGED: + if (android.nfc.Flags.nfcEventListener()) { + onPreferredServiceChanged(msg.arg1 == 1); + } + break; + default: super.handleMessage(msg); } } @@ -441,4 +461,26 @@ public abstract class HostApduService extends Service { * @param reason Either {@link #DEACTIVATION_LINK_LOSS} or {@link #DEACTIVATION_DESELECTED} */ public abstract void onDeactivated(int reason); + + + /** + * This method is called when this service is the preferred Nfc service and + * Observe mode has been enabled or disabled. + * + * @param isEnabled true if observe mode has been enabled, false if it has been disabled + */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER) + public void onObserveModeStateChanged(boolean isEnabled) { + + } + + /** + * This method is called when this service gains or loses preferred Nfc service status. + * + * @param isPreferred true is this service has become the preferred Nfc service, + * false if it is no longer the preferred service + */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER) + public void onPreferredServiceChanged(boolean isPreferred) { + } } diff --git a/nfc/java/android/nfc/flags.aconfig b/nfc/java/android/nfc/flags.aconfig index f16fa801a3e6..5819b98dd411 100644 --- a/nfc/java/android/nfc/flags.aconfig +++ b/nfc/java/android/nfc/flags.aconfig @@ -2,6 +2,14 @@ package: "android.nfc" container: "system" flag { + name: "nfc_event_listener" + is_exported: true + namespace: "nfc" + description: "Enable NFC Event listener APIs" + bug: "356447790" +} + +flag { name: "enable_nfc_mainline" is_exported: true namespace: "nfc" diff --git a/packages/PackageInstaller/TEST_MAPPING b/packages/PackageInstaller/TEST_MAPPING index b3fb1e7b3034..ff836104f799 100644 --- a/packages/PackageInstaller/TEST_MAPPING +++ b/packages/PackageInstaller/TEST_MAPPING @@ -28,6 +28,17 @@ }, { "name": "CtsIntentSignatureTestCases" + }, + { + "name": "CtsPackageInstallerCUJTestCases", + "options":[ + { + "exclude-annotation":"androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation":"org.junit.Ignore" + } + ] } ] } diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java index 9ad3e3c0af0f..170cb4546d0c 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java @@ -91,8 +91,7 @@ public class UninstallerActivity extends Activity { // be stale, if e.g. the app was uninstalled while the activity was destroyed. super.onCreate(null); - // TODO(b/318521110) Enable PIA v2 for archive dialog. - if (usePiaV2() && !isTv() && !isArchiveDialog(getIntent())) { + if (usePiaV2() && !isTv()) { Log.i(TAG, "Using Pia V2"); boolean returnResult = getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false); @@ -225,11 +224,6 @@ public class UninstallerActivity extends Activity { showConfirmationDialog(); } - private boolean isArchiveDialog(Intent intent) { - return (intent.getIntExtra(PackageInstaller.EXTRA_DELETE_FLAGS, 0) - & PackageManager.DELETE_ARCHIVE) != 0; - } - /** * Parses specific {@link android.content.pm.PackageManager.DeleteFlags} from {@link Intent} * to archive an app if requested. diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt index 186b69b4107b..3b0faf0cb56c 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt @@ -736,7 +736,8 @@ class InstallRepository(private val context: Context) { val appInfo = packageManager.getApplicationInfo( pkgName, PackageManager.MATCH_UNINSTALLED_PACKAGES ) - if (appInfo.flags and ApplicationInfo.FLAG_INSTALLED == 0) { + // If the package is archived, treat it as an update case. + if (!appInfo.isArchived && appInfo.flags and ApplicationInfo.FLAG_INSTALLED == 0) { return false } } catch (e: PackageManager.NameNotFoundException) { diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.kt index 0091a3e8b2cf..96525f645004 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.kt +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.kt @@ -32,6 +32,8 @@ import android.content.pm.ActivityInfo import android.content.pm.ApplicationInfo import android.content.pm.PackageInstaller import android.content.pm.PackageManager +import android.content.pm.PackageManager.ApplicationInfoFlags +import android.content.pm.PackageManager.PackageInfoFlags import android.content.pm.VersionedPackage import android.graphics.drawable.Icon import android.os.Build @@ -51,6 +53,9 @@ import com.android.packageinstaller.v2.model.PackageUtil.getMaxTargetSdkVersionF import com.android.packageinstaller.v2.model.PackageUtil.getPackageNameForUid import com.android.packageinstaller.v2.model.PackageUtil.isPermissionGranted import com.android.packageinstaller.v2.model.PackageUtil.isProfileOfOrSame +import com.android.packageinstaller.v2.model.UninstallAborted.Companion.ABORT_REASON_UNINSTALL_DONE +import android.content.pm.Flags as PmFlags +import android.multiuser.Flags as MultiuserFlags class UninstallRepository(private val context: Context) { @@ -71,6 +76,7 @@ class UninstallRepository(private val context: Context) { private var uninstallFromAllUsers = false private var isClonedApp = false private var uninstallId = 0 + private var deleteFlags = 0 fun performPreUninstallChecks(intent: Intent, callerInfo: CallerInfo): UninstallStage { this.intent = intent @@ -155,7 +161,9 @@ class UninstallRepository(private val context: Context) { try { targetAppInfo = packageManager.getApplicationInfo( targetPackageName!!, - PackageManager.ApplicationInfoFlags.of(PackageManager.MATCH_ANY_USER.toLong()) + ApplicationInfoFlags.of( + PackageManager.MATCH_ANY_USER.toLong() or PackageManager.MATCH_ARCHIVED_PACKAGES + ) ) } catch (e: PackageManager.NameNotFoundException) { Log.e(LOG_TAG, "Unable to get packageName") @@ -180,9 +188,27 @@ class UninstallRepository(private val context: Context) { } } + parseDeleteFlags(intent) + return UninstallReady() } + /** + * Parses specific {@link android.content.pm.PackageManager.DeleteFlags} from {@link Intent} + * to archive an app if requested. + * + * Do not parse other flags because developers might pass here any flags which might cause + * unintended behaviour. + * For more context {@link com.android.server.pm.PackageArchiver#requestArchive}. + */ + private fun parseDeleteFlags(intent: Intent) { + val flags = intent.getIntExtra(PackageInstaller.EXTRA_DELETE_FLAGS, 0) + val archive = flags and PackageManager.DELETE_ARCHIVE + val keepData = flags and PackageManager.DELETE_KEEP_DATA + + deleteFlags = archive or keepData + } + fun generateUninstallDetails(): UninstallStage { val messageBuilder = StringBuilder() @@ -201,6 +227,8 @@ class UninstallRepository(private val context: Context) { } val isUpdate = (targetAppInfo!!.flags and ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0 + val isArchive = + PmFlags.archiving() && ((deleteFlags and PackageManager.DELETE_ARCHIVE) != 0) val myUserHandle = Process.myUserHandle() val isSingleUser = isSingleUser() @@ -215,34 +243,54 @@ class UninstallRepository(private val context: Context) { ) ) } else if (uninstallFromAllUsers && !isSingleUser) { - messageBuilder.append(context.getString(R.string.uninstall_application_text_all_users)) + val messageString = if (isArchive) { + context.getString(R.string.archive_application_text_all_users) + } else { + context.getString(R.string.uninstall_application_text_all_users) + } + messageBuilder.append(messageString) } else if (uninstalledUser != myUserHandle) { // Uninstalling user is issuing uninstall for another user val customUserManager = context.createContextAsUser(uninstalledUser!!, 0) .getSystemService(UserManager::class.java) val userName = customUserManager!!.userName - var messageString = context.getString( - R.string.uninstall_application_text_user, - userName - ) + + var messageString = if (isArchive) { + context.getString(R.string.archive_application_text_user, userName) + } else { + context.getString(R.string.uninstall_application_text_user, userName) + } + if (userManager!!.isSameProfileGroup(myUserHandle, uninstalledUser!!)) { if (customUserManager.isManagedProfile) { - messageString = context.getString( + messageString = if (isArchive) { + context.getString( + R.string.archive_application_text_current_user_work_profile, userName + ) + } else { + context.getString( R.string.uninstall_application_text_current_user_work_profile, userName - ) + ) + } } else if (customUserManager.isCloneProfile){ isClonedApp = true messageString = context.getString( R.string.uninstall_application_text_current_user_clone_profile ) } else if (Flags.allowPrivateProfile() - && android.multiuser.Flags.enablePrivateSpaceFeatures() + && MultiuserFlags.enablePrivateSpaceFeatures() && customUserManager.isPrivateProfile ) { // TODO(b/324244123): Get these Strings from a User Property API. - messageString = context.getString( + messageString = if (isArchive) { + context.getString( + R.string.archive_application_text_current_user_private_profile, userName + ) + } else { + context.getString( R.string.uninstall_application_text_current_user_private_profile - ) + ) + } } } messageBuilder.append(messageString) @@ -262,6 +310,8 @@ class UninstallRepository(private val context: Context) { targetAppLabel ) ) + } else if (isArchive) { + messageBuilder.append(context.getString(R.string.archive_application_text)) } else { messageBuilder.append(context.getString(R.string.uninstall_application_text)) } @@ -270,15 +320,21 @@ class UninstallRepository(private val context: Context) { val title = if (isClonedApp) { context.getString(R.string.cloned_app_label, targetAppLabel) + } else if (isArchive) { + context.getString(R.string.archiving_app_label, targetAppLabel) } else { targetAppLabel.toString() } var suggestToKeepAppData = false try { - val pkgInfo = packageManager.getPackageInfo(targetPackageName!!, 0) + val pkgInfo = packageManager.getPackageInfo( + targetPackageName!!, PackageInfoFlags.of(PackageManager.MATCH_ARCHIVED_PACKAGES) + ) suggestToKeepAppData = - pkgInfo.applicationInfo != null && pkgInfo.applicationInfo!!.hasFragileUserData() + pkgInfo.applicationInfo != null + && pkgInfo.applicationInfo!!.hasFragileUserData() + && !isArchive } catch (e: PackageManager.NameNotFoundException) { Log.e(LOG_TAG, "Cannot check hasFragileUserData for $targetPackageName", e) } @@ -291,7 +347,7 @@ class UninstallRepository(private val context: Context) { ) } - return UninstallUserActionRequired(title, message, appDataSize) + return UninstallUserActionRequired(title, message, appDataSize, isArchive) } /** @@ -444,10 +500,11 @@ class UninstallRepository(private val context: Context) { callback!!.onUninstallComplete(targetPackageName!!, legacyStatus, message) // Since the caller already received the results, just finish the app at this point - uninstallResult.value = null + uninstallResult.value = UninstallAborted(ABORT_REASON_UNINSTALL_DONE) return } val returnResult = intent.getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false) + if (returnResult || callingActivity != null) { val intent = Intent() intent.putExtra(Intent.EXTRA_INSTALL_RESULT, legacyStatus) @@ -717,6 +774,7 @@ class UninstallRepository(private val context: Context) { ): Boolean { var flags = if (uninstallFromAllUsers) PackageManager.DELETE_ALL_USERS else 0 flags = flags or if (keepData) PackageManager.DELETE_KEEP_DATA else 0 + flags = flags or deleteFlags return try { context.createContextAsUser(targetUser, 0) diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallStages.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallStages.kt index f086209fe498..316e8b7e3a73 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallStages.kt +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallStages.kt @@ -38,7 +38,8 @@ class UninstallReady : UninstallStage(STAGE_READY) data class UninstallUserActionRequired( val title: String? = null, val message: String? = null, - val appDataSize: Long = 0 + val appDataSize: Long = 0, + val isArchive: Boolean = false ) : UninstallStage(STAGE_USER_ACTION_REQUIRED) data class UninstallUninstalling(val appLabel: CharSequence, val isCloneUser: Boolean) : @@ -96,6 +97,11 @@ data class UninstallAborted(val abortReason: Int) : UninstallStage(STAGE_ABORTED dialogTextResource = R.string.user_is_not_allowed_dlg_text } + ABORT_REASON_UNINSTALL_DONE -> { + dialogTitleResource = 0 + dialogTextResource = 0 + } + else -> { dialogTitleResource = 0 dialogTextResource = R.string.generic_error_dlg_text @@ -107,6 +113,7 @@ data class UninstallAborted(val abortReason: Int) : UninstallStage(STAGE_ABORTED const val ABORT_REASON_GENERIC_ERROR = 0 const val ABORT_REASON_APP_UNAVAILABLE = 1 const val ABORT_REASON_USER_NOT_ALLOWED = 2 + const val ABORT_REASON_UNINSTALL_DONE = 3 } } diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.kt index e2ab31662380..c61a2ac9f0dd 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.kt +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.kt @@ -16,7 +16,9 @@ package com.android.packageinstaller.v2.ui -import android.app.Activity +import android.app.Activity.RESULT_CANCELED +import android.app.Activity.RESULT_FIRST_USER +import android.app.Activity.RESULT_OK import android.app.AppOpsManager import android.content.ActivityNotFoundException import android.content.Intent @@ -135,7 +137,7 @@ class InstallLaunch : FragmentActivity(), InstallActionListener { } InstallAborted.ABORT_REASON_POLICY -> showPolicyRestrictionDialog(aborted) - else -> setResult(Activity.RESULT_CANCELED, null, true) + else -> setResult(RESULT_CANCELED, null, true) } } @@ -169,7 +171,7 @@ class InstallLaunch : FragmentActivity(), InstallActionListener { val success = installStage as InstallSuccess if (success.shouldReturnResult) { val successIntent = success.resultIntent - setResult(Activity.RESULT_OK, successIntent, true) + setResult(RESULT_OK, successIntent, true) } else { val successDialog = InstallSuccessFragment(success) showDialogInner(successDialog) @@ -180,7 +182,7 @@ class InstallLaunch : FragmentActivity(), InstallActionListener { val failed = installStage as InstallFailed if (failed.shouldReturnResult) { val failureIntent = failed.resultIntent - setResult(Activity.RESULT_FIRST_USER, failureIntent, true) + setResult(RESULT_FIRST_USER, failureIntent, true) } else { val failureDialog = InstallFailedFragment(failed) showDialogInner(failureDialog) @@ -219,7 +221,7 @@ class InstallLaunch : FragmentActivity(), InstallActionListener { shouldFinish = blockedByPolicyDialog == null showDialogInner(blockedByPolicyDialog) } - setResult(Activity.RESULT_CANCELED, null, shouldFinish) + setResult(RESULT_CANCELED, null, shouldFinish) } /** @@ -257,6 +259,10 @@ class InstallLaunch : FragmentActivity(), InstallActionListener { fun setResult(resultCode: Int, data: Intent?, shouldFinish: Boolean) { super.setResult(resultCode, data) + if (resultCode != RESULT_OK) { + // Let callers know that the install was cancelled + installViewModel!!.cleanupInstall() + } if (shouldFinish) { finish() } @@ -282,7 +288,7 @@ class InstallLaunch : FragmentActivity(), InstallActionListener { if (stageCode == InstallStage.STAGE_USER_ACTION_REQUIRED) { installViewModel!!.cleanupInstall() } - setResult(Activity.RESULT_CANCELED, null, true) + setResult(RESULT_CANCELED, null, true) } override fun onNegativeResponse(resultCode: Int, data: Intent?) { @@ -318,7 +324,7 @@ class InstallLaunch : FragmentActivity(), InstallActionListener { if (localLogv) { Log.d(LOG_TAG, "Opening $intent") } - setResult(Activity.RESULT_OK, intent, true) + setResult(RESULT_OK, intent, true) if (intent != null && intent.hasCategory(Intent.CATEGORY_LAUNCHER)) { startActivity(intent) } diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallConfirmationFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallConfirmationFragment.java index 87af1ae200ca..524b4e6a0e63 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallConfirmationFragment.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallConfirmationFragment.java @@ -60,7 +60,7 @@ public class UninstallConfirmationFragment extends DialogFragment { Log.i(LOG_TAG, "Creating " + LOG_TAG + "\n" + mDialogData); AlertDialog.Builder builder = new AlertDialog.Builder(requireContext()) .setTitle(mDialogData.getTitle()) - .setPositiveButton(R.string.ok, + .setPositiveButton(mDialogData.isArchive() ? R.string.archive : R.string.ok, (dialogInt, which) -> mUninstallActionListener.onPositiveResponse( mKeepData != null && mKeepData.isChecked())) .setNegativeButton(R.string.cancel, diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java index 7124ed2d96b8..c6eb9fddf2a7 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java @@ -178,7 +178,7 @@ public class CsipDeviceManager { } log("updateRelationshipOfGroupDevices: mCachedDevices list =" + mCachedDevices.toString()); - // Get the preferred main device by getPreferredMainDeviceWithoutConectionState + // Get the preferred main device by getPreferredMainDeviceWithoutConnectionState List<CachedBluetoothDevice> groupDevicesList = getGroupDevicesFromAllOfDevicesList(groupId); CachedBluetoothDevice preferredMainDevice = getPreferredMainDevice(groupId, groupDevicesList); @@ -373,6 +373,7 @@ public class CsipDeviceManager { preferredMainDevice.addMemberDevice(deviceItem); mCachedDevices.remove(deviceItem); mBtManager.getEventManager().dispatchDeviceRemoved(deviceItem); + preferredMainDevice.refresh(); hasChanged = true; } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java index 27fcdbe0334f..26905b1d86d2 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java @@ -80,6 +80,7 @@ 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_START_LE_AUDIO_SHARING = "START_LE_AUDIO_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; diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModesBackend.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModesBackend.java index 492828d701b9..64e503b323b8 100644 --- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModesBackend.java +++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModesBackend.java @@ -174,7 +174,6 @@ public class ZenModesBackend { mNotificationManager.setZenMode(Settings.Global.ZEN_MODE_OFF, null, TAG, /* fromUser= */ true); } else { - // TODO: b/333527800 - This should (potentially) snooze the rule if it was active. mNotificationManager.setAutomaticZenRuleState(mode.getId(), new Condition(mode.getRule().getConditionId(), "", Condition.STATE_FALSE, Condition.SOURCE_USER_ACTION)); diff --git a/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java b/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java index 69c7410818dd..6198d80cefe6 100644 --- a/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java +++ b/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java @@ -181,14 +181,14 @@ public class CreateUserDialogController { * admin status. */ public Dialog createDialog(Activity activity, - ActivityStarter activityStarter, boolean isMultipleAdminEnabled, + ActivityStarter activityStarter, boolean canCreateAdminUser, NewUserData successCallback, Runnable cancelCallback) { mActivity = activity; mCustomDialogHelper = new CustomDialogHelper(activity); mSuccessCallback = successCallback; mCancelCallback = cancelCallback; mActivityStarter = activityStarter; - addCustomViews(isMultipleAdminEnabled); + addCustomViews(canCreateAdminUser); mUserCreationDialog = mCustomDialogHelper.getDialog(); updateLayout(); mUserCreationDialog.setOnDismissListener(view -> finish()); @@ -197,19 +197,19 @@ public class CreateUserDialogController { return mUserCreationDialog; } - private void addCustomViews(boolean isMultipleAdminEnabled) { + private void addCustomViews(boolean canCreateAdminUser) { addGrantAdminView(); addUserInfoEditView(); mCustomDialogHelper.setPositiveButton(R.string.next, view -> { mCurrentState++; - if (mCurrentState == GRANT_ADMIN_DIALOG && !isMultipleAdminEnabled) { + if (mCurrentState == GRANT_ADMIN_DIALOG && !canCreateAdminUser) { mCurrentState++; } updateLayout(); }); mCustomDialogHelper.setNegativeButton(R.string.back, view -> { mCurrentState--; - if (mCurrentState == GRANT_ADMIN_DIALOG && !isMultipleAdminEnabled) { + if (mCurrentState == GRANT_ADMIN_DIALOG && !canCreateAdminUser) { mCurrentState--; } updateLayout(); diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt index c88c4c94b5fb..0e71116db6cc 100644 --- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt +++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt @@ -25,6 +25,7 @@ import android.media.AudioManager.OnCommunicationDeviceChangedListener import android.provider.Settings import androidx.concurrent.futures.DirectExecutor import com.android.internal.util.ConcurrentUtils +import com.android.settingslib.volume.shared.AudioLogger import com.android.settingslib.volume.shared.AudioManagerEventsReceiver import com.android.settingslib.volume.shared.model.AudioManagerEvent import com.android.settingslib.volume.shared.model.AudioStream @@ -99,7 +100,7 @@ class AudioRepositoryImpl( private val contentResolver: ContentResolver, private val backgroundCoroutineContext: CoroutineContext, private val coroutineScope: CoroutineScope, - private val logger: Logger, + private val logger: AudioLogger, ) : AudioRepository { private val streamSettingNames: Map<AudioStream, String> = @@ -117,10 +118,10 @@ class AudioRepositoryImpl( override val mode: StateFlow<Int> = callbackFlow { - val listener = AudioManager.OnModeChangedListener { newMode -> trySend(newMode) } - audioManager.addOnModeChangedListener(ConcurrentUtils.DIRECT_EXECUTOR, listener) - awaitClose { audioManager.removeOnModeChangedListener(listener) } - } + val listener = AudioManager.OnModeChangedListener { newMode -> trySend(newMode) } + audioManager.addOnModeChangedListener(ConcurrentUtils.DIRECT_EXECUTOR, listener) + awaitClose { audioManager.removeOnModeChangedListener(listener) } + } .onStart { emit(audioManager.mode) } .flowOn(backgroundCoroutineContext) .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), audioManager.mode) @@ -140,14 +141,14 @@ class AudioRepositoryImpl( override val communicationDevice: StateFlow<AudioDeviceInfo?> get() = callbackFlow { - val listener = OnCommunicationDeviceChangedListener { trySend(Unit) } - audioManager.addOnCommunicationDeviceChangedListener( - ConcurrentUtils.DIRECT_EXECUTOR, - listener, - ) + val listener = OnCommunicationDeviceChangedListener { trySend(Unit) } + audioManager.addOnCommunicationDeviceChangedListener( + ConcurrentUtils.DIRECT_EXECUTOR, + listener, + ) - awaitClose { audioManager.removeOnCommunicationDeviceChangedListener(listener) } - } + awaitClose { audioManager.removeOnCommunicationDeviceChangedListener(listener) } + } .filterNotNull() .map { audioManager.communicationDevice } .onStart { emit(audioManager.communicationDevice) } @@ -160,15 +161,15 @@ class AudioRepositoryImpl( override fun getAudioStream(audioStream: AudioStream): Flow<AudioStreamModel> { return merge( - audioManagerEventsReceiver.events.filter { - if (it is StreamAudioManagerEvent) { - it.audioStream == audioStream - } else { - true - } - }, - volumeSettingChanges(audioStream), - ) + audioManagerEventsReceiver.events.filter { + if (it is StreamAudioManagerEvent) { + it.audioStream == audioStream + } else { + true + } + }, + volumeSettingChanges(audioStream), + ) .conflate() .map { getCurrentAudioStream(audioStream) } .onStart { emit(getCurrentAudioStream(audioStream)) } @@ -251,11 +252,4 @@ class AudioRepositoryImpl( awaitClose { contentResolver.unregisterContentObserver(observer) } } } - - interface Logger { - - fun onSetVolumeRequested(audioStream: AudioStream, volume: Int) - - fun onVolumeUpdateReceived(audioStream: AudioStream, model: AudioStreamModel) - } } diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt index 7a66335ef22f..ebba7f152b90 100644 --- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt +++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt @@ -34,6 +34,7 @@ import com.android.settingslib.bluetooth.onServiceStateChanged import com.android.settingslib.bluetooth.onSourceConnectedOrRemoved import com.android.settingslib.volume.data.repository.AudioSharingRepository.Companion.AUDIO_SHARING_VOLUME_MAX import com.android.settingslib.volume.data.repository.AudioSharingRepository.Companion.AUDIO_SHARING_VOLUME_MIN +import com.android.settingslib.volume.shared.AudioSharingLogger import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -50,6 +51,7 @@ import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.runningFold import kotlinx.coroutines.flow.stateIn @@ -90,6 +92,7 @@ class AudioSharingRepositoryImpl( private val btManager: LocalBluetoothManager, private val coroutineScope: CoroutineScope, private val backgroundCoroutineContext: CoroutineContext, + private val logger: AudioSharingLogger ) : AudioSharingRepository { private val isAudioSharingProfilesReady: StateFlow<Boolean> = btManager.profileManager.onServiceStateChanged @@ -104,6 +107,7 @@ class AudioSharingRepositoryImpl( btManager.profileManager.leAudioBroadcastProfile.onBroadcastStartedOrStopped .map { isBroadcasting() } .onStart { emit(isBroadcasting()) } + .onEach { logger.onAudioSharingStateChanged(it) } .flowOn(backgroundCoroutineContext) } else { flowOf(false) @@ -156,6 +160,7 @@ class AudioSharingRepositoryImpl( .map { getSecondaryGroupId() }, primaryGroupId.map { getSecondaryGroupId() }) .onStart { emit(getSecondaryGroupId()) } + .onEach { logger.onSecondaryGroupIdChanged(it) } .flowOn(backgroundCoroutineContext) .stateIn( coroutineScope, @@ -202,6 +207,7 @@ class AudioSharingRepositoryImpl( acc } } + .onEach { logger.onVolumeMapChanged(it) } .flowOn(backgroundCoroutineContext) } else { emptyFlow() @@ -220,6 +226,7 @@ class AudioSharingRepositoryImpl( BluetoothUtils.getSecondaryDeviceForBroadcast(contentResolver, btManager) if (cachedDevice != null) { it.setDeviceVolume(cachedDevice.device, volume, /* isGroupOp= */ true) + logger.onSetDeviceVolumeRequested(volume) } } } diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioLogger.kt b/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioLogger.kt new file mode 100644 index 000000000000..84f7fcbd8b96 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioLogger.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.volume.shared + +import com.android.settingslib.volume.shared.model.AudioStream +import com.android.settingslib.volume.shared.model.AudioStreamModel + +/** A log interface for audio streams volume events. */ +interface AudioLogger { + fun onSetVolumeRequested(audioStream: AudioStream, volume: Int) + + fun onVolumeUpdateReceived(audioStream: AudioStream, model: AudioStreamModel) +}
\ No newline at end of file diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioSharingLogger.kt b/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioSharingLogger.kt new file mode 100644 index 000000000000..18a4c6d1748d --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioSharingLogger.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.volume.shared + +/** A log interface for audio sharing volume events. */ +interface AudioSharingLogger { + + fun onAudioSharingStateChanged(state: Boolean) + + fun onSecondaryGroupIdChanged(groupId: Int) + + fun onVolumeMapChanged(map: Map<Int, Int>) + + fun onSetDeviceVolumeRequested(volume: Int) +}
\ No newline at end of file diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioSharingRepositoryTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioSharingRepositoryTest.kt index 078f0c8adba5..8c5a0851cc92 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioSharingRepositoryTest.kt +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioSharingRepositoryTest.kt @@ -48,6 +48,7 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest +import org.junit.After import org.junit.Before import org.junit.Rule import org.junit.Test @@ -101,7 +102,7 @@ class AudioSharingRepositoryTest { @Captor private lateinit var assistantCallbackCaptor: - ArgumentCaptor<BluetoothLeBroadcastAssistant.Callback> + ArgumentCaptor<BluetoothLeBroadcastAssistant.Callback> @Captor private lateinit var btCallbackCaptor: ArgumentCaptor<BluetoothCallback> @@ -110,6 +111,7 @@ class AudioSharingRepositoryTest { @Captor private lateinit var volumeCallbackCaptor: ArgumentCaptor<BluetoothVolumeControl.Callback> + private val logger = FakeAudioSharingRepositoryLogger() private val testScope = TestScope() private val context: Context = ApplicationProvider.getApplicationContext() @Spy private val contentResolver: ContentResolver = context.contentResolver @@ -135,16 +137,23 @@ class AudioSharingRepositoryTest { Settings.Secure.putInt( contentResolver, BluetoothUtils.getPrimaryGroupIdUriForBroadcast(), - TEST_GROUP_ID_INVALID) + TEST_GROUP_ID_INVALID + ) underTest = AudioSharingRepositoryImpl( contentResolver, btManager, testScope.backgroundScope, testScope.testScheduler, + logger ) } + @After + fun tearDown() { + logger.reset() + } + @Test fun audioSharingStateChange_profileReady_emitValues() { testScope.runTest { @@ -160,6 +169,13 @@ class AudioSharingRepositoryTest { runCurrent() Truth.assertThat(states).containsExactly(false, true, false, true) + Truth.assertThat(logger.logs) + .containsAtLeastElementsIn( + listOf( + "onAudioSharingStateChanged state=true", + "onAudioSharingStateChanged state=false", + ) + ).inOrder() } } @@ -187,7 +203,8 @@ class AudioSharingRepositoryTest { Truth.assertThat(groupIds) .containsExactly( TEST_GROUP_ID_INVALID, - TEST_GROUP_ID2) + TEST_GROUP_ID2 + ) } } @@ -219,13 +236,16 @@ class AudioSharingRepositoryTest { triggerSourceAdded() runCurrent() triggerProfileConnectionChange( - BluetoothAdapter.STATE_CONNECTING, BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT) + BluetoothAdapter.STATE_CONNECTING, BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT + ) runCurrent() triggerProfileConnectionChange( - BluetoothAdapter.STATE_DISCONNECTED, BluetoothProfile.LE_AUDIO) + BluetoothAdapter.STATE_DISCONNECTED, BluetoothProfile.LE_AUDIO + ) runCurrent() triggerProfileConnectionChange( - BluetoothAdapter.STATE_DISCONNECTED, BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT) + BluetoothAdapter.STATE_DISCONNECTED, BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT + ) runCurrent() Truth.assertThat(groupIds) @@ -235,7 +255,16 @@ class AudioSharingRepositoryTest { TEST_GROUP_ID1, TEST_GROUP_ID_INVALID, TEST_GROUP_ID2, - TEST_GROUP_ID_INVALID) + TEST_GROUP_ID_INVALID + ) + Truth.assertThat(logger.logs) + .containsAtLeastElementsIn( + listOf( + "onSecondaryGroupIdChanged groupId=$TEST_GROUP_ID_INVALID", + "onSecondaryGroupIdChanged groupId=$TEST_GROUP_ID2", + "onSecondaryGroupIdChanged groupId=$TEST_GROUP_ID1", + ) + ).inOrder() } } @@ -257,11 +286,22 @@ class AudioSharingRepositoryTest { verify(volumeControl).unregisterCallback(any()) runCurrent() + val expectedMap1 = mapOf(TEST_GROUP_ID1 to TEST_VOLUME1) + val expectedMap2 = mapOf(TEST_GROUP_ID1 to TEST_VOLUME2) Truth.assertThat(volumeMaps) .containsExactly( emptyMap<Int, Int>(), - mapOf(TEST_GROUP_ID1 to TEST_VOLUME1), - mapOf(TEST_GROUP_ID1 to TEST_VOLUME2)) + expectedMap1, + expectedMap2 + ) + Truth.assertThat(logger.logs) + .containsAtLeastElementsIn( + listOf( + "onVolumeMapChanged map={}", + "onVolumeMapChanged map=$expectedMap1", + "onVolumeMapChanged map=$expectedMap2", + ) + ).inOrder() } } @@ -281,12 +321,19 @@ class AudioSharingRepositoryTest { Settings.Secure.putInt( contentResolver, BluetoothUtils.getPrimaryGroupIdUriForBroadcast(), - TEST_GROUP_ID2) + TEST_GROUP_ID2 + ) `when`(assistant.allConnectedDevices).thenReturn(listOf(device1, device2)) underTest.setSecondaryVolume(TEST_VOLUME1) runCurrent() verify(volumeControl).setDeviceVolume(device1, TEST_VOLUME1, true) + Truth.assertThat(logger.logs) + .isEqualTo( + listOf( + "onSetVolumeRequested volume=$TEST_VOLUME1", + ) + ) } } @@ -313,7 +360,8 @@ class AudioSharingRepositoryTest { Settings.Secure.putInt( contentResolver, BluetoothUtils.getPrimaryGroupIdUriForBroadcast(), - TEST_GROUP_ID1) + TEST_GROUP_ID1 + ) `when`(assistant.allConnectedDevices).thenReturn(listOf(device1, device2)) assistantCallbackCaptor.value.sourceAdded(device1, receiveState) } @@ -324,7 +372,8 @@ class AudioSharingRepositoryTest { Settings.Secure.putInt( contentResolver, BluetoothUtils.getPrimaryGroupIdUriForBroadcast(), - TEST_GROUP_ID1) + TEST_GROUP_ID1 + ) assistantCallbackCaptor.value.sourceRemoved(device2) } @@ -334,7 +383,8 @@ class AudioSharingRepositoryTest { Settings.Secure.putInt( contentResolver, BluetoothUtils.getPrimaryGroupIdUriForBroadcast(), - TEST_GROUP_ID1) + TEST_GROUP_ID1 + ) btCallbackCaptor.value.onProfileConnectionStateChanged(cachedDevice2, state, profile) } @@ -343,12 +393,14 @@ class AudioSharingRepositoryTest { .registerContentObserver( eq(Settings.Secure.getUriFor(BluetoothUtils.getPrimaryGroupIdUriForBroadcast())), eq(false), - contentObserverCaptor.capture()) + contentObserverCaptor.capture() + ) `when`(assistant.allConnectedDevices).thenReturn(listOf(device1, device2)) Settings.Secure.putInt( contentResolver, BluetoothUtils.getPrimaryGroupIdUriForBroadcast(), - TEST_GROUP_ID2) + TEST_GROUP_ID2 + ) contentObserverCaptor.value.primaryChanged() } @@ -380,8 +432,9 @@ class AudioSharingRepositoryTest { onBroadcastStopped(TEST_REASON, TEST_BROADCAST_ID) } val sourceAdded: - BluetoothLeBroadcastAssistant.Callback.( - sink: BluetoothDevice, state: BluetoothLeBroadcastReceiveState) -> Unit = + BluetoothLeBroadcastAssistant.Callback.( + sink: BluetoothDevice, state: BluetoothLeBroadcastReceiveState + ) -> Unit = { sink, state -> onReceiveStateChanged(sink, TEST_SOURCE_ID, state) } diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepositoryLogger.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepositoryLogger.kt index 389bf5304262..bd573fb2c675 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepositoryLogger.kt +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepositoryLogger.kt @@ -16,10 +16,11 @@ package com.android.settingslib.volume.data.repository +import com.android.settingslib.volume.shared.AudioLogger import com.android.settingslib.volume.shared.model.AudioStream import com.android.settingslib.volume.shared.model.AudioStreamModel -class FakeAudioRepositoryLogger : AudioRepositoryImpl.Logger { +class FakeAudioRepositoryLogger : AudioLogger { private val mutableLogs: MutableList<String> = mutableListOf() val logs: List<String> diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioSharingRepositoryLogger.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioSharingRepositoryLogger.kt new file mode 100644 index 000000000000..cc4cc8d4ab96 --- /dev/null +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioSharingRepositoryLogger.kt @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.volume.data.repository + +import com.android.settingslib.volume.shared.AudioSharingLogger +import java.util.concurrent.CopyOnWriteArrayList + +class FakeAudioSharingRepositoryLogger : AudioSharingLogger { + private val mutableLogs = CopyOnWriteArrayList<String>() + val logs: List<String> + get() = mutableLogs.toList() + + fun reset() { + mutableLogs.clear() + } + + override fun onAudioSharingStateChanged(state: Boolean) { + mutableLogs.add("onAudioSharingStateChanged state=$state") + } + + override fun onSecondaryGroupIdChanged(groupId: Int) { + mutableLogs.add("onSecondaryGroupIdChanged groupId=$groupId") + } + + override fun onVolumeMapChanged(map: GroupIdToVolumes) { + mutableLogs.add("onVolumeMapChanged map=$map") + } + + override fun onSetDeviceVolumeRequested(volume: Int) { + mutableLogs.add("onSetVolumeRequested volume=$volume") + } +}
\ No newline at end of file diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java index 7e7c76e6ecba..8cc997414d70 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java @@ -35,6 +35,7 @@ import android.os.Parcel; import android.os.ParcelUuid; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -545,6 +546,7 @@ public class CachedBluetoothDeviceManagerTest { * Test to verify OnDeviceUnpaired() for csip device unpair. */ @Test + @Ignore("b/359066481") public void onDeviceUnpaired_unpairCsipSubDevice() { when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); when(mDevice2.getBondState()).thenReturn(BluetoothDevice.BOND_NONE); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CsipDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CsipDeviceManagerTest.java index f94f21fe5d45..698eb8159846 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CsipDeviceManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CsipDeviceManagerTest.java @@ -19,7 +19,9 @@ package com.android.settingslib.bluetooth; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.bluetooth.BluetoothClass; @@ -352,4 +354,34 @@ public class CsipDeviceManagerTest { assertThat(mCachedDevice1.getMemberDevice()).contains(mCachedDevice3); assertThat(mCachedDevice1.getDevice()).isEqualTo(expectedMainBluetoothDevice); } + + @Test + public void onProfileConnectionStateChangedIfProcessed_addMemberDevice_refreshUI() { + mCachedDevice3.setGroupId(GROUP1); + + mCsipDeviceManager.onProfileConnectionStateChangedIfProcessed(mCachedDevice3, + BluetoothProfile.STATE_CONNECTED); + + verify(mCachedDevice1).refresh(); + } + + @Test + public void onProfileConnectionStateChangedIfProcessed_switchMainDevice_refreshUI() { + when(mDevice3.isConnected()).thenReturn(true); + when(mDevice2.isConnected()).thenReturn(false); + when(mDevice1.isConnected()).thenReturn(false); + mCachedDevice3.setGroupId(GROUP1); + mCsipDeviceManager.onProfileConnectionStateChangedIfProcessed(mCachedDevice3, + BluetoothProfile.STATE_CONNECTED); + + when(mDevice3.isConnected()).thenReturn(false); + mCsipDeviceManager.onProfileConnectionStateChangedIfProcessed(mCachedDevice3, + BluetoothProfile.STATE_DISCONNECTED); + when(mDevice1.isConnected()).thenReturn(true); + mCsipDeviceManager.onProfileConnectionStateChangedIfProcessed(mCachedDevice1, + BluetoothProfile.STATE_CONNECTED); + + verify(mCachedDevice3).switchMemberDeviceContent(mCachedDevice1); + verify(mCachedDevice3, atLeastOnce()).refresh(); + } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java index 194a0e29de34..4e54d8f8688f 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java @@ -40,6 +40,7 @@ import com.android.settingslib.testutils.OverpoweredReflectionHelper; import com.android.settingslib.testutils.shadow.ShadowInteractionJankMonitor; import org.junit.Before; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -58,6 +59,7 @@ import java.util.concurrent.TimeUnit; @RunWith(RobolectricTestRunner.class) @Config(shadows = {ShadowInteractionJankMonitor.class, SettingsJankMonitorTest.ShadowBuilder.class}) +@Ignore("b/359066481") public class SettingsJankMonitorTest { private static final String TEST_KEY = "key"; diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerAllowlistBackendTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerAllowlistBackendTest.java index 0d318c346521..325bb2c941b7 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerAllowlistBackendTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerAllowlistBackendTest.java @@ -36,6 +36,7 @@ import com.android.settingslib.testutils.shadow.ShadowDefaultDialerManager; import com.android.settingslib.testutils.shadow.ShadowSmsApplication; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -86,6 +87,7 @@ public class PowerAllowlistBackendTest { mPowerAllowlistBackend = new PowerAllowlistBackend(mContext, mDeviceIdleService); } + @Ignore("b/359066481") @Test public void testIsAllowlisted() throws Exception { doReturn(new String[] {PACKAGE_ONE}).when(mDeviceIdleService).getFullPowerWhitelist(); @@ -160,6 +162,7 @@ public class PowerAllowlistBackendTest { assertThat(mPowerAllowlistBackend.isDefaultActiveApp(PACKAGE_ONE, UID)).isTrue(); } + @Ignore("b/359066481") @Test public void testIsSystemAllowlisted() throws Exception { doReturn(new String[] {PACKAGE_ONE}).when(mDeviceIdleService).getSystemPowerWhitelist(); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModesBackendTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModesBackendTest.java index 00c7ae3e97d7..539519b3ec3b 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModesBackendTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModesBackendTest.java @@ -123,7 +123,6 @@ public class ZenModesBackendTest { zenRule.id = id; zenRule.pkg = "package"; zenRule.enabled = azr.isEnabled(); - zenRule.snoozing = false; zenRule.conditionId = azr.getConditionId(); zenRule.condition = new Condition(azr.getConditionId(), "", active ? Condition.STATE_TRUE : Condition.STATE_FALSE, diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java index 4f3b2005b197..b549e3f3049e 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java @@ -26,6 +26,7 @@ import android.content.Context; import androidx.test.core.app.ApplicationProvider; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.MockitoAnnotations; @@ -86,6 +87,7 @@ public class PowerUtilTest { assertThat(PowerUtil.roundTimeToNearestThreshold(-200, -75)).isEqualTo(225); } + @Ignore("b/359066481") @Test public void getTargetTimeShortString_lessThan15Minutes_returnsTimeShortStringWithoutRounded() { mContext.getSystemService(AlarmManager.class).setTimeZone("UTC"); @@ -100,6 +102,7 @@ public class PowerUtilTest { assertThat(actualTimeString).endsWith("14 PM"); } + @Ignore("b/359066481") @Test public void getTargetTimeShortString_moreThan15Minutes_returnsTimeShortStringWithRounded() { mContext.getSystemService(AlarmManager.class).setTimeZone("UTC"); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BannerMessagePreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BannerMessagePreferenceTest.java index f0f53d6e82ad..7f4bdaeac855 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BannerMessagePreferenceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BannerMessagePreferenceTest.java @@ -44,6 +44,7 @@ import com.android.settingslib.testutils.OverpoweredReflectionHelper; import com.android.settingslib.widget.preference.banner.R; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.Robolectric; @@ -53,6 +54,7 @@ import org.robolectric.shadows.ShadowDrawable; import org.robolectric.shadows.ShadowTouchDelegate; import org.robolectric.util.ReflectionHelpers; +@Ignore("b/359066481") @RunWith(RobolectricTestRunner.class) public class BannerMessagePreferenceTest { diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java index 30c4ee55842e..9ab853ff4964 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java @@ -690,7 +690,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { Cursor cursor = getContentResolver().query(Settings.Global.CONTENT_URI, PROJECTION, null, null, null); try { - return extractRelevantValues(cursor, GlobalSettings.SETTINGS_TO_BACKUP); + return extractRelevantValues(cursor, getGlobalSettingsToBackup()); } finally { cursor.close(); } @@ -1011,7 +1011,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { Settings.System.LEGACY_RESTORE_SETTINGS); validators = SystemSettingsValidators.VALIDATORS; } else if (contentUri.equals(Settings.Global.CONTENT_URI)) { - whitelist = ArrayUtils.concat(String.class, GlobalSettings.SETTINGS_TO_BACKUP, + whitelist = ArrayUtils.concat(String.class, getGlobalSettingsToBackup(), Settings.Global.LEGACY_RESTORE_SETTINGS); validators = GlobalSettingsValidators.VALIDATORS; } else { @@ -1021,6 +1021,17 @@ public class SettingsBackupAgent extends BackupAgentHelper { return new SettingsBackupWhitelist(whitelist, validators); } + private String[] getGlobalSettingsToBackup() { + // On watches, we don't want to backup or restore 'bluetooth_on' setting, as setting it to + // false during restore would cause watch OOBE to fail due to bluetooth connection loss. + if (isWatch()) { + return ArrayUtils.removeElement( + String.class, GlobalSettings.SETTINGS_TO_BACKUP, Settings.Global.BLUETOOTH_ON); + } + + return GlobalSettings.SETTINGS_TO_BACKUP; + } + private boolean isBlockedByDynamicList(Set<String> dynamicBlockList, Uri areaUri, String key) { String contentKey = Uri.withAppendedPath(areaUri, key).toString(); return dynamicBlockList.contains(contentKey); diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java index d4ca4a35ed26..3a39150523ac 100644 --- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java +++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java @@ -26,6 +26,7 @@ import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.content.ContextWrapper; +import android.content.pm.PackageManager; import android.database.Cursor; import android.database.MatrixCursor; import android.net.Uri; @@ -221,6 +222,21 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest { } @Test + public void testOnRestore_bluetoothOnRestoredOnNonWearablesOnly() { + TestSettingsHelper settingsHelper = new TestSettingsHelper(mContext); + mAgentUnderTest.mSettingsHelper = settingsHelper; + + restoreGlobalSettings(generateBackupData(Map.of(Settings.Global.BLUETOOTH_ON, "0"))); + + var isWatch = mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH); + if (isWatch) { + assertFalse(settingsHelper.mWrittenValues.containsKey(Settings.Global.BLUETOOTH_ON)); + } else { + assertEquals("0", settingsHelper.mWrittenValues.get(Settings.Global.BLUETOOTH_ON)); + } + } + + @Test @EnableFlags(Flags.FLAG_CONFIGURABLE_FONT_SCALE_DEFAULT) public void testFindClosestAllowedFontScale() { final String[] availableFontScales = new String[]{"0.5", "0.9", "1.0", "1.1", "1.5"}; @@ -266,6 +282,20 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest { return buffer.array(); } + private void restoreGlobalSettings(byte[] backupData) { + mAgentUnderTest.restoreSettings( + backupData, + /* pos= */ 0, + backupData.length, + Settings.Global.CONTENT_URI, + null, + null, + null, + R.array.restore_blocked_global_settings, + /* dynamicBlockList= */ Collections.emptySet(), + /* settingsToPreserve= */ Collections.emptySet()); + } + private byte[] generateUncorruptedHeader() throws IOException { try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { mAgentUnderTest.writeHeader(os); diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 9f3c2bfb237a..1d9f46971502 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -482,6 +482,15 @@ android:exported="true" android:theme="@style/Theme.AppCompat.NoActionBar"> <intent-filter> + <action android:name="com.android.systemui.action.TOUCHPAD_TUTORIAL"/> + <category android:name="android.intent.category.DEFAULT"/> + </intent-filter> + </activity> + + <activity android:name=".inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity" + android:exported="true" + android:theme="@style/Theme.AppCompat.NoActionBar"> + <intent-filter> <action android:name="com.android.systemui.action.TOUCHPAD_KEYBOARD_TUTORIAL"/> <category android:name="android.intent.category.DEFAULT"/> </intent-filter> diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java index 2e036e651d4e..6bea30fa8d08 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java +++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java @@ -348,7 +348,17 @@ public class A11yMenuOverlayLayout { /** Toggles a11y menu layout visibility. */ public void toggleVisibility() { - mLayout.setVisibility((mLayout.getVisibility() == View.VISIBLE) ? View.GONE : View.VISIBLE); + if (mLayout.getVisibility() == View.VISIBLE) { + mLayout.setVisibility(View.GONE); + } else { + if (Flags.hideRestrictedActions()) { + // Reconfigure the shortcut list in case the set of restricted actions has changed. + mA11yMenuViewPager.configureViewPagerAndFooter( + mLayout, createShortcutList(), getPageIndex()); + updateViewLayout(); + } + mLayout.setVisibility(View.VISIBLE); + } } /** Shows hint text on a minimal Snackbar-like text view. */ diff --git a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java index d16617fdc8e5..4ab771be55f4 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java +++ b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java @@ -542,8 +542,6 @@ public class AccessibilityMenuServiceTest { final Context context = sInstrumentation.getTargetContext(); final UserManager userManager = context.getSystemService(UserManager.class); userManager.setUserRestriction(restriction, isRestricted); - // Re-enable the service for the restriction to take effect. - enableA11yMenuService(context); } private static void unlockSignal() throws IOException { diff --git a/packages/SystemUI/aconfig/biometrics_framework.aconfig b/packages/SystemUI/aconfig/biometrics_framework.aconfig index e81d5d5ece5a..95e4b593a72f 100644 --- a/packages/SystemUI/aconfig/biometrics_framework.aconfig +++ b/packages/SystemUI/aconfig/biometrics_framework.aconfig @@ -3,9 +3,3 @@ container: "system" # NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors. -flag { - name: "constraint_bp" - namespace: "biometrics_framework" - description: "Refactors Biometric Prompt to use a ConstraintLayout" - bug: "288175072" -} diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index e6fae7b588ce..cdbac338e0be 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -156,17 +156,6 @@ flag { } flag { - name: "pss_app_selector_abrupt_exit_fix" - namespace: "systemui" - description: "Fixes the app selector abruptly disappearing without an animation, when the" - "selected task is the foreground task." - bug: "314385883" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { name: "pss_app_selector_recents_split_screen" namespace: "systemui" description: "Allows recent apps selected for partial screenshare to be launched in split screen mode" @@ -388,6 +377,16 @@ flag { } flag { + name: "status_bar_swipe_over_chip" + namespace: "systemui" + description: "Allow users to swipe over the status bar chip to open the shade" + bug: "185897191" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "compose_bouncer" namespace: "systemui" description: "Use the new compose bouncer in SystemUI" @@ -504,6 +503,16 @@ flag { } flag { + name: "status_bar_switch_to_spn_from_data_spn" + namespace: "systemui" + description: "Fix usage of the SPN broadcast extras" + bug: "350812372" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "haptic_volume_slider" namespace: "systemui" description: "Adds haptic feedback to the volume slider." @@ -1248,6 +1257,16 @@ flag { } flag { + name: "relock_with_power_button_immediately" + namespace: "systemui" + description: "UDFPS unlock followed by immediate power button push should relock" + bug: "343327511" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "lockscreen_preview_renderer_create_on_main_thread" namespace: "systemui" description: "Force preview renderer to be created on the main thread" @@ -1274,6 +1293,13 @@ flag { } flag { + name: "new_picker_ui" + namespace: "systemui" + description: "Enables the BC25 design of the customization picker UI." + bug: "339081035" +} + +flag { namespace: "systemui" name: "settings_ext_register_content_observer_on_bg_thread" description: "Register content observer in callback flow APIs on background thread in SettingsProxyExt." diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterContentObserverViaContentResolverDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterContentObserverViaContentResolverDetector.kt new file mode 100644 index 000000000000..8f5cdbf0b2bc --- /dev/null +++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterContentObserverViaContentResolverDetector.kt @@ -0,0 +1,105 @@ +/* + * 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.systemui.lint + +import com.android.tools.lint.detector.api.Category +import com.android.tools.lint.detector.api.Detector +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 com.android.tools.lint.detector.api.SourceCodeScanner +import com.intellij.psi.PsiMethod +import org.jetbrains.uast.UCallExpression +import org.jetbrains.uast.UClass +import org.jetbrains.uast.getParentOfType + +/** + * Checks if registerContentObserver/registerContentObserverAsUser/unregisterContentObserver is + * called on a ContentResolver (or subclasses), and directs the caller to using + * com.android.systemui.util.settings.SettingsProxy or its sub-classes. + */ +@Suppress("UnstableApiUsage") +class RegisterContentObserverViaContentResolverDetector : Detector(), SourceCodeScanner { + + override fun getApplicableMethodNames(): List<String> { + return CONTENT_RESOLVER_METHOD_LIST + } + + override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) { + val classQualifiedName = node.getParentOfType(UClass::class.java)?.qualifiedName + if (classQualifiedName in CLASSNAME_ALLOWLIST) { + // Don't warn for class we want the developers to use. + return + } + + val evaluator = context.evaluator + if (evaluator.isMemberInSubClassOf(method, "android.content.ContentResolver")) { + context.report( + issue = CONTENT_RESOLVER_ERROR, + location = context.getNameLocation(node), + message = + "`ContentResolver.${method.name}()` should be replaced with " + + "an appropriate interface API call, for eg. " + + "`<SettingsProxy>/<UserSettingsProxy>.${method.name}()`" + ) + } + } + + companion object { + @JvmField + val CONTENT_RESOLVER_ERROR: Issue = + Issue.create( + id = "RegisterContentObserverViaContentResolver", + briefDescription = + "Content observer registration done via `ContentResolver`" + + "instead of `SettingsProxy or child interfaces.`", + // lint trims indents and converts \ to line continuations + explanation = + """ + Use registerContentObserver/unregisterContentObserver methods in \ + `SettingsProxy`, `UserSettingsProxy` or `GlobalSettings` class instead of \ + using `ContentResolver.registerContentObserver` or \ + `ContentResolver.unregisterContentObserver`.""", + category = Category.PERFORMANCE, + priority = 10, + severity = Severity.ERROR, + implementation = + Implementation( + RegisterContentObserverViaContentResolverDetector::class.java, + Scope.JAVA_FILE_SCOPE + ) + ) + + private val CLASSNAME_ALLOWLIST = + listOf( + "com.android.systemui.util.settings.SettingsProxy", + "com.android.systemui.util.settings.UserSettingsProxy", + "com.android.systemui.util.settings.GlobalSettings", + "com.android.systemui.util.settings.SecureSettings", + "com.android.systemui.util.settings.SystemSettings" + ) + + private val CONTENT_RESOLVER_METHOD_LIST = + listOf( + "registerContentObserver", + "registerContentObserverAsUser", + "unregisterContentObserver" + ) + } +} diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt index 5206b05a3f4e..a1f4f5507e5f 100644 --- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt +++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt @@ -46,7 +46,8 @@ class SystemUIIssueRegistry : IssueRegistry() { DemotingTestWithoutBugDetector.ISSUE, TestFunctionNameViolationDetector.ISSUE, MissingApacheLicenseDetector.ISSUE, - RegisterContentObserverSyncViaSettingsProxyDetector.SYNC_WARNING + RegisterContentObserverSyncViaSettingsProxyDetector.SYNC_WARNING, + RegisterContentObserverViaContentResolverDetector.CONTENT_RESOLVER_ERROR ) override val api: Int diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterContentObserverViaContentResolverDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterContentObserverViaContentResolverDetectorTest.kt new file mode 100644 index 000000000000..1d33bce8dea8 --- /dev/null +++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterContentObserverViaContentResolverDetectorTest.kt @@ -0,0 +1,207 @@ +/* + * 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.systemui.lint + +import com.android.tools.lint.checks.infrastructure.TestFiles +import com.android.tools.lint.detector.api.Detector +import com.android.tools.lint.detector.api.Issue +import org.junit.Test + +class RegisterContentObserverViaContentResolverDetectorTest : SystemUILintDetectorTest() { + + override fun getDetector(): Detector = RegisterContentObserverViaContentResolverDetector() + + override fun getIssues(): List<Issue> = + listOf(RegisterContentObserverViaContentResolverDetector.CONTENT_RESOLVER_ERROR) + + @Test + fun testRegisterContentObserver_throwError() { + lint() + .files( + TestFiles.java( + """ + package test.pkg; + import android.content.Context; + + public class TestClass { + public void register(Context context) { + context.getContentResolver(). + registerContentObserver(Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED), + false, mSettingObserver); + } + } + """ + ) + .indented(), + *androidStubs + ) + .issues(RegisterContentObserverViaContentResolverDetector.CONTENT_RESOLVER_ERROR) + .run() + .expect( + """ + src/test/pkg/TestClass.java:7: Error: ContentResolver.registerContentObserver() should be replaced with an appropriate interface API call, for eg. <SettingsProxy>/<UserSettingsProxy>.registerContentObserver() [RegisterContentObserverViaContentResolver] + registerContentObserver(Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED), + ~~~~~~~~~~~~~~~~~~~~~~~ + 1 errors, 0 warnings + """ + .trimIndent() + ) + } + + @Test + fun testRegisterContentObserverForUser_throwError() { + lint() + .files( + TestFiles.java( + """ + package test.pkg; + import android.content.Context; + + public class TestClass { + public void register(Context context) { + context.getContentResolver(). + registerContentObserverAsUser(Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED), + false, mSettingObserver); + } + } + """ + ) + .indented(), + *androidStubs + ) + .issues(RegisterContentObserverViaContentResolverDetector.CONTENT_RESOLVER_ERROR) + .run() + .expect( + """ + src/test/pkg/TestClass.java:7: Error: ContentResolver.registerContentObserverAsUser() should be replaced with an appropriate interface API call, for eg. <SettingsProxy>/<UserSettingsProxy>.registerContentObserverAsUser() [RegisterContentObserverViaContentResolver] + registerContentObserverAsUser(Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED), + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +1 errors, 0 warnings + """ + .trimIndent() + ) + } + + @Test + fun testSuppressRegisterContentObserver() { + lint() + .files( + TestFiles.java( + """ + package test.pkg; + import android.content.Context; + + public class TestClass { + @SuppressWarnings("RegisterContentObserverViaContentResolver") + public void register(Context context) { + context.getContentResolver(). + registerContentObserver(Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED), + false, mSettingObserver); + } + } + """ + ) + .indented(), + *androidStubs + ) + .issues(RegisterContentObserverViaContentResolverDetector.CONTENT_RESOLVER_ERROR) + .run() + .expectClean() + } + + @Test + fun testRegisterContentObserverInSettingsProxy_allowed() { + lint() + .files( + TestFiles.java( + """ + package com.android.systemui.util.settings; + import android.content.Context; + + public class SettingsProxy { + public void register(Context context) { + context.getContentResolver(). + registerContentObserver(Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED), + false, mSettingObserver); + } + } + """ + ) + .indented(), + *androidStubs + ) + .issues(RegisterContentObserverViaContentResolverDetector.CONTENT_RESOLVER_ERROR) + .run() + .expectClean() + } + + @Test + fun testNoopIfNoCall() { + lint() + .files( + TestFiles.java( + """ + package test.pkg; + import android.content.Context; + + public class SettingsProxy { + public void register(Context context) { + } + } + """ + ) + .indented(), + *androidStubs + ) + .issues(RegisterContentObserverViaContentResolverDetector.CONTENT_RESOLVER_ERROR) + .run() + .expectClean() + } + + @Test + fun testUnRegisterContentObserver_throwError() { + lint() + .files( + TestFiles.java( + """ + package test.pkg; + import android.content.Context; + + public class TestClass { + public void register(Context context) { + context.getContentResolver(). + unregisterContentObserver(mSettingObserver); + } + } + """ + ) + .indented(), + *androidStubs + ) + .issues(RegisterContentObserverViaContentResolverDetector.CONTENT_RESOLVER_ERROR) + .run() + .expect( + """ + src/test/pkg/TestClass.java:7: Error: ContentResolver.unregisterContentObserver() should be replaced with an appropriate interface API call, for eg. <SettingsProxy>/<UserSettingsProxy>.unregisterContentObserver() [RegisterContentObserverViaContentResolver] + unregisterContentObserver(mSettingObserver); + ~~~~~~~~~~~~~~~~~~~~~~~~~ + 1 errors, 0 warnings + """ + .trimIndent() + ) + } +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt index 8c38253d6469..6fca1785d768 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt @@ -28,6 +28,7 @@ import com.android.compose.theme.LocalAndroidColorScheme import com.android.systemui.communal.smartspace.SmartspaceInteractionHandler import com.android.systemui.communal.ui.compose.section.AmbientStatusBarSection import com.android.systemui.communal.ui.compose.section.CommunalPopupSection +import com.android.systemui.communal.ui.view.layout.sections.CommunalAppWidgetSection import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.keyguard.ui.composable.blueprint.BlueprintAlignmentLines import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection @@ -46,6 +47,7 @@ constructor( private val bottomAreaSection: BottomAreaSection, private val ambientStatusBarSection: AmbientStatusBarSection, private val communalPopupSection: CommunalPopupSection, + private val widgetSection: CommunalAppWidgetSection, ) { @Composable @@ -63,6 +65,7 @@ constructor( viewModel = viewModel, interactionHandler = interactionHandler, dialogFactory = dialogFactory, + widgetSection = widgetSection, modifier = Modifier.element(Communal.Elements.Grid) ) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt index b65b47123eaa..b93b0490816d 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt @@ -19,10 +19,7 @@ package com.android.systemui.communal.ui.compose import android.content.Context import android.content.res.Configuration import android.graphics.drawable.Icon -import android.os.Bundle import android.util.SizeF -import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_AUTO -import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS import android.widget.FrameLayout import android.widget.RemoteViews import androidx.annotation.VisibleForTesting @@ -105,7 +102,6 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment -import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.geometry.CornerRadius @@ -169,6 +165,7 @@ import com.android.systemui.communal.ui.compose.extensions.allowGestures import com.android.systemui.communal.ui.compose.extensions.detectLongPressGesture import com.android.systemui.communal.ui.compose.extensions.firstItemAtOffset import com.android.systemui.communal.ui.compose.extensions.observeTaps +import com.android.systemui.communal.ui.view.layout.sections.CommunalAppWidgetSection import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel import com.android.systemui.communal.ui.viewmodel.CommunalViewModel @@ -180,11 +177,12 @@ import com.android.systemui.res.R import com.android.systemui.statusbar.phone.SystemUIDialogFactory import kotlinx.coroutines.launch -@OptIn(ExperimentalComposeUiApi::class, ExperimentalMaterial3Api::class) +@OptIn(ExperimentalMaterial3Api::class) @Composable fun CommunalHub( modifier: Modifier = Modifier, viewModel: BaseCommunalViewModel, + widgetSection: CommunalAppWidgetSection, interactionHandler: RemoteViews.InteractionHandler? = null, dialogFactory: SystemUIDialogFactory? = null, widgetConfigurator: WidgetConfigurator? = null, @@ -383,6 +381,7 @@ fun CommunalHub( selectedKey = selectedKey, widgetConfigurator = widgetConfigurator, interactionHandler = interactionHandler, + widgetSection = widgetSection, ) } } @@ -632,6 +631,7 @@ private fun BoxScope.CommunalHubLazyGrid( updateDragPositionForRemove: (offset: Offset) -> Boolean, widgetConfigurator: WidgetConfigurator?, interactionHandler: RemoteViews.InteractionHandler?, + widgetSection: CommunalAppWidgetSection, ) { var gridModifier = Modifier.align(Alignment.TopStart).onGloballyPositioned { setGridCoordinates(it) } @@ -719,18 +719,20 @@ private fun BoxScope.CommunalHubLazyGrid( index = index, contentListState = contentListState, interactionHandler = interactionHandler, + widgetSection = widgetSection, ) } } else { CommunalContent( - modifier = cardModifier.animateItem(), model = list[index], viewModel = viewModel, size = size, selected = false, + modifier = cardModifier.animateItem(), index = index, contentListState = contentListState, interactionHandler = interactionHandler, + widgetSection = widgetSection, ) } } @@ -972,6 +974,7 @@ private fun CommunalContent( index: Int, contentListState: ContentListState, interactionHandler: RemoteViews.InteractionHandler?, + widgetSection: CommunalAppWidgetSection, ) { when (model) { is CommunalContentModel.WidgetContent.Widget -> @@ -983,7 +986,8 @@ private fun CommunalContent( widgetConfigurator, modifier, index, - contentListState + contentListState, + widgetSection, ) is CommunalContentModel.WidgetPlaceholder -> HighlightedItem(modifier) is CommunalContentModel.WidgetContent.DisabledWidget -> @@ -1116,9 +1120,9 @@ private fun WidgetContent( modifier: Modifier = Modifier, index: Int, contentListState: ContentListState, + widgetSection: CommunalAppWidgetSection, ) { val context = LocalContext.current - val isFocusable by viewModel.isFocusable.collectAsStateWithLifecycle(initialValue = false) val accessibilityLabel = remember(model, context) { model.providerInfo.loadLabel(context.packageManager).toString().trim() @@ -1205,36 +1209,14 @@ private fun WidgetContent( } } ) { - AndroidView( - modifier = Modifier.fillMaxSize().allowGestures(allowed = !viewModel.isEditMode), - factory = { context -> - model.appWidgetHost - .createViewForCommunal(context, model.appWidgetId, model.providerInfo) - .apply { - updateAppWidgetSize( - /* newOptions = */ Bundle(), - /* minWidth = */ size.width.toInt(), - /* minHeight = */ size.height.toInt(), - /* maxWidth = */ size.width.toInt(), - /* maxHeight = */ size.height.toInt(), - /* ignorePadding = */ true - ) - accessibilityDelegate = viewModel.widgetAccessibilityDelegate - } - }, - update = { - it.apply { - importantForAccessibility = - if (isFocusable) { - IMPORTANT_FOR_ACCESSIBILITY_AUTO - } else { - IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS - } - } - }, - // For reusing composition in lazy lists. - onReset = {}, - ) + with(widgetSection) { + Widget( + viewModel = viewModel, + model = model, + size = size, + modifier = Modifier.fillMaxSize().allowGestures(allowed = !viewModel.isEditMode), + ) + } if ( viewModel is CommunalEditModeViewModel && model.reconfigurable && diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt index 5886d7de47b9..6750e41009c7 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt @@ -23,6 +23,7 @@ import com.android.compose.animation.scene.Swipe import com.android.compose.animation.scene.SwipeDirection import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult +import com.android.systemui.communal.ui.view.layout.sections.CommunalAppWidgetSection import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.communal.widgets.WidgetInteractionHandler import com.android.systemui.dagger.SysUISingleton @@ -42,6 +43,7 @@ constructor( private val viewModel: CommunalViewModel, private val dialogFactory: SystemUIDialogFactory, private val interactionHandler: WidgetInteractionHandler, + private val widgetSection: CommunalAppWidgetSection, ) : ComposableScene { override val key = Scenes.Communal @@ -55,6 +57,12 @@ constructor( @Composable override fun SceneScope.Content(modifier: Modifier) { - CommunalHub(modifier, viewModel, interactionHandler, dialogFactory) + CommunalHub( + modifier = modifier, + viewModel = viewModel, + interactionHandler = interactionHandler, + widgetSection = widgetSection, + dialogFactory = dialogFactory, + ) } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/AlternateBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/AlternateBouncer.kt new file mode 100644 index 000000000000..04bcc3624532 --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/AlternateBouncer.kt @@ -0,0 +1,198 @@ +/* + * 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.ui.composable + +import androidx.activity.compose.BackHandler +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.Crossfade +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.foundation.gestures.detectTapGestures +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.viewinterop.AndroidView +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.android.compose.modifiers.background +import com.android.compose.modifiers.height +import com.android.compose.modifiers.width +import com.android.systemui.deviceentry.shared.model.BiometricMessage +import com.android.systemui.deviceentry.ui.binder.UdfpsAccessibilityOverlayBinder +import com.android.systemui.deviceentry.ui.view.UdfpsAccessibilityOverlay +import com.android.systemui.deviceentry.ui.viewmodel.AlternateBouncerUdfpsAccessibilityOverlayViewModel +import com.android.systemui.keyguard.ui.binder.AlternateBouncerUdfpsViewBinder +import com.android.systemui.keyguard.ui.view.DeviceEntryIconView +import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerDependencies +import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerMessageAreaViewModel +import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerUdfpsIconViewModel +import com.android.systemui.res.R +import kotlinx.coroutines.ExperimentalCoroutinesApi + +@ExperimentalCoroutinesApi +@Composable +fun AlternateBouncer( + alternateBouncerDependencies: AlternateBouncerDependencies, + modifier: Modifier = Modifier, +) { + + val isVisible by + alternateBouncerDependencies.viewModel.isVisible.collectAsStateWithLifecycle( + initialValue = false + ) + + val udfpsIconLocation by + alternateBouncerDependencies.udfpsIconViewModel.iconLocation.collectAsStateWithLifecycle( + initialValue = null + ) + + // TODO (b/353955910): back handling doesn't work + BackHandler { alternateBouncerDependencies.viewModel.onBackRequested() } + + AnimatedVisibility( + visible = isVisible, + enter = fadeIn(), + exit = fadeOut(), + modifier = modifier, + ) { + Box( + contentAlignment = Alignment.TopCenter, + modifier = + Modifier.background(color = Colors.AlternateBouncerBackgroundColor, alpha = { 1f }) + .pointerInput(Unit) { + detectTapGestures( + onTap = { alternateBouncerDependencies.viewModel.onTapped() } + ) + }, + ) { + StatusMessage( + viewModel = alternateBouncerDependencies.messageAreaViewModel, + ) + } + + udfpsIconLocation?.let { udfpsLocation -> + Box { + DeviceEntryIcon( + viewModel = alternateBouncerDependencies.udfpsIconViewModel, + modifier = + Modifier.width { udfpsLocation.width } + .height { udfpsLocation.height } + .fillMaxHeight() + .offset { + IntOffset( + x = udfpsLocation.left, + y = udfpsLocation.top, + ) + }, + ) + } + + UdfpsA11yOverlay( + viewModel = alternateBouncerDependencies.udfpsAccessibilityOverlayViewModel.get(), + modifier = Modifier.fillMaxHeight(), + ) + } + } +} + +@ExperimentalCoroutinesApi +@Composable +private fun StatusMessage( + viewModel: AlternateBouncerMessageAreaViewModel, + modifier: Modifier = Modifier, +) { + val message: BiometricMessage? by + viewModel.message.collectAsStateWithLifecycle(initialValue = null) + + Crossfade( + targetState = message, + label = "Alternate Bouncer message", + animationSpec = tween(), + modifier = modifier, + ) { biometricMessage -> + biometricMessage?.let { + Text( + textAlign = TextAlign.Center, + text = it.message ?: "", + color = Colors.AlternateBouncerTextColor, + fontSize = 18.sp, + lineHeight = 24.sp, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.padding(top = 92.dp), + ) + } + } +} + +@ExperimentalCoroutinesApi +@Composable +private fun DeviceEntryIcon( + viewModel: AlternateBouncerUdfpsIconViewModel, + modifier: Modifier = Modifier, +) { + AndroidView( + modifier = modifier, + factory = { context -> + val view = + DeviceEntryIconView(context, null).apply { + id = R.id.alternate_bouncer_udfps_icon_view + contentDescription = + context.resources.getString(R.string.accessibility_fingerprint_label) + } + AlternateBouncerUdfpsViewBinder.bind(view, viewModel) + view + }, + ) +} + +/** TODO (b/353955910): Validate accessibility CUJs */ +@ExperimentalCoroutinesApi +@Composable +private fun UdfpsA11yOverlay( + viewModel: AlternateBouncerUdfpsAccessibilityOverlayViewModel, + modifier: Modifier = Modifier, +) { + AndroidView( + factory = { context -> + val view = + UdfpsAccessibilityOverlay(context).apply { + id = R.id.alternate_bouncer_udfps_accessibility_overlay + } + UdfpsAccessibilityOverlayBinder.bind(view, viewModel) + view + }, + modifier = modifier, + ) +} + +private object Colors { + val AlternateBouncerBackgroundColor: Color = Color.Black.copy(alpha = .66f) + val AlternateBouncerTextColor: Color = Color.White +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt index 4e117d6ff4db..6801cf27a64d 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt @@ -32,6 +32,7 @@ import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.ui.composable.modifier.burnInAware import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters +import com.android.systemui.lifecycle.rememberViewModel import com.android.systemui.notifications.ui.composable.ConstrainedNotificationStack import com.android.systemui.shade.LargeScreenHeaderHelper import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout @@ -48,7 +49,7 @@ class NotificationSection @Inject constructor( private val stackScrollView: Lazy<NotificationScrollView>, - private val viewModel: NotificationsPlaceholderViewModel, + private val viewModelFactory: NotificationsPlaceholderViewModel.Factory, private val aodBurnInViewModel: AodBurnInViewModel, sharedNotificationContainer: SharedNotificationContainer, sharedNotificationContainerViewModel: SharedNotificationContainerViewModel, @@ -98,7 +99,7 @@ constructor( ConstrainedNotificationStack( stackScrollView = stackScrollView.get(), - viewModel = viewModel, + viewModel = rememberViewModel { viewModelFactory.create() }, modifier = modifier .fillMaxWidth() diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt index 26ab10b459d8..808e6666e6e6 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt @@ -24,9 +24,10 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.approachLayout import androidx.compose.ui.layout.layout -import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.viewinterop.AndroidView import com.android.compose.animation.scene.MovableElementKey import com.android.compose.animation.scene.SceneScope @@ -51,38 +52,48 @@ fun SceneScope.MediaCarousel( mediaHost: MediaHost, modifier: Modifier = Modifier, carouselController: MediaCarouselController, + offsetProvider: (() -> IntOffset)? = null, ) { if (!isVisible) { return } - val density = LocalDensity.current val mediaHeight = dimensionResource(R.dimen.qs_media_session_height_expanded) - val layoutWidth = 0 - val layoutHeight = with(density) { mediaHeight.toPx() }.toInt() - - // Notify controller to size the carousel for the current space - mediaHost.measurementInput = MeasurementInput(layoutWidth, layoutHeight) - carouselController.setSceneContainerSize(layoutWidth, layoutHeight) - MovableElement( key = MediaCarousel.Elements.Content, - modifier = modifier.height(mediaHeight).fillMaxWidth() + modifier = modifier.height(mediaHeight).fillMaxWidth(), ) { content { AndroidView( modifier = - Modifier.fillMaxSize().layout { measurable, constraints -> - val placeable = measurable.measure(constraints) + Modifier.fillMaxSize() + .approachLayout( + isMeasurementApproachInProgress = { offsetProvider != null }, + approachMeasure = { measurable, constraints -> + val placeable = measurable.measure(constraints) + layout(placeable.width, placeable.height) { + placeable.placeRelative( + offsetProvider?.invoke() ?: IntOffset.Zero + ) + } + } + ) + .layout { measurable, constraints -> + val placeable = measurable.measure(constraints) - // Notify controller to size the carousel for the current space - mediaHost.measurementInput = - MeasurementInput(placeable.width, placeable.height) - carouselController.setSceneContainerSize(placeable.width, placeable.height) + // Notify controller to size the carousel for the current space + mediaHost.measurementInput = + MeasurementInput(placeable.width, placeable.height) + carouselController.setSceneContainerSize( + placeable.width, + placeable.height + ) - layout(placeable.width, placeable.height) { placeable.placeRelative(0, 0) } - }, + layout(placeable.width, placeable.height) { + placeable.placeRelative(0, 0) + } + }, factory = { context -> FrameLayout(context).apply { layoutParams = diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaContentPicker.kt b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaContentPicker.kt index 3f04f3728bef..70c0db1582c4 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaContentPicker.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaContentPicker.kt @@ -27,7 +27,6 @@ import com.android.systemui.scene.shared.model.Scenes /** [ElementContentPicker] implementation for the media carousel object. */ object MediaContentPicker : StaticElementContentPicker { - const val SHADE_FRACTION = 0.66f override val contents = setOf( Scenes.Lockscreen, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt index 66be7bc83c64..0cb8bd3a7efb 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt @@ -20,10 +20,8 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.SceneScope import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult @@ -32,7 +30,6 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.ui.composable.LockscreenContent import com.android.systemui.lifecycle.rememberViewModel import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeSceneActionsViewModel -import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeSceneContentViewModel import com.android.systemui.scene.session.ui.composable.SaveableSession import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.ui.composable.ComposableScene @@ -54,11 +51,10 @@ import kotlinx.coroutines.flow.Flow class NotificationsShadeScene @Inject constructor( - private val contentViewModelFactory: NotificationsShadeSceneContentViewModel.Factory, private val actionsViewModelFactory: NotificationsShadeSceneActionsViewModel.Factory, private val overlayShadeViewModelFactory: OverlayShadeViewModel.Factory, private val shadeHeaderViewModelFactory: ShadeHeaderViewModel.Factory, - private val notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel, + private val notificationsPlaceholderViewModelFactory: NotificationsPlaceholderViewModel.Factory, private val tintedIconManagerFactory: TintedIconManager.Factory, private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory, private val statusBarIconController: StatusBarIconController, @@ -84,8 +80,9 @@ constructor( override fun SceneScope.Content( modifier: Modifier, ) { - val viewModel = rememberViewModel { contentViewModelFactory.create() } - val isEmptySpaceClickable by viewModel.isEmptySpaceClickable.collectAsStateWithLifecycle() + val notificationsPlaceholderViewModel = rememberViewModel { + notificationsPlaceholderViewModelFactory.create() + } OverlayShade( modifier = modifier, @@ -110,8 +107,6 @@ constructor( shouldFillMaxSize = false, shouldReserveSpaceForNavBar = false, shadeMode = ShadeMode.Dual, - onEmptySpaceClick = - viewModel::onEmptySpaceClicked.takeIf { isEmptySpaceClickable }, modifier = Modifier.fillMaxWidth(), ) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt index 8bba0f4f3651..12edd049c0e1 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt @@ -121,7 +121,7 @@ constructor( private val shadeSession: SaveableSession, private val notificationStackScrollView: Lazy<NotificationScrollView>, private val viewModel: QuickSettingsSceneViewModel, - private val notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel, + private val notificationsPlaceholderViewModelFactory: NotificationsPlaceholderViewModel.Factory, private val tintedIconManagerFactory: TintedIconManager.Factory, private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory, private val statusBarIconController: StatusBarIconController, @@ -140,7 +140,8 @@ constructor( QuickSettingsScene( notificationStackScrollView = notificationStackScrollView.get(), viewModel = viewModel, - notificationsPlaceholderViewModel = notificationsPlaceholderViewModel, + notificationsPlaceholderViewModel = + rememberViewModel { notificationsPlaceholderViewModelFactory.create() }, createTintedIconManager = tintedIconManagerFactory::create, createBatteryMeterViewController = batteryMeterViewControllerFactory::create, statusBarIconController = statusBarIconController, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt index b0c3fb31256f..f15e87b4514d 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt @@ -26,6 +26,7 @@ import com.android.compose.animation.scene.UserActionResult import com.android.compose.animation.scene.animateSceneDpAsState import com.android.compose.animation.scene.animateSceneFloatAsState import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.lifecycle.rememberViewModel import com.android.systemui.notifications.ui.composable.SnoozeableHeadsUpNotificationSpace import com.android.systemui.qs.ui.composable.QuickSettings import com.android.systemui.qs.ui.composable.QuickSettings.SharedValues.MediaLandscapeTopOffset @@ -47,7 +48,7 @@ class GoneScene @Inject constructor( private val notificationStackScrolLView: Lazy<NotificationScrollView>, - private val notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel, + private val notificationsPlaceholderViewModelFactory: NotificationsPlaceholderViewModel.Factory, private val viewModelFactory: GoneSceneActionsViewModel.Factory, ) : ComposableScene { override val key = Scenes.Gone @@ -73,7 +74,7 @@ constructor( Spacer(modifier.fillMaxSize()) SnoozeableHeadsUpNotificationSpace( stackScrollView = notificationStackScrolLView.get(), - viewModel = notificationsPlaceholderViewModel + viewModel = rememberViewModel { notificationsPlaceholderViewModelFactory.create() }, ) } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt index a9da733116fc..71fa6c9e567a 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt @@ -25,7 +25,6 @@ import com.android.compose.animation.scene.TransitionBuilder import com.android.compose.animation.scene.UserActionDistance import com.android.compose.animation.scene.UserActionDistanceScope import com.android.systemui.media.controls.ui.composable.MediaCarousel -import com.android.systemui.media.controls.ui.composable.MediaContentPicker import com.android.systemui.notifications.ui.composable.Notifications import com.android.systemui.qs.ui.composable.QuickSettings import com.android.systemui.shade.ui.composable.Shade @@ -54,13 +53,7 @@ fun TransitionBuilder.goneToSplitShadeTransition( fractionRange(end = .33f) { fade(Shade.Elements.BackgroundScrim) } fractionRange(start = .33f) { - val qsTranslation = - ShadeHeader.Dimensions.CollapsedHeight * MediaContentPicker.SHADE_FRACTION - val qsExpansionDiff = - ShadeHeader.Dimensions.ExpandedHeight - ShadeHeader.Dimensions.CollapsedHeight - translate(MediaCarousel.Elements.Content, y = -(qsExpansionDiff + qsTranslation)) fade(MediaCarousel.Elements.Content) - fade(ShadeHeader.Elements.Clock) fade(ShadeHeader.Elements.CollapsedContentStart) fade(ShadeHeader.Elements.CollapsedContentEnd) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt index 21dfc49cbe7b..b677dff2dcf9 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt @@ -26,7 +26,6 @@ import com.android.compose.animation.scene.TransitionBuilder import com.android.compose.animation.scene.UserActionDistance import com.android.compose.animation.scene.UserActionDistanceScope import com.android.systemui.media.controls.ui.composable.MediaCarousel -import com.android.systemui.media.controls.ui.composable.MediaContentPicker import com.android.systemui.notifications.ui.composable.Notifications import com.android.systemui.qs.ui.composable.QuickSettings import com.android.systemui.scene.shared.model.Scenes @@ -62,12 +61,9 @@ fun TransitionBuilder.toShadeTransition( fade(QuickSettings.Elements.FooterActions) } - val qsTranslation = ShadeHeader.Dimensions.CollapsedHeight * MediaContentPicker.SHADE_FRACTION - val qsExpansionDiff = - ShadeHeader.Dimensions.ExpandedHeight - ShadeHeader.Dimensions.CollapsedHeight - - translate(QuickSettings.Elements.QuickQuickSettings, y = -qsTranslation) - translate(MediaCarousel.Elements.Content, y = -(qsExpansionDiff + qsTranslation)) + val qsTranslation = -ShadeHeader.Dimensions.CollapsedHeight * 0.66f + translate(QuickSettings.Elements.QuickQuickSettings, y = qsTranslation) + translate(MediaCarousel.Elements.Content, y = qsTranslation) translate(Notifications.Elements.NotificationScrim, Edge.Top, false) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeMediaOffsetProvider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeMediaOffsetProvider.kt new file mode 100644 index 000000000000..e0b7909dcaa3 --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeMediaOffsetProvider.kt @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.shade.ui.composable + +import androidx.compose.ui.unit.IntOffset +import com.android.systemui.qs.ui.adapter.QSSceneAdapter + +/** + * Provider for the extra offset for the Media section in the shade to accommodate for the squishing + * qs or qqs tiles. + */ +interface ShadeMediaOffsetProvider { + + /** Returns current offset to be applied to the Media Carousel */ + val offset: IntOffset + + /** + * [ShadeMediaOffsetProvider] implementation for Quick Settings. + * + * [updateLayout] should represent an access to some state to trigger Compose to relayout to + * track [QSSceneAdapter] internal state changes during the transition. + */ + class Qs(private val updateLayout: () -> Unit, private val qsSceneAdapter: QSSceneAdapter) : + ShadeMediaOffsetProvider { + + override val offset: IntOffset + get() = + calculateQsOffset( + updateLayout, + qsSceneAdapter.qsHeight, + qsSceneAdapter.squishedQsHeight + ) + } + + /** + * [ShadeMediaOffsetProvider] implementation for Quick Quick Settings. + * + * [updateLayout] should represent an access to some state to trigger Compose to relayout to + * track [QSSceneAdapter] internal state changes during the transition. + */ + class Qqs(private val updateLayout: () -> Unit, private val qsSceneAdapter: QSSceneAdapter) : + ShadeMediaOffsetProvider { + + override val offset: IntOffset + get() = + calculateQsOffset( + updateLayout, + qsSceneAdapter.qqsHeight, + qsSceneAdapter.squishedQqsHeight + ) + } + + companion object { + + protected fun calculateQsOffset( + updateLayout: () -> Unit, + qsHeight: Int, + qsSquishedHeight: Int + ): IntOffset { + updateLayout() + val distanceFromBottomToActualBottom = qsHeight - qsSquishedHeight + return IntOffset(0, -distanceFromBottomToActualBottom) + } + } +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt index 0e3fcf4598af..7920e74eff01 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt @@ -118,7 +118,6 @@ import kotlinx.coroutines.flow.Flow object Shade { object Elements { - val MediaCarousel = ElementKey("ShadeMediaCarousel") val BackgroundScrim = ElementKey("ShadeBackgroundScrim", contentPicker = LowestZIndexContentPicker) val SplitShadeStartColumn = ElementKey("SplitShadeStartColumn") @@ -149,7 +148,7 @@ constructor( private val notificationStackScrollView: Lazy<NotificationScrollView>, private val actionsViewModelFactory: ShadeSceneActionsViewModel.Factory, private val contentViewModelFactory: ShadeSceneContentViewModel.Factory, - private val notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel, + private val notificationsPlaceholderViewModelFactory: NotificationsPlaceholderViewModel.Factory, private val tintedIconManagerFactory: TintedIconManager.Factory, private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory, private val statusBarIconController: StatusBarIconController, @@ -178,7 +177,8 @@ constructor( ShadeScene( notificationStackScrollView.get(), viewModel = rememberViewModel { contentViewModelFactory.create() }, - notificationsPlaceholderViewModel = notificationsPlaceholderViewModel, + notificationsPlaceholderViewModel = + rememberViewModel { notificationsPlaceholderViewModelFactory.create() }, createTintedIconManager = tintedIconManagerFactory::create, createBatteryMeterViewController = batteryMeterViewControllerFactory::create, statusBarIconController = statusBarIconController, @@ -283,6 +283,13 @@ private fun SceneScope.SingleShade( val navBarHeight = WindowInsets.systemBars.asPaddingValues().calculateBottomPadding() + val mediaOffsetProvider = remember { + ShadeMediaOffsetProvider.Qqs( + { @Suppress("UNUSED_EXPRESSION") tileSquishiness }, + viewModel.qsSceneAdapter, + ) + } + Box( modifier = modifier.thenIf(shouldPunchHoleBehindScrim) { @@ -335,12 +342,12 @@ private fun SceneScope.SingleShade( ) } - MediaCarousel( + ShadeMediaCarousel( isVisible = isMediaVisible, mediaHost = mediaHost, + mediaOffsetProvider = mediaOffsetProvider, modifier = - Modifier.fillMaxWidth() - .layoutId(QSMediaMeasurePolicy.LayoutId.Media), + Modifier.layoutId(QSMediaMeasurePolicy.LayoutId.Media), carouselController = mediaCarouselController, ) } @@ -497,6 +504,13 @@ private fun SceneScope.SplitShade( val brightnessMirrorShowingModifier = Modifier.graphicsLayer { alpha = contentAlpha } + val mediaOffsetProvider = remember { + ShadeMediaOffsetProvider.Qs( + { @Suppress("UNUSED_EXPRESSION") tileSquishiness }, + viewModel.qsSceneAdapter, + ) + } + Box { Box( modifier = @@ -570,11 +584,13 @@ private fun SceneScope.SplitShade( squishiness = { tileSquishiness }, ) } - MediaCarousel( + + ShadeMediaCarousel( isVisible = isMediaVisible, mediaHost = mediaHost, + mediaOffsetProvider = mediaOffsetProvider, modifier = - Modifier.fillMaxWidth().thenIf( + Modifier.thenIf( MediaContentPicker.shouldElevateMedia(layoutState) ) { Modifier.zIndex(1f) @@ -620,3 +636,25 @@ private fun SceneScope.SplitShade( ) } } + +@Composable +private fun SceneScope.ShadeMediaCarousel( + isVisible: Boolean, + mediaHost: MediaHost, + carouselController: MediaCarouselController, + mediaOffsetProvider: ShadeMediaOffsetProvider, + modifier: Modifier = Modifier, +) { + MediaCarousel( + modifier = modifier.fillMaxWidth(), + isVisible = isVisible, + mediaHost = mediaHost, + carouselController = carouselController, + offsetProvider = + if (MediaContentPicker.shouldElevateMedia(layoutState)) { + null + } else { + { mediaOffsetProvider.offset } + } + ) +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt index 9da2a1b06f30..5ffb6f82fbba 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt @@ -50,7 +50,7 @@ fun VolumePanelRoot( ) { val accessibilityTitle = stringResource(R.string.accessibility_volume_settings) val state: VolumePanelState by viewModel.volumePanelState.collectAsStateWithLifecycle() - val components by viewModel.componentsLayout.collectAsStateWithLifecycle(null) + val components by viewModel.componentsLayout.collectAsStateWithLifecycle() with(VolumePanelComposeScope(state)) { components?.let { componentsState -> diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt new file mode 100644 index 000000000000..5eabd2275285 --- /dev/null +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.compose.animation.scene + +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.AnimationVector1D +import androidx.compose.animation.core.SpringSpec +import com.android.compose.animation.scene.content.state.ContentState +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.CoroutineStart +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch + +internal fun CoroutineScope.animateContent( + transition: ContentState.Transition<*>, + oneOffAnimation: OneOffAnimation, + targetProgress: Float, + startTransition: () -> Unit, + finishTransition: () -> Unit, +) { + // Start the transition. This will compute the TransformationSpec associated to [transition], + // which we need to initialize the Animatable that will actually animate it. + startTransition() + + // The transition now contains the transformation spec that we should use to instantiate the + // Animatable. + val animationSpec = transition.transformationSpec.progressSpec + val visibilityThreshold = + (animationSpec as? SpringSpec)?.visibilityThreshold ?: ProgressVisibilityThreshold + val replacedTransition = transition.replacedTransition + val initialProgress = replacedTransition?.progress ?: 0f + val initialVelocity = replacedTransition?.progressVelocity ?: 0f + val animatable = + Animatable(initialProgress, visibilityThreshold = visibilityThreshold).also { + oneOffAnimation.animatable = it + } + + // Animate the progress to its target value. + // + // Important: We start atomically to make sure that we start the coroutine even if it is + // cancelled right after it is launched, so that finishTransition() is correctly called. + // Otherwise, this transition will never be stopped and we will never settle to Idle. + oneOffAnimation.job = + launch(start = CoroutineStart.ATOMIC) { + try { + animatable.animateTo(targetProgress, animationSpec, initialVelocity) + } finally { + finishTransition() + } + } +} + +internal class OneOffAnimation { + /** + * The animatable used to animate this transition. + * + * Note: This is lateinit because we need to first create this object so that + * [SceneTransitionLayoutState] can compute the transformations and animation spec associated to + * the transition, which is needed to initialize this Animatable. + */ + lateinit var animatable: Animatable<Float, AnimationVector1D> + + /** The job that is animating [animatable]. */ + lateinit var job: Job + + val progress: Float + get() = animatable.value + + val progressVelocity: Float + get() = animatable.velocity + + fun finish(): Job = job +} + +// TODO(b/290184746): Compute a good default visibility threshold that depends on the layout size +// and screen density. +internal const val ProgressVisibilityThreshold = 1e-3f diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt index e13ca39149f3..ae5a84b859e5 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt @@ -120,7 +120,7 @@ fun ContentScope.animateContentFloatAsState( } @Deprecated( - "Use animateSceneFloatAsState() instead", + "Use animateContentFloatAsState() instead", replaceWith = ReplaceWith("animateContentFloatAsState(value, key, canOverflow)") ) @Composable @@ -171,7 +171,7 @@ fun ContentScope.animateContentDpAsState( } @Deprecated( - "Use animateSceneDpAsState() instead", + "Use animateContentDpAsState() instead", replaceWith = ReplaceWith("animateContentDpAsState(value, key, canOverflow)") ) @Composable diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt index 1fc1f989b095..68a6c9836875 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt @@ -16,15 +16,10 @@ package com.android.compose.animation.scene -import androidx.compose.animation.core.Animatable -import androidx.compose.animation.core.AnimationVector1D -import androidx.compose.animation.core.SpringSpec import com.android.compose.animation.scene.content.state.TransitionState import kotlin.math.absoluteValue import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.Job -import kotlinx.coroutines.launch /** * Transition to [target] using a canned animation. This function will try to be smart and take over @@ -50,7 +45,7 @@ internal fun CoroutineScope.animateToScene( return when (transitionState) { is TransitionState.Idle -> { - animate( + animateToScene( layoutState, target, transitionKey, @@ -80,13 +75,11 @@ internal fun CoroutineScope.animateToScene( } else { // The transition is in progress: start the canned animation at the same // progress as it was in. - animate( + animateToScene( layoutState, target, transitionKey, isInitiatedByUserInput, - initialProgress = progress, - initialVelocity = transitionState.progressVelocity, replacedTransition = transitionState, ) } @@ -102,13 +95,11 @@ internal fun CoroutineScope.animateToScene( layoutState.finishTransition(transitionState, target) null } else { - animate( + animateToScene( layoutState, target, transitionKey, isInitiatedByUserInput, - initialProgress = progress, - initialVelocity = transitionState.progressVelocity, reversed = true, replacedTransition = transitionState, ) @@ -140,7 +131,7 @@ internal fun CoroutineScope.animateToScene( animateToScene(layoutState, animateFrom, transitionKey = null) } - animate( + animateToScene( layoutState, target, transitionKey, @@ -154,103 +145,68 @@ internal fun CoroutineScope.animateToScene( } } -private fun CoroutineScope.animate( +private fun CoroutineScope.animateToScene( layoutState: MutableSceneTransitionLayoutStateImpl, targetScene: SceneKey, transitionKey: TransitionKey?, isInitiatedByUserInput: Boolean, replacedTransition: TransitionState.Transition?, - initialProgress: Float = 0f, - initialVelocity: Float = 0f, reversed: Boolean = false, fromScene: SceneKey = layoutState.transitionState.currentScene, chain: Boolean = true, ): TransitionState.Transition { + val oneOffAnimation = OneOffAnimation() val targetProgress = if (reversed) 0f else 1f val transition = if (reversed) { - OneOffTransition( + OneOffSceneTransition( key = transitionKey, fromScene = targetScene, toScene = fromScene, currentScene = targetScene, isInitiatedByUserInput = isInitiatedByUserInput, - isUserInputOngoing = false, replacedTransition = replacedTransition, + oneOffAnimation = oneOffAnimation, ) } else { - OneOffTransition( + OneOffSceneTransition( key = transitionKey, fromScene = fromScene, toScene = targetScene, currentScene = targetScene, isInitiatedByUserInput = isInitiatedByUserInput, - isUserInputOngoing = false, replacedTransition = replacedTransition, + oneOffAnimation = oneOffAnimation, ) } - // Change the current layout state to start this new transition. This will compute the - // TransformationSpec associated to this transition, which we need to initialize the Animatable - // that will actually animate it. - layoutState.startTransition(transition, chain) - - // The transition now contains the transformation spec that we should use to instantiate the - // Animatable. - val animationSpec = transition.transformationSpec.progressSpec - val visibilityThreshold = - (animationSpec as? SpringSpec)?.visibilityThreshold ?: ProgressVisibilityThreshold - val animatable = - Animatable(initialProgress, visibilityThreshold = visibilityThreshold).also { - transition.animatable = it - } - - // Animate the progress to its target value. - // Important: We start atomically to make sure that we start the coroutine even if it is - // cancelled right after it is launched, so that finishTransition() is correctly called. - // Otherwise, this transition will never be stopped and we will never settle to Idle. - transition.job = - launch(start = CoroutineStart.ATOMIC) { - try { - animatable.animateTo(targetProgress, animationSpec, initialVelocity) - } finally { - layoutState.finishTransition(transition, targetScene) - } - } + animateContent( + transition = transition, + oneOffAnimation = oneOffAnimation, + targetProgress = targetProgress, + startTransition = { layoutState.startTransition(transition, chain) }, + finishTransition = { layoutState.finishTransition(transition, targetScene) }, + ) return transition } -private class OneOffTransition( +private class OneOffSceneTransition( override val key: TransitionKey?, fromScene: SceneKey, toScene: SceneKey, override val currentScene: SceneKey, override val isInitiatedByUserInput: Boolean, - override val isUserInputOngoing: Boolean, replacedTransition: TransitionState.Transition?, + private val oneOffAnimation: OneOffAnimation, ) : TransitionState.Transition(fromScene, toScene, replacedTransition) { - /** - * The animatable used to animate this transition. - * - * Note: This is lateinit because we need to first create this Transition object so that - * [SceneTransitionLayoutState] can compute the transformations and animation spec associated to - * it, which is need to initialize this Animatable. - */ - lateinit var animatable: Animatable<Float, AnimationVector1D> - - /** The job that is animating [animatable]. */ - lateinit var job: Job - override val progress: Float - get() = animatable.value + get() = oneOffAnimation.progress override val progressVelocity: Float - get() = animatable.velocity + get() = oneOffAnimation.progressVelocity - override fun finish(): Job = job -} + override val isUserInputOngoing: Boolean = false -// TODO(b/290184746): Compute a good default visibility threshold that depends on the layout size -// and screen density. -internal const val ProgressVisibilityThreshold = 1e-3f + override fun finish(): Job = oneOffAnimation.finish() +} diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt index a43028a340f4..712fe6b2ff50 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt @@ -459,38 +459,11 @@ private class DragControllerImpl( animateTo(targetScene = targetScene, targetOffset = targetOffset) } else { - // We are doing an overscroll animation between scenes. In this case, we can also start - // from the idle position. - - val startFromIdlePosition = swipeTransition.dragOffset == 0f - - if (startFromIdlePosition) { - // If there is a target scene, we start the overscroll animation. - val result = swipes.findUserActionResultStrict(velocity) - if (result == null) { - // We will not animate - swipeTransition.snapToScene(fromScene.key) - return 0f - } - - val newSwipeTransition = - SwipeTransition( - layoutState = layoutState, - coroutineScope = draggableHandler.coroutineScope, - fromScene = fromScene, - result = result, - swipes = swipes, - layoutImpl = draggableHandler.layoutImpl, - orientation = draggableHandler.orientation, - ) - .apply { _currentScene = swipeTransition._currentScene } - - updateTransition(newSwipeTransition) - animateTo(targetScene = fromScene, targetOffset = 0f) - } else { - // We were between two scenes: animate to the initial scene. - animateTo(targetScene = fromScene, targetOffset = 0f) + // We are doing an overscroll preview animation between scenes. + check(fromScene == swipeTransition._currentScene) { + "canChangeScene is false but currentScene != fromScene" } + animateTo(targetScene = fromScene, targetOffset = 0f) } // The onStop animation consumes any remaining velocity. diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt index a30b78049213..79b38563b8f9 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt @@ -17,6 +17,8 @@ package com.android.compose.animation.scene import androidx.compose.animation.core.AnimationSpec +import androidx.compose.animation.core.Easing +import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.SpringSpec import androidx.compose.foundation.gestures.Orientation import androidx.compose.ui.geometry.Offset @@ -140,6 +142,7 @@ interface BaseTransitionBuilder : PropertyTransformationBuilder { fun fractionRange( start: Float? = null, end: Float? = null, + easing: Easing = LinearEasing, builder: PropertyTransformationBuilder.() -> Unit, ) } @@ -182,6 +185,7 @@ interface TransitionBuilder : BaseTransitionBuilder { fun timestampRange( startMillis: Int? = null, endMillis: Int? = null, + easing: Easing = LinearEasing, builder: PropertyTransformationBuilder.() -> Unit, ) diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt index 6515cb8f68ca..a63b19a0306f 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt @@ -18,6 +18,7 @@ package com.android.compose.animation.scene import androidx.compose.animation.core.AnimationSpec import androidx.compose.animation.core.DurationBasedAnimationSpec +import androidx.compose.animation.core.Easing import androidx.compose.animation.core.Spring import androidx.compose.animation.core.SpringSpec import androidx.compose.animation.core.VectorConverter @@ -163,9 +164,10 @@ internal abstract class BaseTransitionBuilderImpl : BaseTransitionBuilder { override fun fractionRange( start: Float?, end: Float?, + easing: Easing, builder: PropertyTransformationBuilder.() -> Unit ) { - range = TransformationRange(start, end) + range = TransformationRange(start, end, easing) builder() range = null } @@ -251,6 +253,7 @@ internal class TransitionBuilderImpl : BaseTransitionBuilderImpl(), TransitionBu override fun timestampRange( startMillis: Int?, endMillis: Int?, + easing: Easing, builder: PropertyTransformationBuilder.() -> Unit ) { if (startMillis != null && (startMillis < 0 || startMillis > durationMillis)) { @@ -263,7 +266,7 @@ internal class TransitionBuilderImpl : BaseTransitionBuilderImpl(), TransitionBu val start = startMillis?.let { it.toFloat() / durationMillis } val end = endMillis?.let { it.toFloat() / durationMillis } - fractionRange(start, end, builder) + fractionRange(start, end, easing, builder) } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt index 77ec89161d43..eda8edeceeb9 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt @@ -16,6 +16,8 @@ package com.android.compose.animation.scene.transformation +import androidx.compose.animation.core.Easing +import androidx.compose.animation.core.LinearEasing import androidx.compose.ui.util.fastCoerceAtLeast import androidx.compose.ui.util.fastCoerceAtMost import androidx.compose.ui.util.fastCoerceIn @@ -90,11 +92,13 @@ internal class RangedPropertyTransformation<T>( data class TransformationRange( val start: Float, val end: Float, + val easing: Easing, ) { constructor( start: Float? = null, - end: Float? = null - ) : this(start ?: BoundUnspecified, end ?: BoundUnspecified) + end: Float? = null, + easing: Easing = LinearEasing, + ) : this(start ?: BoundUnspecified, end ?: BoundUnspecified, easing) init { require(!start.isSpecified() || (start in 0f..1f)) @@ -103,17 +107,20 @@ data class TransformationRange( } /** Reverse this range. */ - fun reversed() = TransformationRange(start = reverseBound(end), end = reverseBound(start)) + fun reversed() = + TransformationRange(start = reverseBound(end), end = reverseBound(start), easing = easing) /** Get the progress of this range given the global [transitionProgress]. */ fun progress(transitionProgress: Float): Float { - return when { - start.isSpecified() && end.isSpecified() -> - ((transitionProgress - start) / (end - start)).fastCoerceIn(0f, 1f) - !start.isSpecified() && !end.isSpecified() -> transitionProgress - end.isSpecified() -> (transitionProgress / end).fastCoerceAtMost(1f) - else -> ((transitionProgress - start) / (1f - start)).fastCoerceAtLeast(0f) - } + val progress = + when { + start.isSpecified() && end.isSpecified() -> + ((transitionProgress - start) / (end - start)).fastCoerceIn(0f, 1f) + !start.isSpecified() && !end.isSpecified() -> transitionProgress + end.isSpecified() -> (transitionProgress / end).fastCoerceAtMost(1f) + else -> ((transitionProgress - start) / (1f - start)).fastCoerceAtLeast(0f) + } + return easing.transform(progress) } private fun Float.isSpecified() = this != BoundUnspecified diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt index 68240b5337fe..bed6cefa459d 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt @@ -16,6 +16,7 @@ package com.android.compose.animation.scene +import androidx.compose.animation.core.CubicBezierEasing import androidx.compose.animation.core.SpringSpec import androidx.compose.animation.core.TweenSpec import androidx.compose.animation.core.spring @@ -107,6 +108,13 @@ class TransitionDslTest { fractionRange(start = 0.1f, end = 0.8f) { fade(TestElements.Foo) } fractionRange(start = 0.2f) { fade(TestElements.Foo) } fractionRange(end = 0.9f) { fade(TestElements.Foo) } + fractionRange( + start = 0.1f, + end = 0.8f, + easing = CubicBezierEasing(0.1f, 0.1f, 0f, 1f) + ) { + fade(TestElements.Foo) + } } } @@ -118,6 +126,11 @@ class TransitionDslTest { TransformationRange(start = 0.1f, end = 0.8f), TransformationRange(start = 0.2f, end = TransformationRange.BoundUnspecified), TransformationRange(start = TransformationRange.BoundUnspecified, end = 0.9f), + TransformationRange( + start = 0.1f, + end = 0.8f, + CubicBezierEasing(0.1f, 0.1f, 0f, 1f) + ), ) } @@ -130,6 +143,13 @@ class TransitionDslTest { timestampRange(startMillis = 100, endMillis = 300) { fade(TestElements.Foo) } timestampRange(startMillis = 200) { fade(TestElements.Foo) } timestampRange(endMillis = 400) { fade(TestElements.Foo) } + timestampRange( + startMillis = 100, + endMillis = 300, + easing = CubicBezierEasing(0.1f, 0.1f, 0f, 1f) + ) { + fade(TestElements.Foo) + } } } @@ -141,6 +161,11 @@ class TransitionDslTest { TransformationRange(start = 100 / 500f, end = 300 / 500f), TransformationRange(start = 200 / 500f, end = TransformationRange.BoundUnspecified), TransformationRange(start = TransformationRange.BoundUnspecified, end = 400 / 500f), + TransformationRange( + start = 100 / 500f, + end = 300 / 500f, + easing = CubicBezierEasing(0.1f, 0.1f, 0f, 1f) + ), ) } diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/EasingTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/EasingTest.kt new file mode 100644 index 000000000000..07901f27388d --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/EasingTest.kt @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.compose.animation.scene.transformation + +import androidx.compose.animation.core.CubicBezierEasing +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.tween +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.size +import androidx.compose.ui.Modifier +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.unit.dp +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.compose.animation.scene.TestElements +import com.android.compose.animation.scene.testTransition +import com.android.compose.test.assertSizeIsEqualTo +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class EasingTest { + @get:Rule val rule = createComposeRule() + + @Test + fun testFractionRangeEasing() { + val easing = CubicBezierEasing(0.1f, 0.1f, 0f, 1f) + rule.testTransition( + fromSceneContent = { Box(Modifier.size(100.dp).element(TestElements.Foo)) }, + toSceneContent = { Box(Modifier.size(100.dp).element(TestElements.Bar)) }, + transition = { + // Scale during 4 frames. + spec = tween(16 * 4, easing = LinearEasing) + fractionRange(easing = easing) { + scaleSize(TestElements.Foo, width = 0f, height = 0f) + scaleSize(TestElements.Bar, width = 0f, height = 0f) + } + }, + ) { + // Foo is entering, is 100dp x 100dp at rest and is scaled by (2, 0.5) during the + // transition so it starts at 200dp x 50dp. + before { onElement(TestElements.Bar).assertDoesNotExist() } + at(0) { + onElement(TestElements.Foo).assertSizeIsEqualTo(100.dp, 100.dp) + onElement(TestElements.Bar).assertSizeIsEqualTo(0.dp, 0.dp) + } + at(16) { + // 25% linear progress is mapped to 68.5% eased progress + onElement(TestElements.Foo).assertSizeIsEqualTo(31.5.dp, 31.5.dp) + onElement(TestElements.Bar).assertSizeIsEqualTo(68.5.dp, 68.5.dp) + } + at(32) { + // 50% linear progress is mapped to 89.5% eased progress + onElement(TestElements.Foo).assertSizeIsEqualTo(10.5.dp, 10.5.dp) + onElement(TestElements.Bar).assertSizeIsEqualTo(89.5.dp, 89.5.dp) + } + at(48) { + // 75% linear progress is mapped to 97.8% eased progress + onElement(TestElements.Foo).assertSizeIsEqualTo(2.2.dp, 2.2.dp) + onElement(TestElements.Bar).assertSizeIsEqualTo(97.8.dp, 97.8.dp) + } + after { + onElement(TestElements.Foo).assertDoesNotExist() + onElement(TestElements.Bar).assertSizeIsEqualTo(100.dp, 100.dp) + } + } + } + + @Test + fun testTimestampRangeEasing() { + val easing = CubicBezierEasing(0.1f, 0.1f, 0f, 1f) + rule.testTransition( + fromSceneContent = { Box(Modifier.size(100.dp).element(TestElements.Foo)) }, + toSceneContent = { Box(Modifier.size(100.dp).element(TestElements.Bar)) }, + transition = { + // Scale during 4 frames. + spec = tween(16 * 4, easing = LinearEasing) + timestampRange(easing = easing) { + scaleSize(TestElements.Foo, width = 0f, height = 0f) + scaleSize(TestElements.Bar, width = 0f, height = 0f) + } + }, + ) { + // Foo is entering, is 100dp x 100dp at rest and is scaled by (2, 0.5) during the + // transition so it starts at 200dp x 50dp. + before { onElement(TestElements.Bar).assertDoesNotExist() } + at(0) { + onElement(TestElements.Foo).assertSizeIsEqualTo(100.dp, 100.dp) + onElement(TestElements.Bar).assertSizeIsEqualTo(0.dp, 0.dp) + } + at(16) { + // 25% linear progress is mapped to 68.5% eased progress + onElement(TestElements.Foo).assertSizeIsEqualTo(31.5.dp, 31.5.dp) + onElement(TestElements.Bar).assertSizeIsEqualTo(68.5.dp, 68.5.dp) + } + at(32) { + // 50% linear progress is mapped to 89.5% eased progress + onElement(TestElements.Foo).assertSizeIsEqualTo(10.5.dp, 10.5.dp) + onElement(TestElements.Bar).assertSizeIsEqualTo(89.5.dp, 89.5.dp) + } + at(48) { + // 75% linear progress is mapped to 97.8% eased progress + onElement(TestElements.Foo).assertSizeIsEqualTo(2.2.dp, 2.2.dp) + onElement(TestElements.Bar).assertSizeIsEqualTo(97.8.dp, 97.8.dp) + } + after { + onElement(TestElements.Foo).assertDoesNotExist() + onElement(TestElements.Bar).assertSizeIsEqualTo(100.dp, 100.dp) + } + } + } +} diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt index 9b725eb9eb5a..954155d16b05 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt @@ -88,7 +88,6 @@ open class ClockRegistry( val clockBuffers: ClockMessageBuffers? = null, val keepAllLoaded: Boolean, subTag: String, - var isTransitClockEnabled: Boolean = false, val assert: ThreadAssert = ThreadAssert(), ) { private val TAG = "${ClockRegistry::class.simpleName} ($subTag)" @@ -188,10 +187,6 @@ open class ClockRegistry( var isClockListChanged = false for (clock in plugin.getClocks()) { val id = clock.clockId - if (!isTransitClockEnabled && id == "DIGITAL_CLOCK_METRO") { - continue - } - val info = availableClocks.concurrentGetOrPut(id, ClockInfo(clock, plugin, manager)) { isClockListChanged = true diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt index 9d62e387500f..8721c7885265 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt @@ -182,9 +182,6 @@ object CustomizationProviderContract { const val FLAG_NAME_WALLPAPER_PICKER_UI_FOR_AIWP = "wallpaper_picker_ui_for_aiwp" /** Flag denoting transit clock are enabled in wallpaper picker. */ - const val FLAG_NAME_TRANSIT_CLOCK = "lockscreen_custom_transit_clock" - - /** Flag denoting transit clock are enabled in wallpaper picker. */ const val FLAG_NAME_PAGE_TRANSITIONS = "wallpaper_picker_page_transitions" /** Flag denoting adding apply button to wallpaper picker's grid preview page. */ diff --git a/packages/SystemUI/lint-baseline.xml b/packages/SystemUI/lint-baseline.xml index b4c839f08607..7577147a6f16 100644 --- a/packages/SystemUI/lint-baseline.xml +++ b/packages/SystemUI/lint-baseline.xml @@ -32157,4 +32157,631 @@ column="6"/> </issue> + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" contentResolver.registerContentObserver(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt" + line="154" + column="33"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" resolver.registerContentObserver(ALWAYS_ON_DISPLAY_CONSTANTS_URI," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/doze/AlwaysOnDisplayPolicy.java" + line="164" + column="22"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" resolver.registerContentObserver(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/util/animation/data/repository/AnimationStatusRepository.kt" + line="61" + column="26"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.unregisterContentObserver()`" + errorLine1=" awaitClose { resolver.unregisterContentObserver(observer) }" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/util/animation/data/repository/AnimationStatusRepository.kt" + line="67" + column="39"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.unregisterContentObserver()`" + errorLine1=" mContentResolver.unregisterContentObserver(mSettingObserver);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java" + line="123" + column="38"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.unregisterContentObserver()`" + errorLine1=" mContentResolver.unregisterContentObserver(mSettingObserver);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java" + line="180" + column="26"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" mContentResolver.registerContentObserver(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java" + line="211" + column="26"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" mContentResolver.registerContentObserver(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java" + line="219" + column="26"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" resolver.registerContentObserver(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/demomode/DemoModeAvailabilityTracker.kt" + line="46" + column="18"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" resolver.registerContentObserver(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/demomode/DemoModeAvailabilityTracker.kt" + line="48" + column="18"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.unregisterContentObserver()`" + errorLine1=" resolver.unregisterContentObserver(allowedObserver)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/demomode/DemoModeAvailabilityTracker.kt" + line="54" + column="18"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.unregisterContentObserver()`" + errorLine1=" resolver.unregisterContentObserver(onObserver)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/demomode/DemoModeAvailabilityTracker.kt" + line="55" + column="18"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" resolver.registerContentObserver(mQuickPickupGesture, false, this," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java" + line="511" + column="26"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" resolver.registerContentObserver(mPickupGesture, false, this, UserHandle.USER_ALL);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java" + line="513" + column="26"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" resolver.registerContentObserver(mAlwaysOnEnabled, false, this," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java" + line="514" + column="26"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" mContext.getContentResolver().registerContentObserver(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java" + line="2470" + column="39"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" mContext.getContentResolver().registerContentObserver(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java" + line="3077" + column="39"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.unregisterContentObserver()`" + errorLine1=" mContext.getContentResolver().unregisterContentObserver(mDeviceProvisionedObserver);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java" + line="3168" + column="43"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.unregisterContentObserver()`" + errorLine1=" mContext.getContentResolver().unregisterContentObserver(mDeviceProvisionedObserver);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java" + line="3957" + column="43"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.unregisterContentObserver()`" + errorLine1=" mContext.getContentResolver().unregisterContentObserver(mTimeFormatChangeObserver);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java" + line="3961" + column="43"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" contentResolver.registerContentObserver(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt" + line="486" + column="25"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" contentResolver.registerContentObserver(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt" + line="492" + column="25"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.unregisterContentObserver()`" + errorLine1=" contentResolver.unregisterContentObserver(settingsObserver)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt" + line="543" + column="25"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" contentResolver.registerContentObserver(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/smartspace/filters/LockscreenTargetFilter.kt" + line="82" + column="25"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.unregisterContentObserver()`" + errorLine1=" contentResolver.unregisterContentObserver(settingsObserver)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/smartspace/filters/LockscreenTargetFilter.kt" + line="100" + column="25"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.unregisterContentObserver()`" + errorLine1=" mContext.getContentResolver().unregisterContentObserver(mMenuTargetFeaturesContentObserver);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java" + line="275" + column="39"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.unregisterContentObserver()`" + errorLine1=" mContext.getContentResolver().unregisterContentObserver(mMenuSizeContentObserver);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java" + line="276" + column="39"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.unregisterContentObserver()`" + errorLine1=" mContext.getContentResolver().unregisterContentObserver(mMenuFadeOutContentObserver);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java" + line="277" + column="39"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" mContext.getContentResolver().registerContentObserver(Global.getUriFor(Global.MOBILE_DATA)," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java" + line="200" + column="39"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" mContext.getContentResolver().registerContentObserver(Global.getUriFor(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java" + line="202" + column="39"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.unregisterContentObserver()`" + errorLine1=" mContext.getContentResolver().unregisterContentObserver(mObserver);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java" + line="212" + column="39"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" context.contentResolver.registerContentObserver(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NaturalScrollingSettingObserver.kt" + line="54" + column="33"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" mContentResolver.registerContentObserver(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java" + line="253" + column="26"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" mContentResolver.registerContentObserver(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java" + line="256" + column="26"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" mContentResolver.registerContentObserver(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java" + line="259" + column="26"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" mContentResolver.registerContentObserver(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java" + line="262" + column="26"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.unregisterContentObserver()`" + errorLine1=" mContentResolver.unregisterContentObserver(mAssistContentObserver);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java" + line="295" + column="26"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" mContext.getContentResolver().registerContentObserver(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java" + line="395" + column="39"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" mContext.getContentResolver().registerContentObserver(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java" + line="400" + column="39"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" mContentResolver.registerContentObserver(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java" + line="3675" + column="26"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.unregisterContentObserver()`" + errorLine1=" mContentResolver.unregisterContentObserver(mSettingsChangeObserver);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java" + line="4705" + column="30"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" resolver.registerContentObserver(Settings.Global.getUriFor(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/power/PowerUI.java" + line="191" + column="18"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" resolver.registerContentObserver(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/power/PowerUI.java" + line="205" + column="18"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" resolver.registerContentObserver(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/power/PowerUI.java" + line="216" + column="18"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" mContext.getContentResolver().registerContentObserver(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java" + line="178" + column="39"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.unregisterContentObserver()`" + errorLine1=" mContext.getContentResolver().unregisterContentObserver(mDeveloperSettingsObserver);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java" + line="186" + column="39"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" mContentResolver.registerContentObserver(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/accessibility/SecureSettingsContentObserver.java" + line="83" + column="30"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.unregisterContentObserver()`" + errorLine1=" mContentResolver.unregisterContentObserver(mContentObserver);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/accessibility/SecureSettingsContentObserver.java" + line="100" + column="30"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" mContentResolver.registerContentObserver(uri, false, mObserver, mCurrentUser);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java" + line="219" + column="30"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.unregisterContentObserver()`" + errorLine1=" mContentResolver.unregisterContentObserver(mObserver);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java" + line="241" + column="26"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" mContentResolver.registerContentObserver(uri, false, mObserver, mCurrentUser);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java" + line="243" + column="30"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" mContext.getContentResolver().registerContentObserver(ZEN_MODE_URI, false, this);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java" + line="1235" + column="43"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" mContext.getContentResolver().registerContentObserver(ZEN_MODE_CONFIG_URI, false, this);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java" + line="1236" + column="43"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.unregisterContentObserver()`" + errorLine1=" mContext.getContentResolver().unregisterContentObserver(this);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java" + line="1240" + column="43"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.unregisterContentObserver()`" + errorLine1=" mResolver.unregisterContentObserver(this);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java" + line="377" + column="27"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" mResolver.registerContentObserver(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java" + line="379" + column="23"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" mResolver.registerContentObserver(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java" + line="381" + column="23"/> + </issue> + </issues> diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java index 9b1d4eca8b2b..752c93e077ac 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java @@ -148,8 +148,6 @@ public class AuthControllerTest extends SysuiTestCase { @Mock private WakefulnessLifecycle mWakefulnessLifecycle; @Mock - private AuthDialogPanelInteractionDetector mPanelInteractionDetector; - @Mock private UserManager mUserManager; @Mock private LockPatternUtils mLockPatternUtils; @@ -1059,10 +1057,9 @@ public class AuthControllerTest extends SysuiTestCase { super(context, null /* applicationCoroutineScope */, mExecution, mCommandQueue, mActivityTaskManager, mWindowManager, mFingerprintManager, mFaceManager, () -> mUdfpsController, mDisplayManager, - mWakefulnessLifecycle, mPanelInteractionDetector, mUserManager, - mLockPatternUtils, () -> mUdfpsLogger, () -> mLogContextInteractor, - () -> mPromptSelectionInteractor, () -> mCredentialViewModel, - () -> mPromptViewModel, mInteractionJankMonitor, + mWakefulnessLifecycle, mUserManager, mLockPatternUtils, () -> mUdfpsLogger, + () -> mLogContextInteractor, () -> mPromptSelectionInteractor, + () -> mCredentialViewModel, () -> mPromptViewModel, mInteractionJankMonitor, mHandler, mBackgroundExecutor, mUdfpsUtils, mVibratorHelper); } @@ -1071,7 +1068,6 @@ public class AuthControllerTest extends SysuiTestCase { boolean requireConfirmation, int userId, int[] sensorIds, String opPackageName, boolean skipIntro, long operationId, long requestId, WakefulnessLifecycle wakefulnessLifecycle, - AuthDialogPanelInteractionDetector panelInteractionDetector, UserManager userManager, LockPatternUtils lockPatternUtils, PromptViewModel viewModel) { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java deleted file mode 100644 index cd9189bef7f1..000000000000 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.biometrics; - -import static org.junit.Assert.assertEquals; - -import android.hardware.biometrics.ComponentInfoInternal; -import android.hardware.biometrics.SensorLocationInternal; -import android.hardware.biometrics.SensorProperties; -import android.hardware.fingerprint.FingerprintSensorProperties; -import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; - -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.filters.SmallTest; - -import com.android.systemui.SysuiTestCase; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.ArrayList; -import java.util.List; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public class UdfpsDialogMeasureAdapterTest extends SysuiTestCase { - @Test - public void testUdfpsBottomSpacerHeightForPortrait() { - final int displayHeightPx = 3000; - final int navbarHeightPx = 10; - final int dialogBottomMarginPx = 20; - final int buttonBarHeightPx = 100; - final int textIndicatorHeightPx = 200; - - final int sensorLocationX = 540; - final int sensorLocationY = 1600; - final int sensorRadius = 100; - - final List<ComponentInfoInternal> componentInfo = new ArrayList<>(); - componentInfo.add(new ComponentInfoInternal("faceSensor" /* componentId */, - "vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */, - "00000001" /* serialNumber */, "" /* softwareVersion */)); - componentInfo.add(new ComponentInfoInternal("matchingAlgorithm" /* componentId */, - "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */, - "vendor/version/revision" /* softwareVersion */)); - - final FingerprintSensorPropertiesInternal props = new FingerprintSensorPropertiesInternal( - 0 /* sensorId */, SensorProperties.STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */, - componentInfo, - FingerprintSensorProperties.TYPE_UDFPS_OPTICAL, - true /* halControlsIllumination */, - true /* resetLockoutRequiresHardwareAuthToken */, - List.of(new SensorLocationInternal("" /* displayId */, - sensorLocationX, sensorLocationY, sensorRadius))); - - assertEquals(970, - UdfpsDialogMeasureAdapter.calculateBottomSpacerHeightForPortrait( - props, displayHeightPx, textIndicatorHeightPx, buttonBarHeightPx, - dialogBottomMarginPx, navbarHeightPx, 1.0f /* resolutionScale */ - )); - } - - @Test - public void testUdfpsBottomSpacerHeightForLandscape_whenMoreSpaceAboveIcon() { - final int titleHeightPx = 320; - final int subtitleHeightPx = 240; - final int descriptionHeightPx = 200; - final int topSpacerHeightPx = 550; - final int textIndicatorHeightPx = 190; - final int buttonBarHeightPx = 160; - final int navbarBottomInsetPx = 75; - - assertEquals(885, - UdfpsDialogMeasureAdapter.calculateBottomSpacerHeightForLandscape( - titleHeightPx, subtitleHeightPx, descriptionHeightPx, topSpacerHeightPx, - textIndicatorHeightPx, buttonBarHeightPx, navbarBottomInsetPx)); - } - - @Test - public void testUdfpsBottomSpacerHeightForLandscape_whenMoreSpaceBelowIcon() { - final int titleHeightPx = 315; - final int subtitleHeightPx = 160; - final int descriptionHeightPx = 75; - final int topSpacerHeightPx = 220; - final int textIndicatorHeightPx = 290; - final int buttonBarHeightPx = 360; - final int navbarBottomInsetPx = 205; - - assertEquals(-85, - UdfpsDialogMeasureAdapter.calculateBottomSpacerHeightForLandscape( - titleHeightPx, subtitleHeightPx, descriptionHeightPx, topSpacerHeightPx, - textIndicatorHeightPx, buttonBarHeightPx, navbarBottomInsetPx)); - } - - @Test - public void testUdfpsHorizontalSpacerWidthForLandscape() { - final int displayWidthPx = 3000; - final int dialogMarginPx = 20; - final int navbarHorizontalInsetPx = 75; - - final int sensorLocationX = 540; - final int sensorLocationY = 1600; - final int sensorRadius = 100; - - final List<ComponentInfoInternal> componentInfo = new ArrayList<>(); - componentInfo.add(new ComponentInfoInternal("faceSensor" /* componentId */, - "vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */, - "00000001" /* serialNumber */, "" /* softwareVersion */)); - componentInfo.add(new ComponentInfoInternal("matchingAlgorithm" /* componentId */, - "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */, - "vendor/version/revision" /* softwareVersion */)); - - final FingerprintSensorPropertiesInternal props = new FingerprintSensorPropertiesInternal( - 0 /* sensorId */, SensorProperties.STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */, - componentInfo, - FingerprintSensorProperties.TYPE_UDFPS_OPTICAL, - true /* halControlsIllumination */, - true /* resetLockoutRequiresHardwareAuthToken */, - List.of(new SensorLocationInternal("" /* displayId */, - sensorLocationX, sensorLocationY, sensorRadius))); - - assertEquals(1205, - UdfpsDialogMeasureAdapter.calculateHorizontalSpacerWidthForLandscape( - props, displayWidthPx, dialogMarginPx, navbarHorizontalInsetPx, - 1.0f /* resolutionScale */)); - } -} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt index 0242c2d6b89d..e57a4cbc230e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt @@ -1039,6 +1039,22 @@ class CommunalInteractorTest : SysuiTestCase() { assertThat(showCommunalFromOccluded).isTrue() } + @Test + fun showCommunalFromOccluded_enteredOccludedFromDreaming() = + testScope.runTest { + kosmos.setCommunalAvailable(true) + val showCommunalFromOccluded by collectLastValue(underTest.showCommunalFromOccluded) + assertThat(showCommunalFromOccluded).isFalse() + + kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.DREAMING, + to = KeyguardState.OCCLUDED, + testScope + ) + + assertThat(showCommunalFromOccluded).isTrue() + } + private fun smartspaceTimer(id: String, timestamp: Long = 0L): CommunalSmartspaceTimer { return CommunalSmartspaceTimer( smartspaceTargetId = id, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt new file mode 100644 index 000000000000..1f733472cbed --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt @@ -0,0 +1,82 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.education.domain.ui.view + +import android.content.applicationContext +import android.widget.Toast +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.contextualeducation.GestureType +import com.android.systemui.contextualeducation.GestureType.BACK +import com.android.systemui.education.domain.interactor.KeyboardTouchpadEduInteractor +import com.android.systemui.education.domain.interactor.contextualEducationInteractor +import com.android.systemui.education.domain.interactor.keyboardTouchpadEduInteractor +import com.android.systemui.education.ui.view.ContextualEduUiCoordinator +import com.android.systemui.education.ui.viewmodel.ContextualEduViewModel +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.junit.MockitoJUnit +import org.mockito.kotlin.verify + +@SmallTest +@RunWith(AndroidJUnit4::class) +class ContextualEduUiCoordinatorTest : SysuiTestCase() { + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val interactor = kosmos.contextualEducationInteractor + private lateinit var underTest: ContextualEduUiCoordinator + @Mock private lateinit var toast: Toast + + @get:Rule val mockitoRule = MockitoJUnit.rule() + + @Before + fun setUp() { + val viewModel = + ContextualEduViewModel( + kosmos.applicationContext.resources, + kosmos.keyboardTouchpadEduInteractor + ) + underTest = + ContextualEduUiCoordinator(kosmos.applicationCoroutineScope, viewModel) { _ -> toast } + underTest.start() + kosmos.keyboardTouchpadEduInteractor.start() + } + + @Test + @OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class) + fun showToastOnNewEdu() = + testScope.runTest { + triggerEducation(BACK) + runCurrent() + verify(toast).show() + } + + private suspend fun triggerEducation(gestureType: GestureType) { + for (i in 1..KeyboardTouchpadEduInteractor.MAX_SIGNAL_COUNT) { + interactor.incrementSignalCount(gestureType) + } + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt index 03647b9a3956..6e76cbce95f1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt @@ -205,6 +205,38 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() { } @Test + fun transitionValue_badTransitionResetsTransitionValue() = + testScope.runTest { + resetTransitionValueReplayCache(setOf(AOD, DOZING, LOCKSCREEN)) + val transitionValues by collectValues(underTest.transitionValue(state = DOZING)) + + val toSteps = + listOf( + TransitionStep(AOD, DOZING, 0f, STARTED), + TransitionStep(AOD, DOZING, 0.5f, RUNNING), + ) + toSteps.forEach { + repository.sendTransitionStep(it) + runCurrent() + } + + // This is an intentionally bad sequence that will leave the transitionValue for + // DOZING in a bad place, since no CANCELED will be issued for DOZING + val fromSteps = + listOf( + TransitionStep(AOD, LOCKSCREEN, 0f, STARTED), + TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING), + TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED), + ) + fromSteps.forEach { + repository.sendTransitionStep(it) + runCurrent() + } + + assertThat(transitionValues).isEqualTo(listOf(0f, 0.5f, 0f)) + } + + @Test fun transitionValue_canceled_toAnotherState() = testScope.runTest { resetTransitionValueReplayCache(setOf(AOD, GONE, LOCKSCREEN)) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModelTest.kt index 4a10d80430e9..8e4876d3c6df 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModelTest.kt @@ -42,6 +42,28 @@ class DozingToLockscreenTransitionViewModelTest : SysuiTestCase() { val underTest = kosmos.dozingToLockscreenTransitionViewModel @Test + fun lockscreenAlpha() = + testScope.runTest { + val lockscreenAlpha by collectValues(underTest.lockscreenAlpha) + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + repository.sendTransitionStep(step(0.1f)) + repository.sendTransitionStep(step(0.5f)) + repository.sendTransitionStep(step(1f)) + lockscreenAlpha.forEach { assertThat(it).isEqualTo(1f) } + } + + @Test + fun shortcutsAlpha() = + testScope.runTest { + val shortcutsAlpha by collectValues(underTest.shortcutsAlpha) + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + repository.sendTransitionStep(step(0.5f)) + repository.sendTransitionStep(step(1f)) + assertThat(shortcutsAlpha[0]).isEqualTo(0f) + assertThat(shortcutsAlpha[1]).isEqualTo(1f) + } + + @Test fun deviceEntryParentViewShows() = testScope.runTest { val deviceEntryParentViewAlpha by collectValues(underTest.deviceEntryParentViewAlpha) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt index d1f908dfc795..46b370fedf37 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt @@ -16,16 +16,27 @@ package com.android.systemui.lifecycle +import android.view.View import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.test.junit4.createComposeRule import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.util.Assert import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.awaitCancellation +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.stub +import org.mockito.kotlin.verify @SmallTest @RunWith(AndroidJUnit4::class) @@ -110,4 +121,45 @@ class SysUiViewModelTest : SysuiTestCase() { assertThat(isActive).isFalse() } + + @Test + fun viewModel_viewBinder() = runTest { + Assert.setTestThread(Thread.currentThread()) + + val view: View = mock { on { isAttachedToWindow } doReturn false } + val viewModel = FakeViewModel() + backgroundScope.launch { + view.viewModel( + minWindowLifecycleState = WindowLifecycleState.ATTACHED, + factory = { viewModel }, + ) { + awaitCancellation() + } + } + runCurrent() + + assertThat(viewModel.isActivated).isFalse() + + view.stub { on { isAttachedToWindow } doReturn true } + argumentCaptor<View.OnAttachStateChangeListener>() + .apply { verify(view).addOnAttachStateChangeListener(capture()) } + .allValues + .forEach { it.onViewAttachedToWindow(view) } + runCurrent() + + assertThat(viewModel.isActivated).isTrue() + } +} + +private class FakeViewModel : SysUiViewModel() { + var isActivated = false + + override suspend fun onActivated() { + isActivated = true + try { + awaitCancellation() + } finally { + isActivated = false + } + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/AirplaneModeMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/AirplaneModeMapperTest.kt new file mode 100644 index 000000000000..5a73fe28ee18 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/AirplaneModeMapperTest.kt @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl + +import android.graphics.drawable.TestStubDrawable +import android.service.quicksettings.Tile +import android.widget.Switch +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.tiles.impl.airplane.domain.AirplaneModeMapper +import com.android.systemui.qs.tiles.impl.airplane.domain.model.AirplaneModeTileModel +import com.android.systemui.qs.tiles.impl.airplane.qsAirplaneModeTileConfig +import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject +import com.android.systemui.qs.tiles.viewmodel.QSTileState +import com.android.systemui.res.R +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class AirplaneModeMapperTest : SysuiTestCase() { + private val kosmos = Kosmos() + private val airplaneModeConfig = kosmos.qsAirplaneModeTileConfig + + private lateinit var mapper: AirplaneModeMapper + + @Before + fun setup() { + mapper = + AirplaneModeMapper( + context.orCreateTestableResources + .apply { + addOverride(R.drawable.qs_airplane_icon_off, TestStubDrawable()) + addOverride(R.drawable.qs_airplane_icon_on, TestStubDrawable()) + } + .resources, + context.theme, + ) + } + + @Test + fun enabledModel_mapsCorrectly() { + val inputModel = AirplaneModeTileModel(true) + + val outputState = mapper.map(airplaneModeConfig, inputModel) + + val expectedState = + createAirplaneModeState( + QSTileState.ActivationState.ACTIVE, + context.resources.getStringArray(R.array.tile_states_airplane)[Tile.STATE_ACTIVE], + R.drawable.qs_airplane_icon_on + ) + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + @Test + fun disabledModel_mapsCorrectly() { + val inputModel = AirplaneModeTileModel(false) + + val outputState = mapper.map(airplaneModeConfig, inputModel) + + val expectedState = + createAirplaneModeState( + QSTileState.ActivationState.INACTIVE, + context.resources.getStringArray(R.array.tile_states_airplane)[Tile.STATE_INACTIVE], + R.drawable.qs_airplane_icon_off + ) + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + private fun createAirplaneModeState( + activationState: QSTileState.ActivationState, + secondaryLabel: String, + iconRes: Int + ): QSTileState { + val label = context.getString(R.string.airplane_mode) + return QSTileState( + { Icon.Loaded(context.getDrawable(iconRes)!!, null) }, + iconRes, + label, + activationState, + secondaryLabel, + setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK), + label, + null, + QSTileState.SideViewIcon.None, + QSTileState.EnabledState.ENABLED, + Switch::class.qualifiedName + ) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt index b4ff56566c75..f1d08c068150 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.qs.tiles.impl.custom.domain.interactor import android.app.IUriGrantsManager import android.content.ComponentName +import android.content.Context import android.graphics.drawable.Drawable import android.graphics.drawable.Icon import android.graphics.drawable.TestStubDrawable @@ -51,11 +52,13 @@ import org.junit.runner.RunWith class CustomTileMapperTest : SysuiTestCase() { private val uriGrantsManager: IUriGrantsManager = mock {} + private val mockContext = + mock<Context> { whenever(createContextAsUser(any(), any())).thenReturn(context) } private val kosmos = testKosmos().apply { customTileSpec = TileSpec.Companion.create(TEST_COMPONENT) } private val underTest by lazy { CustomTileMapper( - context = mock { whenever(createContextAsUser(any(), any())).thenReturn(context) }, + context = mockContext, uriGrantsManager = uriGrantsManager, ) } @@ -164,7 +167,7 @@ class CustomTileMapperTest : SysuiTestCase() { ) val expected = createTileState( - activationState = QSTileState.ActivationState.INACTIVE, + activationState = QSTileState.ActivationState.UNAVAILABLE, icon = DEFAULT_DRAWABLE, ) @@ -173,7 +176,7 @@ class CustomTileMapperTest : SysuiTestCase() { } @Test - fun failedToLoadIconTileIsInactive() = + fun failedToLoadIconTileIsUnavailable() = with(kosmos) { testScope.runTest { val actual = @@ -187,13 +190,32 @@ class CustomTileMapperTest : SysuiTestCase() { val expected = createTileState( icon = null, - activationState = QSTileState.ActivationState.INACTIVE, + activationState = QSTileState.ActivationState.UNAVAILABLE, ) assertThat(actual).isEqualTo(expected) } } + @Test + fun nullUserContextDoesNotCauseExceptionReturnsNullIconAndUnavailableState() = + with(kosmos) { + testScope.runTest { + // map() will catch this exception + whenever(mockContext.createContextAsUser(any(), any())) + .thenThrow(IllegalStateException("Unable to create userContext")) + + val actual = underTest.map(customTileQsTileConfig, createModel()) + + val expected = + createTileState( + icon = null, + activationState = QSTileState.ActivationState.UNAVAILABLE, + ) + assertThat(actual).isEqualTo(expected) + } + } + private fun Kosmos.createModel( tileState: Int = Tile.STATE_ACTIVE, tileIcon: Icon = createIcon(DRAWABLE, false), diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt index 13d6411382cf..1ea8abc9b3b3 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt @@ -204,7 +204,7 @@ class InternetTileDataInteractorTest : SysuiTestCase() { val actualIcon = latest?.icon assertThat(actualIcon).isEqualTo(expectedIcon) - assertThat(latest?.iconId).isNull() + assertThat(latest?.iconId).isEqualTo(WifiIcons.WIFI_NO_INTERNET_ICONS[4]) assertThat(latest?.contentDescription.loadContentDescription(context)) .isEqualTo("$internet,test ssid") val expectedSd = wifiIcon.contentDescription @@ -443,15 +443,15 @@ class InternetTileDataInteractorTest : SysuiTestCase() { * on the mentioned context. Since that context does not have a looper assigned to it, the * handler instantiation will throw a RuntimeException. * - * TODO(b/338068066): Robolectric behavior differs in that it does not throw the exception - * So either we should make Robolectric behvase similar to the device test, or change this - * test to look for a different signal than the exception, when run by Robolectric. For now - * we just assume the test is not Robolectric. + * TODO(b/338068066): Robolectric behavior differs in that it does not throw the exception So + * either we should make Robolectric behave similar to the device test, or change this test to + * look for a different signal than the exception, when run by Robolectric. For now we just + * assume the test is not Robolectric. */ @Test(expected = java.lang.RuntimeException::class) fun mobileDefault_usesNetworkNameAndIcon_throwsRunTimeException() = testScope.runTest { - assumeFalse(isRobolectricTest()); + assumeFalse(isRobolectricTest()) collectLastValue(underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt index f26c39d0ba6d..bbb467f4f33d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt @@ -32,6 +32,7 @@ import com.android.systemui.authentication.data.repository.FakeAuthenticationRep import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository import com.android.systemui.authentication.domain.interactor.authenticationInteractor import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository import com.android.systemui.bouncer.domain.interactor.bouncerInteractor import com.android.systemui.bouncer.shared.logging.BouncerUiEvent import com.android.systemui.classifier.FalsingCollector @@ -109,6 +110,7 @@ class SceneContainerStartableTest : SysuiTestCase() { private val sceneInteractor by lazy { kosmos.sceneInteractor } private val bouncerInteractor by lazy { kosmos.bouncerInteractor } private val faceAuthRepository by lazy { kosmos.fakeDeviceEntryFaceAuthRepository } + private val bouncerRepository by lazy { kosmos.fakeKeyguardBouncerRepository } private val sysUiState = kosmos.sysUiState private val falsingCollector = mock<FalsingCollector>().also { kosmos.falsingCollector = it } private val fakeSceneDataSource = kosmos.fakeSceneDataSource @@ -228,6 +230,32 @@ class SceneContainerStartableTest : SysuiTestCase() { } @Test + fun hydrateVisibility_basedOnAlternateBouncer() = + testScope.runTest { + val isVisible by collectLastValue(sceneInteractor.isVisible) + prepareState( + isDeviceUnlocked = false, + initialSceneKey = Scenes.Lockscreen, + ) + + underTest.start() + assertThat(isVisible).isTrue() + + // WHEN the device is occluded, + kosmos.keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop( + true, + mock() + ) + // THEN scenes are not visible + assertThat(isVisible).isFalse() + + // WHEN the alternate bouncer is visible + kosmos.fakeKeyguardBouncerRepository.setAlternateVisible(true) + // THEN scenes visible + assertThat(isVisible).isTrue() + } + + @Test fun startsInLockscreenScene() = testScope.runTest { val currentSceneKey by collectLastValue(sceneInteractor.currentScene) @@ -275,6 +303,66 @@ class SceneContainerStartableTest : SysuiTestCase() { } @Test + fun switchFromLockscreenToGoneAndHideAltBouncerWhenDeviceUnlocked() = + testScope.runTest { + val alternateBouncerVisible by + collectLastValue(bouncerRepository.alternateBouncerVisible) + val currentSceneKey by collectLastValue(sceneInteractor.currentScene) + + bouncerRepository.setAlternateVisible(true) + assertThat(alternateBouncerVisible).isTrue() + + prepareState( + authenticationMethod = AuthenticationMethodModel.Pin, + isDeviceUnlocked = false, + initialSceneKey = Scenes.Lockscreen, + ) + assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) + underTest.start() + + kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) + + assertThat(currentSceneKey).isEqualTo(Scenes.Gone) + assertThat(alternateBouncerVisible).isFalse() + } + + @Test + fun stayOnCurrentSceneAndHideAltBouncerWhenDeviceUnlocked_whenLeaveOpenShade() = + testScope.runTest { + val alternateBouncerVisible by + collectLastValue(bouncerRepository.alternateBouncerVisible) + val currentSceneKey by collectLastValue(sceneInteractor.currentScene) + + kosmos.sysuiStatusBarStateController.leaveOpen = true // leave shade open + bouncerRepository.setAlternateVisible(true) + assertThat(alternateBouncerVisible).isTrue() + + val transitionState = + prepareState( + authenticationMethod = AuthenticationMethodModel.Pin, + isDeviceUnlocked = false, + initialSceneKey = Scenes.Lockscreen, + ) + assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) + underTest.start() + runCurrent() + + sceneInteractor.changeScene(Scenes.QuickSettings, "switching to qs for test") + transitionState.value = ObservableTransitionState.Idle(Scenes.QuickSettings) + runCurrent() + assertThat(currentSceneKey).isEqualTo(Scenes.QuickSettings) + + kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) + + assertThat(currentSceneKey).isEqualTo(Scenes.QuickSettings) + assertThat(alternateBouncerVisible).isFalse() + } + + @Test fun switchFromBouncerToQuickSettingsWhenDeviceUnlocked_whenLeaveOpenShade() = testScope.runTest { val currentSceneKey by collectLastValue(sceneInteractor.currentScene) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorImplTest.kt index ab184abdc963..f232d52615a4 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorImplTest.kt @@ -28,6 +28,7 @@ import com.android.systemui.volume.panel.domain.model.ComponentModel import com.android.systemui.volume.panel.domain.unavailableCriteria import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey import com.android.systemui.volume.panel.ui.composable.enabledComponents +import com.android.systemui.volume.shared.volumePanelLogger import com.google.common.truth.Truth.assertThat import javax.inject.Provider import kotlinx.coroutines.test.runTest @@ -49,6 +50,7 @@ class ComponentsInteractorImplTest : SysuiTestCase() { enabledComponents, { defaultCriteria }, testScope.backgroundScope, + volumePanelLogger, criteriaByKey, ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt index 420b955e88e0..51a70bda6034 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt @@ -24,21 +24,30 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.broadcastDispatcher import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.dump.DumpManager import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testScope +import com.android.systemui.statusbar.policy.configurationController import com.android.systemui.statusbar.policy.fakeConfigurationController import com.android.systemui.testKosmos +import com.android.systemui.volume.panel.dagger.factory.volumePanelComponentFactory import com.android.systemui.volume.panel.data.repository.volumePanelGlobalStateRepository import com.android.systemui.volume.panel.domain.interactor.criteriaByKey +import com.android.systemui.volume.panel.domain.interactor.volumePanelGlobalStateInteractor import com.android.systemui.volume.panel.domain.unavailableCriteria import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey import com.android.systemui.volume.panel.shared.model.mockVolumePanelUiComponentProvider import com.android.systemui.volume.panel.ui.composable.componentByKey import com.android.systemui.volume.panel.ui.layout.DefaultComponentsLayoutManager import com.android.systemui.volume.panel.ui.layout.componentsLayoutManager +import com.android.systemui.volume.shared.volumePanelLogger import com.google.common.truth.Truth.assertThat +import java.io.PrintWriter +import java.io.StringWriter import javax.inject.Provider import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.launch import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Test @@ -55,6 +64,7 @@ class VolumePanelViewModelTest : SysuiTestCase() { volumePanelGlobalStateRepository.updateVolumePanelState { it.copy(isVisible = true) } } + private val realDumpManager = DumpManager() private val testableResources = context.orCreateTestableResources private lateinit var underTest: VolumePanelViewModel @@ -124,6 +134,60 @@ class VolumePanelViewModelTest : SysuiTestCase() { } @Test + fun testDumpableRegister_unregister() = + with(kosmos) { + testScope.runTest { + val job = launch { + applicationCoroutineScope = this + underTest = createViewModel() + + runCurrent() + + assertThat(realDumpManager.getDumpables().any { it.name == DUMPABLE_NAME }) + .isTrue() + } + + runCurrent() + job.cancel() + + assertThat(realDumpManager.getDumpables().any { it.name == DUMPABLE_NAME }).isTrue() + } + } + + @Test + fun testDumpingState() = + test({ + componentByKey = + mapOf( + COMPONENT_1 to mockVolumePanelUiComponentProvider, + COMPONENT_2 to mockVolumePanelUiComponentProvider, + BOTTOM_BAR to mockVolumePanelUiComponentProvider, + ) + criteriaByKey = mapOf(COMPONENT_2 to Provider { unavailableCriteria }) + }) { + testScope.runTest { + runCurrent() + + StringWriter().use { + underTest.dump(PrintWriter(it), emptyArray()) + + assertThat(it.buffer.toString()) + .isEqualTo( + "volumePanelState=" + + "VolumePanelState(orientation=1, isLargeScreen=false)\n" + + "componentsLayout=( " + + "headerComponents= " + + "contentComponents=" + + "test_component:1:visible=true, " + + "test_component:2:visible=false " + + "footerComponents= " + + "bottomBarComponent=test_bottom_bar:visible=true )\n" + ) + } + } + } + + @Test fun dismissBroadcast_dismissesPanel() = test { testScope.runTest { runCurrent() // run the flows to let allow the receiver to be registered @@ -140,11 +204,26 @@ class VolumePanelViewModelTest : SysuiTestCase() { private fun test(setup: Kosmos.() -> Unit = {}, test: Kosmos.() -> Unit) = with(kosmos) { setup() - underTest = volumePanelViewModel + underTest = createViewModel() + test() } + private fun Kosmos.createViewModel(): VolumePanelViewModel = + VolumePanelViewModel( + context.orCreateTestableResources.resources, + applicationCoroutineScope, + volumePanelComponentFactory, + configurationController, + broadcastDispatcher, + realDumpManager, + volumePanelLogger, + volumePanelGlobalStateInteractor, + ) + private companion object { + const val DUMPABLE_NAME = "VolumePanelViewModel" + const val BOTTOM_BAR: VolumePanelComponentKey = "test_bottom_bar" const val COMPONENT_1: VolumePanelComponentKey = "test_component:1" const val COMPONENT_2: VolumePanelComponentKey = "test_component:2" diff --git a/packages/SystemUI/res/color/audio_sharing_btn_text_color.xml b/packages/SystemUI/res/color/audio_sharing_btn_text_color.xml new file mode 100644 index 000000000000..b72835e6475a --- /dev/null +++ b/packages/SystemUI/res/color/audio_sharing_btn_text_color.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2024 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<selector xmlns:android="http://schemas.android.com/apk/res/android" > + <item android:state_activated="true" + android:color="@color/qs_dialog_btn_filled_text_color" /> + <item android:color="@color/qs_dialog_btn_outline_text" /> +</selector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/audio_sharing_btn_background.xml b/packages/SystemUI/res/drawable/audio_sharing_btn_background.xml new file mode 100644 index 000000000000..310e095205a0 --- /dev/null +++ b/packages/SystemUI/res/drawable/audio_sharing_btn_background.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2024 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_activated="true" android:drawable="@drawable/qs_dialog_btn_filled" /> + <item android:drawable="@drawable/qs_dialog_btn_outline" /> +</selector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/ic_bugreport.xml b/packages/SystemUI/res/drawable/ic_bugreport.xml index ed1c6c723543..badbd8845050 100644 --- a/packages/SystemUI/res/drawable/ic_bugreport.xml +++ b/packages/SystemUI/res/drawable/ic_bugreport.xml @@ -19,14 +19,14 @@ android:height="24.0dp" android:viewportWidth="24.0" android:viewportHeight="24.0" - android:tint="?attr/colorControlNormal"> + android:tint="?android:attr/colorControlNormal"> <path - android:fillColor="#FF000000" + android:fillColor="#FFFFFFFF" android:pathData="M20,10V8h-2.81c-0.45,-0.78 -1.07,-1.46 -1.82,-1.96L17,4.41L15.59,3l-2.17,2.17c-0.03,-0.01 -0.05,-0.01 -0.08,-0.01c-0.16,-0.04 -0.32,-0.06 -0.49,-0.09c-0.06,-0.01 -0.11,-0.02 -0.17,-0.03C12.46,5.02 12.23,5 12,5h0c-0.49,0 -0.97,0.07 -1.42,0.18l0.02,-0.01L8.41,3L7,4.41l1.62,1.63l0.01,0C7.88,6.54 7.26,7.22 6.81,8H4v2h2.09C6.03,10.33 6,10.66 6,11v1H4v2h2v1c0,0.34 0.04,0.67 0.09,1H4v2h2.81c1.04,1.79 2.97,3 5.19,3h0c2.22,0 4.15,-1.21 5.19,-3H20v-2h-2.09l0,0c0.05,-0.33 0.09,-0.66 0.09,-1v-1h2v-2h-2v-1c0,-0.34 -0.04,-0.67 -0.09,-1l0,0H20zM16,15c0,2.21 -1.79,4 -4,4c-2.21,0 -4,-1.79 -4,-4v-4c0,-2.21 1.79,-4 4,-4h0c2.21,0 4,1.79 4,4V15z"/> <path - android:fillColor="#FF000000" + android:fillColor="#FFFFFFFF" android:pathData="M10,14h4v2h-4z"/> <path - android:fillColor="#FF000000" + android:fillColor="#FFFFFFFF" android:pathData="M10,10h4v2h-4z"/> </vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/biometric_prompt_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_layout.xml deleted file mode 100644 index ff89ed9e6e7a..000000000000 --- a/packages/SystemUI/res/layout/biometric_prompt_layout.xml +++ /dev/null @@ -1,208 +0,0 @@ -<!-- - ~ Copyright (C) 2023 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> -<com.android.systemui.biometrics.ui.BiometricPromptLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/contents" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:orientation="vertical"> - - <ImageView - android:id="@+id/logo" - android:layout_width="@dimen/biometric_auth_icon_size" - android:layout_height="@dimen/biometric_auth_icon_size" - android:layout_gravity="center" - android:scaleType="fitXY"/> - - <TextView - android:id="@+id/logo_description" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:gravity="@integer/biometric_dialog_text_gravity" - android:singleLine="true" - android:marqueeRepeatLimit="1" - android:ellipsize="marquee"/> - - <TextView - android:id="@+id/title" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:gravity="@integer/biometric_dialog_text_gravity" - android:singleLine="true" - android:marqueeRepeatLimit="1" - android:ellipsize="marquee" - style="@style/TextAppearance.AuthCredential.OldTitle"/> - - <TextView - android:id="@+id/subtitle" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:gravity="@integer/biometric_dialog_text_gravity" - android:singleLine="true" - android:marqueeRepeatLimit="1" - android:ellipsize="marquee" - style="@style/TextAppearance.AuthCredential.OldSubtitle"/> - - <TextView - android:id="@+id/description" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:gravity="@integer/biometric_dialog_text_gravity" - android:scrollbars ="vertical" - android:importantForAccessibility="no" - style="@style/TextAppearance.AuthCredential.OldDescription"/> - - <Space - android:id="@+id/space_above_content" - android:layout_width="match_parent" - android:layout_height="24dp" - android:visibility="gone" /> - - <LinearLayout - android:id="@+id/customized_view_container" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:fadeScrollbars="false" - android:gravity="center_vertical" - android:orientation="vertical" - android:scrollbars="vertical" - android:visibility="gone" /> - - <Space android:id="@+id/space_above_icon" - android:layout_width="match_parent" - android:layout_height="48dp" /> - - <FrameLayout - android:id="@+id/biometric_icon_frame" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center"> - - <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper - android:id="@+id/biometric_icon" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:contentDescription="@null" - android:scaleType="fitXY" /> - - <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper - android:id="@+id/biometric_icon_overlay" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:contentDescription="@null" - android:scaleType="fitXY" /> - </FrameLayout> - - <!-- For sensors such as UDFPS, this view is used during custom measurement/layout to add extra - padding so that the biometric icon is always in the right physical position. --> - <Space android:id="@+id/space_below_icon" - android:layout_width="match_parent" - android:layout_height="12dp" /> - - <TextView - android:id="@+id/indicator" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:paddingHorizontal="24dp" - android:textSize="12sp" - android:gravity="center_horizontal" - android:accessibilityLiveRegion="polite" - android:singleLine="true" - android:ellipsize="marquee" - android:marqueeRepeatLimit="marquee_forever" - android:scrollHorizontally="true" - android:fadingEdge="horizontal" - android:textColor="@color/biometric_dialog_gray"/> - - <LinearLayout - android:id="@+id/button_bar" - android:layout_width="match_parent" - android:layout_height="88dp" - style="?android:attr/buttonBarStyle" - android:orientation="horizontal" - android:paddingTop="24dp"> - - <Space android:id="@+id/leftSpacer" - android:layout_width="8dp" - android:layout_height="match_parent" - android:visibility="visible" /> - - <!-- Negative Button, reserved for app --> - <Button android:id="@+id/button_negative" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored" - android:layout_gravity="center_vertical" - android:ellipsize="end" - android:maxLines="2" - android:maxWidth="@dimen/biometric_dialog_button_negative_max_width" - android:visibility="gone"/> - <!-- Cancel Button, replaces negative button when biometric is accepted --> - <Button android:id="@+id/button_cancel" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored" - android:layout_gravity="center_vertical" - android:maxWidth="@dimen/biometric_dialog_button_negative_max_width" - android:text="@string/cancel" - android:visibility="gone"/> - <!-- "Use Credential" Button, replaces if device credential is allowed --> - <Button android:id="@+id/button_use_credential" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored" - android:layout_gravity="center_vertical" - android:maxWidth="@dimen/biometric_dialog_button_negative_max_width" - android:visibility="gone"/> - - <Space android:id="@+id/middleSpacer" - android:layout_width="0dp" - android:layout_height="match_parent" - android:layout_weight="1" - android:visibility="visible"/> - - <!-- Positive Button --> - <Button android:id="@+id/button_confirm" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - style="@*android:style/Widget.DeviceDefault.Button.Colored" - android:layout_gravity="center_vertical" - android:ellipsize="end" - android:maxLines="2" - android:maxWidth="@dimen/biometric_dialog_button_positive_max_width" - android:text="@string/biometric_dialog_confirm" - android:visibility="gone"/> - <!-- Try Again Button --> - <Button android:id="@+id/button_try_again" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - style="@*android:style/Widget.DeviceDefault.Button.Colored" - android:layout_gravity="center_vertical" - android:ellipsize="end" - android:maxLines="2" - android:maxWidth="@dimen/biometric_dialog_button_positive_max_width" - android:text="@string/biometric_dialog_try_again" - android:visibility="gone"/> - - <Space android:id="@+id/rightSpacer" - android:layout_width="8dp" - android:layout_height="match_parent" - android:visibility="visible" /> - </LinearLayout> - -</com.android.systemui.biometrics.ui.BiometricPromptLayout> diff --git a/packages/SystemUI/res/layout/bluetooth_device_item.xml b/packages/SystemUI/res/layout/bluetooth_device_item.xml index 4535f67fa7f9..b9314c7ae0e0 100644 --- a/packages/SystemUI/res/layout/bluetooth_device_item.xml +++ b/packages/SystemUI/res/layout/bluetooth_device_item.xml @@ -41,6 +41,7 @@ android:textDirection="locale" android:textAlignment="gravity" android:paddingStart="20dp" + android:paddingEnd="10dp" android:paddingTop="15dp" android:maxLines="1" android:ellipsize="end" @@ -56,6 +57,7 @@ android:id="@+id/bluetooth_device_summary" style="@style/BluetoothTileDialog.DeviceSummary" android:paddingStart="20dp" + android:paddingEnd="10dp" android:paddingBottom="15dp" android:maxLines="1" android:ellipsize="end" @@ -73,11 +75,20 @@ android:orientation="vertical"/> <View + android:id="@+id/divider" + android:layout_width="1dp" + android:layout_height="38dp" + app:layout_constraintStart_toEndOf="@+id/guideline" + app:layout_constraintEnd_toStartOf="@+id/gear_icon" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" /> + + <View android:id="@+id/gear_icon" android:layout_width="0dp" android:layout_height="0dp" android:contentDescription="@string/accessibility_bluetooth_device_settings_gear" - app:layout_constraintStart_toEndOf="@+id/guideline" + app:layout_constraintStart_toEndOf="@+id/divider" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" /> diff --git a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml index 27b80066e0f4..b4eaa407889a 100644 --- a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml +++ b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml @@ -258,7 +258,7 @@ <Button android:id="@+id/audio_sharing_button" - style="@style/Widget.Dialog.Button.BorderButton" + style="@style/BluetoothTileDialog.AudioSharingButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="9dp" @@ -270,7 +270,6 @@ android:text="@string/quick_settings_bluetooth_audio_sharing_button" android:drawableStart="@drawable/ic_bt_le_audio_sharing_18dp" android:drawablePadding="10dp" - android:drawableTint="?android:attr/textColorPrimary" app:layout_constrainedWidth="true" app:layout_constraintHorizontal_bias="0" app:layout_constraintEnd_toStartOf="@+id/done_button" diff --git a/packages/SystemUI/res/layout/media_projection_app_selector.xml b/packages/SystemUI/res/layout/media_projection_app_selector.xml index b77f78dfc644..3b6a64ffa7d0 100644 --- a/packages/SystemUI/res/layout/media_projection_app_selector.xml +++ b/packages/SystemUI/res/layout/media_projection_app_selector.xml @@ -37,6 +37,7 @@ android:background="@*android:drawable/bottomsheet_background"> <ImageView + android:id="@+id/media_projection_app_selector_icon" android:layout_width="@dimen/media_projection_app_selector_icon_size" android:layout_height="@dimen/media_projection_app_selector_icon_size" android:layout_marginTop="@*android:dimen/chooser_edge_margin_normal" diff --git a/packages/SystemUI/res/raw/action_key_edu.json b/packages/SystemUI/res/raw/action_key_edu.json new file mode 100644 index 000000000000..014d83798da9 --- /dev/null +++ b/packages/SystemUI/res/raw/action_key_edu.json @@ -0,0 +1 @@ +{"v":"5.12.1","fr":60,"ip":0,"op":181,"w":554,"h":564,"nm":"Trackpad-JSON_ActionKey-EDU","ddd":0,"assets":[{"id":"comp_0","nm":"BlankButton","fr":60,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[40,39.79,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[80,79.581]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":14.032},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980401039,0.768627464771,0.627451002598,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"shortcut symbols","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":80},"r":{"a":0,"k":0},"p":{"a":0,"k":[40,49.21,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[80,79.581]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":14.032},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705890417,0.258823543787,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"shadow","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]},{"id":"comp_1","nm":"actionKey_themed","fr":60,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".onSecondaryFixed","cl":"onSecondaryFixed","parent":3,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[0.288,-0.035,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0.579,-0.158],[0.605,0],[1.21,1.21],[0,1.684],[-1.184,1.184],[-1.684,0],[-1.21,-1.21],[0,-1.71],[0.184,-0.553],[0.316,-0.474],[0,0],[0,0]],"o":[[-0.474,0.316],[-0.553,0.158],[-1.684,0],[-1.184,-1.21],[0,-1.71],[1.21,-1.21],[1.71,0],[1.21,1.184],[0,0.605],[-0.158,0.553],[0,0],[0,0],[0,0]],"v":[[10.241,12.155],[8.663,12.866],[6.926,13.103],[2.585,11.287],[0.809,6.946],[2.585,2.605],[6.926,0.789],[11.307,2.605],[13.122,6.946],[12.846,8.682],[12.136,10.222],[16.911,14.997],[15.017,16.891]],"c":true}},"nm":"Path 1","hd":false},{"ind":1,"ty":"sh","ks":{"a":0,"k":{"i":[[1.21,1.21],[0,1.684],[-1.21,1.184],[-1.684,0],[-1.184,-1.21],[0,-1.736],[1.21,-1.21],[1.736,0]],"o":[[-1.21,-1.21],[0,-1.736],[1.21,-1.21],[1.736,0],[1.21,1.184],[0,1.684],[-1.184,1.21],[-1.684,0]],"v":[[-15.096,11.327],[-16.911,6.985],[-15.096,2.605],[-10.754,0.789],[-6.374,2.605],[-4.558,6.985],[-6.374,11.327],[-10.754,13.142]],"c":true}},"nm":"Path 2","hd":false},{"ind":2,"ty":"sh","ks":{"a":0,"k":{"i":[[-0.684,0.658],[0,0.947],[0.684,0.684],[0.973,0],[0.684,-0.684],[0,-0.973],[-0.658,-0.684],[-0.947,0]],"o":[[0.684,-0.684],[0,-0.973],[-0.684,-0.684],[-0.947,0],[-0.658,0.684],[0,0.947],[0.684,0.658],[0.973,0]],"v":[[-8.268,9.432],[-7.242,6.985],[-8.268,4.499],[-10.754,3.473],[-13.201,4.499],[-14.188,6.985],[-13.201,9.432],[-10.754,10.419]],"c":true}},"nm":"Path 3","hd":false},{"ind":3,"ty":"sh","ks":{"a":0,"k":{"i":[[-0.684,0.658],[0,0.947],[0.684,0.684],[0.973,0],[0.684,-0.684],[0,-0.973],[-0.684,-0.684],[-0.947,0]],"o":[[0.684,-0.684],[0,-0.973],[-0.684,-0.684],[-0.947,0],[-0.684,0.684],[0,0.947],[0.684,0.658],[0.973,0]],"v":[[9.413,9.432],[10.439,6.985],[9.413,4.499],[6.926,3.473],[4.479,4.499],[3.453,6.985],[4.479,9.432],[6.926,10.419]],"c":true}},"nm":"Path 4","hd":false},{"ind":4,"ty":"sh","ks":{"a":0,"k":{"i":[[1.21,1.21],[0,1.684],[-1.21,1.21],[-1.684,0],[-1.184,-1.21],[0,-1.71],[1.21,-1.21],[1.736,0]],"o":[[-1.21,-1.21],[0,-1.71],[1.21,-1.21],[1.736,0],[1.21,1.21],[0,1.684],[-1.184,1.21],[-1.684,0]],"v":[[-15.096,-6.354],[-16.911,-10.695],[-15.096,-15.076],[-10.754,-16.891],[-6.374,-15.076],[-4.558,-10.695],[-6.374,-6.354],[-10.754,-4.539]],"c":true}},"nm":"Path 5","hd":false},{"ind":5,"ty":"sh","ks":{"a":0,"k":{"i":[[1.21,1.21],[0,1.684],[-1.21,1.21],[-1.684,0],[-1.21,-1.21],[0,-1.71],[1.21,-1.21],[1.71,0]],"o":[[-1.21,-1.21],[0,-1.71],[1.21,-1.21],[1.71,0],[1.21,1.21],[0,1.684],[-1.21,1.21],[-1.684,0]],"v":[[2.585,-6.354],[0.77,-10.695],[2.585,-15.076],[6.926,-16.891],[11.307,-15.076],[13.122,-10.695],[11.307,-6.354],[6.926,-4.539]],"c":true}},"nm":"Path 6","hd":false},{"ind":6,"ty":"sh","ks":{"a":0,"k":{"i":[[-0.684,0.684],[0,0.947],[0.684,0.684],[0.973,0],[0.684,-0.684],[0,-0.973],[-0.658,-0.684],[-0.947,0]],"o":[[0.684,-0.684],[0,-0.973],[-0.684,-0.684],[-0.947,0],[-0.658,0.684],[0,0.947],[0.684,0.684],[0.973,0]],"v":[[-8.268,-8.248],[-7.242,-10.695],[-8.268,-13.182],[-10.754,-14.208],[-13.201,-13.182],[-14.188,-10.695],[-13.201,-8.248],[-10.754,-7.222]],"c":true}},"nm":"Path 7","hd":false},{"ind":7,"ty":"sh","ks":{"a":0,"k":{"i":[[-0.684,0.684],[0,0.947],[0.684,0.684],[0.973,0],[0.684,-0.684],[0,-0.973],[-0.684,-0.684],[-0.947,0]],"o":[[0.684,-0.684],[0,-0.973],[-0.684,-0.684],[-0.947,0],[-0.684,0.684],[0,0.947],[0.684,0.684],[0.973,0]],"v":[[9.413,-8.248],[10.439,-10.695],[9.413,-13.182],[6.926,-14.208],[4.479,-13.182],[3.453,-10.695],[4.479,-8.248],[6.926,-7.222]],"c":true}},"nm":"Path 8","hd":false},{"ty":"fl","c":{"a":0,"k":[0.145098045468,0.101960785687,0.015686275437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"icon","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".primaryFixedDim","cl":"primaryFixedDim","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.64],"y":[1]},"o":{"x":[0.33],"y":[0.52]},"t":34,"s":[0]},{"i":{"x":[0.64],"y":[1]},"o":{"x":[0.36],"y":[0]},"t":37,"s":[100]},{"i":{"x":[0.64],"y":[0.48]},"o":{"x":[0.36],"y":[0]},"t":40,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":46,"s":[0]},{"i":{"x":[0.64],"y":[1]},"o":{"x":[0.33],"y":[0.52]},"t":124,"s":[0]},{"i":{"x":[0.64],"y":[1]},"o":{"x":[0.36],"y":[0]},"t":127,"s":[100]},{"i":{"x":[0.64],"y":[0.48]},"o":{"x":[0.36],"y":[0]},"t":130,"s":[100]},{"t":136,"s":[0]}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,0,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[80,79.581]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":14.032},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.92549020052,0.752941191196,0.423529416323,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"shortcut symbols","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":1,"k":[{"i":{"x":0.072,"y":0.635},"o":{"x":0.424,"y":0.112},"t":27,"s":[40,39.79,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0,"y":1},"o":{"x":0.313,"y":0.131},"t":39,"s":[40,49.21,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":57,"s":[40,39.79,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.07,"y":0.63},"o":{"x":0.42,"y":0.11},"t":117,"s":[40,39.79,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0,"y":1},"o":{"x":0.313,"y":0.131},"t":129,"s":[40,49.21,0],"to":[0,0,0],"ti":[0,0,0]},{"t":147,"s":[40,39.79,0]}]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[80,79.581]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":14.032},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980392157,0.76862745098,0.627450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"shortcut symbols","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":80},"r":{"a":0,"k":0},"p":{"a":0,"k":[40,49.21,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[80,79.581]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":14.032},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705890417,0.258823543787,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"shadow","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]},{"id":"comp_2","nm":"AllApps_Tray_themed","fr":60,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,-8.859,0]},"a":{"a":0,"k":[277,256.562,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Safety app","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[400.047,330.391]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Row 4 App - 6","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Settings","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[350.828,330.391]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Row 4 App - 5","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"SoundCloud","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[301.609,330.391]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Row 4 App - 4","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Starbucks","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[252.391,330.391]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Row 4 App - 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Snapchat","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[203.172,330.391]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Row 4 App - 2","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Spotify","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[153.953,330.391]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Row 4 App - 1","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Safety app","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[400.047,281.172]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Row 3 App - 6","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Settings","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[350.828,281.172]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Row 3 App - 5","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"SoundCloud","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[301.609,281.172]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Row 3 App - 4","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Starbucks","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[252.391,281.172]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Row 3 App - 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Snapchat","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[203.172,281.172]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Row 3 App - 2","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Spotify","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[153.953,281.172]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Row 3 App - 1","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Safety app","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[400.047,231.953]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Row 2 App - 6","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Settings","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[350.828,231.953]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Row 2 App - 5","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"SoundCloud","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[301.609,231.953]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Row 2 App - 4","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Starbucks","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[252.391,231.953]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Row 2 App - 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Snapchat","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[203.172,231.953]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Row 2 App - 2","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Spotify","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[153.953,231.953]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Row 2 App - 1","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Safety app","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[400.047,182.734]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Row 1 App - 6","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Settings","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[350.828,182.734]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Row 1 App - 5","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"SoundCloud","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[301.609,182.734]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Row 1 App - 4","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Starbucks","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[252.391,182.734]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Row 1 App - 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Snapchat","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[203.172,182.734]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Row 1 App - 2","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Spotify","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[153.953,182.734]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Row 1 App - 1","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,-129.938,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[275.625,25.594]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980401039,0.768627464771,0.627451002598,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"560x52","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,-151.594,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[15.75,1.969]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980401039,0.768627464771,0.627451002598,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"handle","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".onSecondaryFixed","cl":"onSecondaryFixed","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":1,"k":[{"i":{"x":0.1,"y":1},"o":{"x":0.05,"y":0.7},"t":45,"s":[277,516,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.214,"y":0.214},"t":75,"s":[277,265.422,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.999,"y":1},"o":{"x":0.3,"y":0},"t":135,"s":[277,265.422,0],"to":[0,0,0],"ti":[0,0,0]},{"t":147,"s":[277,516,0]}]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[316.969,320.906]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":13.78},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.145098045468,0.101960785687,0.015686275437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"all apps","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]},{"id":"comp_3","nm":"AK_LofiLauncher","fr":60,"pfr":1,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Scale Down","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":51,"s":[50]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":135,"s":[50]},{"t":144,"s":[100]}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[252,157.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":1,"k":[{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.153,0.153,0.153],"y":[0.074,0.074,0]},"t":45,"s":[100,100,100]},{"i":{"x":[0.841,0.841,0.841],"y":[1,1,1]},"o":{"x":[0.161,0.161,0.161],"y":[0,0,0]},"t":75,"s":[95,95,100]},{"i":{"x":[0,0,0],"y":[1,1,1]},"o":{"x":[0.2,0.2,0.2],"y":[0,0,0]},"t":135,"s":[95,95,100]},{"t":165,"s":[100,100,100]}]}},"ao":0,"ef":[{"ty":5,"nm":"Void","np":19,"mn":"Pseudo/250958","ix":1,"en":1,"ef":[{"ty":0,"nm":"Width","mn":"Pseudo/250958-0001","ix":1,"v":{"a":0,"k":100}},{"ty":0,"nm":"Height","mn":"Pseudo/250958-0002","ix":2,"v":{"a":0,"k":100}},{"ty":0,"nm":"Offset X","mn":"Pseudo/250958-0003","ix":3,"v":{"a":0,"k":0}},{"ty":0,"nm":"Offset Y","mn":"Pseudo/250958-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Roundness","mn":"Pseudo/250958-0005","ix":5,"v":{"a":0,"k":0}},{"ty":6,"nm":"About","mn":"Pseudo/250958-0006","ix":6,"v":0},{"ty":6,"nm":"Plague of null layers.","mn":"Pseudo/250958-0007","ix":7,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0008","ix":8,"v":0},{"ty":6,"nm":"Following projects","mn":"Pseudo/250958-0009","ix":9,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0010","ix":10,"v":0},{"ty":6,"nm":"through time.","mn":"Pseudo/250958-0011","ix":11,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0012","ix":12,"v":0},{"ty":6,"nm":"Be free of the past.","mn":"Pseudo/250958-0013","ix":13,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0014","ix":14,"v":0},{"ty":6,"nm":"Copyright 2023 Battle Axe Inc","mn":"Pseudo/250958-0015","ix":15,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0016","ix":16,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0017","ix":17,"v":0}]}],"shapes":[],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":1,"sr":1,"ks":{"o":{"k":[{"s":[100],"t":45,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[70],"t":51,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[70],"t":135,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":144,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,117.5,0]},"a":{"a":0,"k":[252,275,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[444,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 5","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[396,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 4","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[348,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[300,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 2","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[252,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 1","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[168,20]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":15},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"qsb","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[132,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"qsb","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":1,"sr":1,"ks":{"o":{"k":[{"s":[100],"t":45,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[70],"t":51,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[70],"t":135,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":144,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,-29.497,0]},"a":{"a":0,"k":[252,128.003,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[20.144,20.144],[20.144,-20.144],[0,0],[-20.144,-20.144],[-20.144,20.144],[0,0]],"o":[[-20.144,-20.144],[0,0],[-20.144,20.144],[20.144,20.144],[0,0],[20.144,-20.144]],"v":[[44.892,-44.892],[-28.057,-44.892],[-44.892,-28.057],[-44.892,44.892],[28.057,44.892],[44.892,28.057]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"widgets","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[108,152.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"widgets weather","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[4.782,-2.684],[0,0],[2.63,-0.033],[0,0],[2.807,-4.716],[0,0],[2.263,-1.343],[0,0],[0.066,-5.485],[0,0],[1.292,-2.295],[0,0],[-2.683,-4.784],[0,0],[-0.033,-2.63],[0,0],[-4.716,-2.807],[0,0],[-1.338,-2.263],[0,0],[-5.483,-0.066],[0,0],[-2.296,-1.292],[0,0],[-4.782,2.683],[0,0],[-2.63,0.033],[0,0],[-2.807,4.716],[0,0],[-2.263,1.338],[0,0],[-0.066,5.483],[0,0],[-1.292,2.295],[0,0],[2.683,4.784],[0,0],[0.033,2.631],[0,0],[4.716,2.801],[0,0],[1.338,2.262],[0,0],[5.483,0.068],[0,0],[2.296,1.287]],"o":[[-4.782,-2.684],[0,0],[-2.296,1.287],[0,0],[-5.483,0.068],[0,0],[-1.338,2.262],[0,0],[-4.716,2.801],[0,0],[-0.033,2.631],[0,0],[-2.683,4.784],[0,0],[1.292,2.295],[0,0],[0.066,5.483],[0,0],[2.263,1.338],[0,0],[2.807,4.716],[0,0],[2.63,0.033],[0,0],[4.782,2.683],[0,0],[2.296,-1.292],[0,0],[5.483,-0.066],[0,0],[1.338,-2.263],[0,0],[4.716,-2.807],[0,0],[0.033,-2.63],[0,0],[2.683,-4.784],[0,0],[-1.292,-2.295],[0,0],[-0.066,-5.485],[0,0],[-2.263,-1.343],[0,0],[-2.807,-4.716],[0,0],[-2.63,-0.033],[0,0]],"v":[[7.7,-57.989],[-7.7,-57.989],[-11.019,-56.128],[-18.523,-54.117],[-22.327,-54.07],[-35.668,-46.369],[-37.609,-43.1],[-43.099,-37.605],[-46.372,-35.663],[-54.072,-22.324],[-54.118,-18.522],[-56.132,-11.016],[-57.988,-7.7],[-57.988,7.703],[-56.132,11.019],[-54.118,18.524],[-54.072,22.328],[-46.372,35.669],[-43.099,37.611],[-37.609,43.101],[-35.668,46.373],[-22.327,54.074],[-18.523,54.12],[-11.019,56.133],[-7.7,57.99],[7.7,57.99],[11.019,56.133],[18.523,54.12],[22.327,54.074],[35.668,46.373],[37.609,43.101],[43.099,37.611],[46.372,35.669],[54.072,22.328],[54.118,18.524],[56.132,11.019],[57.988,7.703],[57.988,-7.7],[56.132,-11.016],[54.118,-18.522],[54.072,-22.324],[46.372,-35.663],[43.099,-37.605],[37.609,-43.1],[35.668,-46.369],[22.327,-54.07],[18.523,-54.117],[11.019,-56.128]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"widgets","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[396,104.003]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"widgets clock","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":1,"sr":1,"ks":{"o":{"k":[{"s":[100],"t":45,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[70],"t":51,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[70],"t":135,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":144,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,-29.497,0]},"a":{"a":0,"k":[252,128.003,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[444,200.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 7","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[348,200.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 6","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[252,200.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 5","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[252,128.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 4","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[252,56.002]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[156,56.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 2","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[60,56.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 1","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":0,"nm":"BlankButton","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[133,455.5,0]},"a":{"a":0,"k":[40,44.5,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":80,"h":89,"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"BlankButton","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[229,455.5,0]},"a":{"a":0,"k":[40,44.5,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":80,"h":89,"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":0,"nm":"BlankButton","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[421,455.5,0]},"a":{"a":0,"k":[40,44.5,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":80,"h":89,"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":0,"nm":"actionKey_themed","refId":"comp_1","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[325,455.5,0]},"a":{"a":0,"k":[40,44.5,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":80,"h":89,"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"matte","td":1,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"illustrations: action key","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":0,"nm":"AllApps_Tray_themed","tt":1,"tp":5,"refId":"comp_2","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,282,0]},"a":{"a":0,"k":[277,282,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":554,"h":564,"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":0,"nm":"AK_LofiLauncher","refId":"comp_3","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[252,157.5,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":504,"h":315,"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":50},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"illustrations: action key","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980401039,0.768627464771,0.627451002598,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"illustrations: action key","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.850980392157,0.76862745098,0.627450980392,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":14},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"op","nm":"Stroke align: Outside","a":{"k":[{"s":[7],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[7],"t":180,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"lj":1,"ml":{"a":0,"k":4},"hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980392157,0.76862745098,0.627450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"frame","bm":0,"hd":false}],"ip":0,"op":451,"st":0,"ct":1,"bm":0}],"markers":[],"props":{}}
\ No newline at end of file diff --git a/packages/SystemUI/res/raw/action_key_success.json b/packages/SystemUI/res/raw/action_key_success.json new file mode 100644 index 000000000000..cae7344d04b9 --- /dev/null +++ b/packages/SystemUI/res/raw/action_key_success.json @@ -0,0 +1 @@ +{"v":"5.12.1","fr":60,"ip":0,"op":50,"w":554,"h":564,"nm":"Trackpad-JSON_ActionKey-Success","ddd":0,"assets":[{"id":"comp_0","nm":"TrackpadAK_Success_Checkmark","fr":60,"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Check Rotate","parent":2,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":1,"k":[{"i":{"x":[0.12],"y":[1]},"o":{"x":[0.44],"y":[0]},"t":2,"s":[-16]},{"t":20,"s":[6]}]},"p":{"a":0,"k":[0,0,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[95.049,95.049,100]}},"ao":0,"ip":0,"op":228,"st":-72,"bm":0},{"ddd":0,"ind":2,"ty":3,"nm":"Bounce","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":1,"k":[{"i":{"x":[0.12],"y":[1]},"o":{"x":[0.44],"y":[0]},"t":12,"s":[0]},{"t":36,"s":[-6]}]},"p":{"a":0,"k":[81,127,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":1,"k":[{"i":{"x":[0.263,0.263,0.833],"y":[1.126,1.126,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.958,0.958,0]},"t":1,"s":[80,80,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.45,0.45,0.167],"y":[0.325,0.325,0]},"t":20,"s":[105,105,100]},{"t":36,"s":[100,100,100]}]}},"ao":0,"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":-0.289},"p":{"a":0,"k":[14.364,-33.591,0]},"a":{"a":0,"k":[-0.125,0,0]},"s":{"a":0,"k":[104.744,104.744,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-1.401,-0.007],[-10.033,11.235]],"o":[[5.954,7.288],[1.401,0.007],[0,0]],"v":[[-28.591,4.149],[-10.73,26.013],[31.482,-21.255]],"c":false}},"nm":"Path 1","hd":false},{"ty":"tm","s":{"a":0,"k":0},"e":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":3,"s":[0]},{"i":{"x":[0.22],"y":[1]},"o":{"x":[0.001],"y":[0.149]},"t":10,"s":[29]},{"t":27,"s":[100]}]},"o":{"a":0,"k":0},"m":1,"nm":"Trim Paths 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.850980392157,0.76862745098,0.627450980392,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":11},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":5,"op":44,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[95,95,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":1,"k":[{"i":{"x":[0.275,0.275,0.21],"y":[1.102,1.102,1]},"o":{"x":[0.037,0.037,0.05],"y":[0.476,0.476,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.252,0.252,0.47],"y":[0.159,0.159,0]},"t":16,"s":[120,120,100]},{"t":28,"s":[100,100,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.32,0.32],"y":[0.11,0.11]},"t":16,"s":[148,148]},{"t":28,"s":[136,136]}]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":88},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Checkbox - Widget","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]},{"id":"comp_1","nm":"actionKey_themed-static","fr":60,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".onSecondaryFixed","cl":"onSecondaryFixed","parent":3,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[0.288,-0.035,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0.579,-0.158],[0.605,0],[1.21,1.21],[0,1.684],[-1.184,1.184],[-1.684,0],[-1.21,-1.21],[0,-1.71],[0.184,-0.553],[0.316,-0.474],[0,0],[0,0]],"o":[[-0.474,0.316],[-0.553,0.158],[-1.684,0],[-1.184,-1.21],[0,-1.71],[1.21,-1.21],[1.71,0],[1.21,1.184],[0,0.605],[-0.158,0.553],[0,0],[0,0],[0,0]],"v":[[10.241,12.155],[8.663,12.866],[6.926,13.103],[2.585,11.287],[0.809,6.946],[2.585,2.605],[6.926,0.789],[11.307,2.605],[13.122,6.946],[12.846,8.682],[12.136,10.222],[16.911,14.997],[15.017,16.891]],"c":true}},"nm":"Path 1","hd":false},{"ind":1,"ty":"sh","ks":{"a":0,"k":{"i":[[1.21,1.21],[0,1.684],[-1.21,1.184],[-1.684,0],[-1.184,-1.21],[0,-1.736],[1.21,-1.21],[1.736,0]],"o":[[-1.21,-1.21],[0,-1.736],[1.21,-1.21],[1.736,0],[1.21,1.184],[0,1.684],[-1.184,1.21],[-1.684,0]],"v":[[-15.096,11.327],[-16.911,6.985],[-15.096,2.605],[-10.754,0.789],[-6.374,2.605],[-4.558,6.985],[-6.374,11.327],[-10.754,13.142]],"c":true}},"nm":"Path 2","hd":false},{"ind":2,"ty":"sh","ks":{"a":0,"k":{"i":[[-0.684,0.658],[0,0.947],[0.684,0.684],[0.973,0],[0.684,-0.684],[0,-0.973],[-0.658,-0.684],[-0.947,0]],"o":[[0.684,-0.684],[0,-0.973],[-0.684,-0.684],[-0.947,0],[-0.658,0.684],[0,0.947],[0.684,0.658],[0.973,0]],"v":[[-8.268,9.432],[-7.242,6.985],[-8.268,4.499],[-10.754,3.473],[-13.201,4.499],[-14.188,6.985],[-13.201,9.432],[-10.754,10.419]],"c":true}},"nm":"Path 3","hd":false},{"ind":3,"ty":"sh","ks":{"a":0,"k":{"i":[[-0.684,0.658],[0,0.947],[0.684,0.684],[0.973,0],[0.684,-0.684],[0,-0.973],[-0.684,-0.684],[-0.947,0]],"o":[[0.684,-0.684],[0,-0.973],[-0.684,-0.684],[-0.947,0],[-0.684,0.684],[0,0.947],[0.684,0.658],[0.973,0]],"v":[[9.413,9.432],[10.439,6.985],[9.413,4.499],[6.926,3.473],[4.479,4.499],[3.453,6.985],[4.479,9.432],[6.926,10.419]],"c":true}},"nm":"Path 4","hd":false},{"ind":4,"ty":"sh","ks":{"a":0,"k":{"i":[[1.21,1.21],[0,1.684],[-1.21,1.21],[-1.684,0],[-1.184,-1.21],[0,-1.71],[1.21,-1.21],[1.736,0]],"o":[[-1.21,-1.21],[0,-1.71],[1.21,-1.21],[1.736,0],[1.21,1.21],[0,1.684],[-1.184,1.21],[-1.684,0]],"v":[[-15.096,-6.354],[-16.911,-10.695],[-15.096,-15.076],[-10.754,-16.891],[-6.374,-15.076],[-4.558,-10.695],[-6.374,-6.354],[-10.754,-4.539]],"c":true}},"nm":"Path 5","hd":false},{"ind":5,"ty":"sh","ks":{"a":0,"k":{"i":[[1.21,1.21],[0,1.684],[-1.21,1.21],[-1.684,0],[-1.21,-1.21],[0,-1.71],[1.21,-1.21],[1.71,0]],"o":[[-1.21,-1.21],[0,-1.71],[1.21,-1.21],[1.71,0],[1.21,1.21],[0,1.684],[-1.21,1.21],[-1.684,0]],"v":[[2.585,-6.354],[0.77,-10.695],[2.585,-15.076],[6.926,-16.891],[11.307,-15.076],[13.122,-10.695],[11.307,-6.354],[6.926,-4.539]],"c":true}},"nm":"Path 6","hd":false},{"ind":6,"ty":"sh","ks":{"a":0,"k":{"i":[[-0.684,0.684],[0,0.947],[0.684,0.684],[0.973,0],[0.684,-0.684],[0,-0.973],[-0.658,-0.684],[-0.947,0]],"o":[[0.684,-0.684],[0,-0.973],[-0.684,-0.684],[-0.947,0],[-0.658,0.684],[0,0.947],[0.684,0.684],[0.973,0]],"v":[[-8.268,-8.248],[-7.242,-10.695],[-8.268,-13.182],[-10.754,-14.208],[-13.201,-13.182],[-14.188,-10.695],[-13.201,-8.248],[-10.754,-7.222]],"c":true}},"nm":"Path 7","hd":false},{"ind":7,"ty":"sh","ks":{"a":0,"k":{"i":[[-0.684,0.684],[0,0.947],[0.684,0.684],[0.973,0],[0.684,-0.684],[0,-0.973],[-0.684,-0.684],[-0.947,0]],"o":[[0.684,-0.684],[0,-0.973],[-0.684,-0.684],[-0.947,0],[-0.684,0.684],[0,0.947],[0.684,0.684],[0.973,0]],"v":[[9.413,-8.248],[10.439,-10.695],[9.413,-13.182],[6.926,-14.208],[4.479,-13.182],[3.453,-10.695],[4.479,-8.248],[6.926,-7.222]],"c":true}},"nm":"Path 8","hd":false},{"ty":"fl","c":{"a":0,"k":[0.145098045468,0.101960785687,0.015686275437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"icon","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".primaryFixedDim","cl":"primaryFixedDim","parent":3,"sr":1,"ks":{"o":{"a":0,"k":0},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,0,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[80,79.581]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":14.032},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.92549020052,0.752941191196,0.423529416323,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"shortcut symbols","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[40,39.79,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[80,79.581]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":14.032},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980392157,0.76862745098,0.627450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"shortcut symbols","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":80},"r":{"a":0,"k":0},"p":{"a":0,"k":[40,49.21,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[80,79.581]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":14.032},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705890417,0.258823543787,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"shadow","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]},{"id":"comp_2","nm":"BlankButton","fr":60,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[40,39.79,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[80,79.581]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":14.032},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980401039,0.768627464771,0.627451002598,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"shortcut symbols","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":80},"r":{"a":0,"k":0},"p":{"a":0,"k":[40,49.21,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[80,79.581]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":14.032},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705890417,0.258823543787,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"shadow","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]},{"id":"comp_3","nm":"AK_LofiLauncher","fr":60,"pfr":1,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Scale Down","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":51,"s":[70]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":135,"s":[70]},{"t":144,"s":[100]}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[252,157.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":1,"k":[{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.153,0.153,0.153],"y":[0.074,0.074,0]},"t":45,"s":[100,100,100]},{"i":{"x":[0.841,0.841,0.841],"y":[1,1,1]},"o":{"x":[0.161,0.161,0.161],"y":[0,0,0]},"t":75,"s":[95,95,100]},{"i":{"x":[0,0,0],"y":[1,1,1]},"o":{"x":[0.2,0.2,0.2],"y":[0,0,0]},"t":135,"s":[95,95,100]},{"t":165,"s":[100,100,100]}]}},"ao":0,"ef":[{"ty":5,"nm":"Void","np":19,"mn":"Pseudo/250958","ix":1,"en":1,"ef":[{"ty":0,"nm":"Width","mn":"Pseudo/250958-0001","ix":1,"v":{"a":0,"k":100}},{"ty":0,"nm":"Height","mn":"Pseudo/250958-0002","ix":2,"v":{"a":0,"k":100}},{"ty":0,"nm":"Offset X","mn":"Pseudo/250958-0003","ix":3,"v":{"a":0,"k":0}},{"ty":0,"nm":"Offset Y","mn":"Pseudo/250958-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Roundness","mn":"Pseudo/250958-0005","ix":5,"v":{"a":0,"k":0}},{"ty":6,"nm":"About","mn":"Pseudo/250958-0006","ix":6,"v":0},{"ty":6,"nm":"Plague of null layers.","mn":"Pseudo/250958-0007","ix":7,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0008","ix":8,"v":0},{"ty":6,"nm":"Following projects","mn":"Pseudo/250958-0009","ix":9,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0010","ix":10,"v":0},{"ty":6,"nm":"through time.","mn":"Pseudo/250958-0011","ix":11,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0012","ix":12,"v":0},{"ty":6,"nm":"Be free of the past.","mn":"Pseudo/250958-0013","ix":13,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0014","ix":14,"v":0},{"ty":6,"nm":"Copyright 2023 Battle Axe Inc","mn":"Pseudo/250958-0015","ix":15,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0016","ix":16,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0017","ix":17,"v":0}]}],"shapes":[],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":1,"sr":1,"ks":{"o":{"k":[{"s":[100],"t":45,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[80],"t":49,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,117.5,0]},"a":{"a":0,"k":[252,275,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[444,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 5","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[396,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 4","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[348,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[300,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 2","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[252,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 1","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[168,20]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":15},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"qsb","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[132,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"qsb","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":1,"sr":1,"ks":{"o":{"k":[{"s":[100],"t":45,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[80],"t":49,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,-29.497,0]},"a":{"a":0,"k":[252,128.003,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[20.144,20.144],[20.144,-20.144],[0,0],[-20.144,-20.144],[-20.144,20.144],[0,0]],"o":[[-20.144,-20.144],[0,0],[-20.144,20.144],[20.144,20.144],[0,0],[20.144,-20.144]],"v":[[44.892,-44.892],[-28.057,-44.892],[-44.892,-28.057],[-44.892,44.892],[28.057,44.892],[44.892,28.057]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"widgets","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[108,152.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"widgets weather","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[4.782,-2.684],[0,0],[2.63,-0.033],[0,0],[2.807,-4.716],[0,0],[2.263,-1.343],[0,0],[0.066,-5.485],[0,0],[1.292,-2.295],[0,0],[-2.683,-4.784],[0,0],[-0.033,-2.63],[0,0],[-4.716,-2.807],[0,0],[-1.338,-2.263],[0,0],[-5.483,-0.066],[0,0],[-2.296,-1.292],[0,0],[-4.782,2.683],[0,0],[-2.63,0.033],[0,0],[-2.807,4.716],[0,0],[-2.263,1.338],[0,0],[-0.066,5.483],[0,0],[-1.292,2.295],[0,0],[2.683,4.784],[0,0],[0.033,2.631],[0,0],[4.716,2.801],[0,0],[1.338,2.262],[0,0],[5.483,0.068],[0,0],[2.296,1.287]],"o":[[-4.782,-2.684],[0,0],[-2.296,1.287],[0,0],[-5.483,0.068],[0,0],[-1.338,2.262],[0,0],[-4.716,2.801],[0,0],[-0.033,2.631],[0,0],[-2.683,4.784],[0,0],[1.292,2.295],[0,0],[0.066,5.483],[0,0],[2.263,1.338],[0,0],[2.807,4.716],[0,0],[2.63,0.033],[0,0],[4.782,2.683],[0,0],[2.296,-1.292],[0,0],[5.483,-0.066],[0,0],[1.338,-2.263],[0,0],[4.716,-2.807],[0,0],[0.033,-2.63],[0,0],[2.683,-4.784],[0,0],[-1.292,-2.295],[0,0],[-0.066,-5.485],[0,0],[-2.263,-1.343],[0,0],[-2.807,-4.716],[0,0],[-2.63,-0.033],[0,0]],"v":[[7.7,-57.989],[-7.7,-57.989],[-11.019,-56.128],[-18.523,-54.117],[-22.327,-54.07],[-35.668,-46.369],[-37.609,-43.1],[-43.099,-37.605],[-46.372,-35.663],[-54.072,-22.324],[-54.118,-18.522],[-56.132,-11.016],[-57.988,-7.7],[-57.988,7.703],[-56.132,11.019],[-54.118,18.524],[-54.072,22.328],[-46.372,35.669],[-43.099,37.611],[-37.609,43.101],[-35.668,46.373],[-22.327,54.074],[-18.523,54.12],[-11.019,56.133],[-7.7,57.99],[7.7,57.99],[11.019,56.133],[18.523,54.12],[22.327,54.074],[35.668,46.373],[37.609,43.101],[43.099,37.611],[46.372,35.669],[54.072,22.328],[54.118,18.524],[56.132,11.019],[57.988,7.703],[57.988,-7.7],[56.132,-11.016],[54.118,-18.522],[54.072,-22.324],[46.372,-35.663],[43.099,-37.605],[37.609,-43.1],[35.668,-46.369],[22.327,-54.07],[18.523,-54.117],[11.019,-56.128]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"widgets","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[396,104.003]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"widgets clock","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":1,"sr":1,"ks":{"o":{"k":[{"s":[100],"t":45,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[80],"t":49,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,-29.497,0]},"a":{"a":0,"k":[252,128.003,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[444,200.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 7","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[348,200.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 6","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[252,200.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 5","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[252,128.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 4","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[252,56.002]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[156,56.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 2","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[60,56.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 1","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":0,"nm":"TrackpadAK_Success_Checkmark","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,198.5,0]},"a":{"a":0,"k":[95,95,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":190,"h":190,"ip":6,"op":50,"st":6,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".onSecondaryFixed","cl":"onSecondaryFixed","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":1,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":7,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":389,"s":[100]},{"t":392,"s":[0]}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"ef":[{"ty":5,"nm":"Global Position","np":4,"mn":"Pseudo/88900","ix":1,"en":1,"ef":[{"ty":10,"nm":"Master Parent","mn":"Pseudo/88900-0001","ix":1,"v":{"a":0,"k":2}},{"ty":3,"nm":"Global Position","mn":"Pseudo/88900-0002","ix":2,"v":{"k":[{"s":[277,197.5],"t":0,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,197.5],"t":49,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]}}]}],"shapes":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.145098039216,0.101960784314,0.01568627451,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false}],"ip":0,"op":50,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":0,"nm":"actionKey_themed-static","refId":"comp_1","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[325,455.5,0]},"a":{"a":0,"k":[40,44.5,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":80,"h":89,"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":0,"nm":"BlankButton","refId":"comp_2","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[421,455.5,0]},"a":{"a":0,"k":[40,44.5,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":80,"h":89,"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":0,"nm":"BlankButton","refId":"comp_2","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[229,455.5,0]},"a":{"a":0,"k":[40,44.5,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":80,"h":89,"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":0,"nm":"BlankButton","refId":"comp_2","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[133,455.5,0]},"a":{"a":0,"k":[40,44.5,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":80,"h":89,"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":0,"nm":"AK_LofiLauncher","refId":"comp_3","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[252,157.5,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":504,"h":315,"ip":0,"op":50,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"ef":[{"ty":5,"nm":"Global Position","np":4,"mn":"Pseudo/88900","ix":1,"en":1,"ef":[{"ty":10,"nm":"Master Parent","mn":"Pseudo/88900-0001","ix":1,"v":{"a":0,"k":2}},{"ty":3,"nm":"Global Position","mn":"Pseudo/88900-0002","ix":2,"v":{"k":[{"s":[277,197.5],"t":0,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,197.5],"t":49,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]}}]}],"shapes":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":50},"r":1,"bm":0,"nm":"Fill 1","hd":false}],"ip":0,"op":50,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980401039,0.768627464771,0.627451002598,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"illustrations: action key","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.850980392157,0.76862745098,0.627450980392,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":14},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"op","nm":"Stroke align: Outside","a":{"k":[{"s":[7],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[7],"t":49,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"lj":1,"ml":{"a":0,"k":4},"hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980392157,0.76862745098,0.627450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"frame","bm":0,"hd":false}],"ip":0,"op":50,"st":0,"ct":1,"bm":0}],"markers":[],"props":{}}
\ No newline at end of file diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index e4f900d3a31a..25bca25be732 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -278,4 +278,7 @@ <!-- Id for the udfps accessibility overlay --> <item type="id" name="udfps_accessibility_overlay" /> <item type="id" name="udfps_accessibility_overlay_top_guideline" /> + + <!-- Ids for communal hub widgets --> + <item type="id" name="communal_widget_disposable_tag"/> </resources> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 875159642f93..f8303ea8e1c6 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -280,13 +280,19 @@ <!-- For updated Screen Recording permission dialog (i.e. with PSS)--> <!-- Title for the screen prompting the user to begin recording their screen [CHAR LIMIT=NONE]--> - <string name="screenrecord_permission_dialog_title">Start Recording?</string> + <string name="screenrecord_permission_dialog_title">Record your screen?</string> + <!-- Screen recording permission option for recording just a single app [CHAR LIMIT=50] --> + <string name="screenrecord_permission_dialog_option_text_single_app">Record one app</string> + <!-- Screen recording permission option for recording the whole screen [CHAR LIMIT=50] --> + <string name="screenrecord_permission_dialog_option_text_entire_screen">Record entire screen</string> <!-- Message reminding the user that sensitive information may be captured during a full screen recording for the updated dialog that includes partial screen sharing option [CHAR_LIMIT=350]--> - <string name="screenrecord_permission_dialog_warning_entire_screen">While you’re recording, Android has access to anything visible on your screen or played on your device. So be careful with things like passwords, payment details, messages, photos, and audio and video.</string> + <string name="screenrecord_permission_dialog_warning_entire_screen">When you’re recording your entire screen, anything shown on your screen is recorded. So be careful with things like passwords, payment details, messages, photos, and audio and video.</string> <!-- Message reminding the user that sensitive information may be captured during a single app screen recording for the updated dialog that includes partial screen sharing option [CHAR_LIMIT=350]--> - <string name="screenrecord_permission_dialog_warning_single_app">While you’re recording an app, Android has access to anything shown or played on that app. So be careful with things like passwords, payment details, messages, photos, and audio and video.</string> - <!-- Button to start a screen recording in the updated screen record dialog that allows to select an app to record [CHAR LIMIT=50]--> - <string name="screenrecord_permission_dialog_continue">Start recording</string> + <string name="screenrecord_permission_dialog_warning_single_app">When you’re recording an app, anything shown or played in that app is recorded. So be careful with things like passwords, payment details, messages, photos, and audio and video.</string> + <!-- Button to start a screen recording of the entire screen in the updated screen record dialog that allows to select an app to record [CHAR LIMIT=50]--> + <string name="screenrecord_permission_dialog_continue_entire_screen">Record screen</string> + <!-- Title of the activity that allows to select an app to screen record [CHAR LIMIT=70] --> + <string name="screenrecord_app_selector_title">Choose app to record</string> <!-- Label for a switch to enable recording audio [CHAR LIMIT=NONE]--> <string name="screenrecord_audio_label">Record audio</string> @@ -971,8 +977,8 @@ <string name="hearing_devices_presets_error">Couldn\'t update preset</string> <!-- QuickSettings: Title for hearing aids presets. Preset is a set of hearing aid settings. User can apply different settings in different environments (e.g. Outdoor, Restaurant, Home) [CHAR LIMIT=40]--> <string name="hearing_devices_preset_label">Preset</string> - <!-- QuickSettings: Tool name for hearing devices dialog related tools [CHAR LIMIT=40]--> - <string name="live_caption_title">Live Caption</string> + <!-- QuickSettings: Tool name for hearing devices dialog related tools [CHAR LIMIT=40] [BACKUP_MESSAGE_ID=8916875614623730005]--> + <string name="quick_settings_hearing_devices_live_caption_title">Live Caption</string> <!--- Title of dialog triggered if the microphone is disabled but an app tried to access it. [CHAR LIMIT=150] --> <string name="sensor_privacy_start_use_mic_dialog_title">Unblock device microphone?</string> @@ -1358,12 +1364,8 @@ <!-- Media projection permission dialog warning text for system services. [CHAR LIMIT=NONE] --> <string name="media_projection_sys_service_dialog_warning">The service providing this function will have access to all of the information that is visible on your screen or played from your device while recording or casting. This includes information such as passwords, payment details, photos, messages, and audio that you play.</string> - <!-- Permission dropdown option for sharing or recording the whole screen. [CHAR LIMIT=30] --> - <string name="screen_share_permission_dialog_option_entire_screen">Entire screen</string> - <!-- Permission dropdown option for sharing or recording single app. [CHAR LIMIT=30] --> - <string name="screen_share_permission_dialog_option_single_app">A single app</string> - <!-- Title of the dialog that allows to select an app to share or record [CHAR LIMIT=NONE] --> - <string name="screen_share_permission_app_selector_title">Share or record an app</string> + <!-- Title of the activity that allows users to select an app to share or record [CHAR LIMIT=NONE] --> + <string name="screen_share_generic_app_selector_title">Share or record an app</string> <!-- Media projection that launched from 1P/3P apps --> <!-- 1P/3P app media projection permission dialog title. [CHAR LIMIT=NONE] --> @@ -1381,6 +1383,8 @@ <string name="media_projection_entry_app_permission_dialog_continue_entire_screen">Share screen</string> <!-- 1P/3P apps disabled the single app projection option. [CHAR LIMIT=NONE] --> <string name="media_projection_entry_app_permission_dialog_single_app_disabled"><xliff:g id="app_name" example="Meet">%1$s</xliff:g> has disabled this option</string> + <!-- Title of the activity that allows users to select an app to share to a 1P/3P app [CHAR LIMIT=70] --> + <string name="media_projection_entry_share_app_selector_title">Choose app to share</string> <!-- Casting that launched by SysUI (i.e. when there is no app name) --> <!-- System casting media projection permission dialog title. [CHAR LIMIT=100] --> @@ -1395,6 +1399,8 @@ <string name="media_projection_entry_cast_permission_dialog_warning_single_app">When you’re casting an app, anything shown or played in that app is visible. So be careful with things like passwords, payment details, messages, photos, and audio and video.</string> <!-- System casting media projection permission button to continue for SysUI casting. [CHAR LIMIT=60] --> <string name="media_projection_entry_cast_permission_dialog_continue_entire_screen">Cast screen</string> + <!-- Title of the activity that allows users to select an app to cast [CHAR LIMIT=70] --> + <string name="media_projection_entry_cast_app_selector_title">Choose app to cast</string> <!-- Other sharing (not recording nor casting) that launched by SysUI (currently not in use) --> <!-- System sharing media projection permission dialog title. [CHAR LIMIT=100] --> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 7fa70881acd9..3ef624365594 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -1455,6 +1455,12 @@ <item name="android:textColor">?androidprv:attr/materialColorOnPrimaryContainer</item> </style> + <style name="BluetoothTileDialog.AudioSharingButton" parent="Widget.Dialog.Button"> + <item name="android:background">@drawable/audio_sharing_btn_background</item> + <item name="android:textColor">@color/audio_sharing_btn_text_color</item> + <item name="android:drawableTint">@color/audio_sharing_btn_text_color</item> + </style> + <style name="BroadcastDialog"> <item name="android:layout_width">wrap_content</item> <item name="android:layout_height">wrap_content</item> diff --git a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/Utils.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/Utils.kt index 12d881b20ca1..c0b6acfb2acd 100644 --- a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/Utils.kt +++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/Utils.kt @@ -16,6 +16,7 @@ package com.android.systemui.biometrics import android.Manifest +import android.app.ActivityTaskManager import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX @@ -35,6 +36,7 @@ import android.hardware.biometrics.PromptInfo import android.hardware.biometrics.SensorPropertiesInternal import android.os.UserManager import android.util.DisplayMetrics +import android.util.Log import android.view.ViewGroup import android.view.WindowInsets import android.view.WindowManager @@ -45,6 +47,8 @@ import com.android.internal.widget.LockPatternUtils import com.android.systemui.biometrics.shared.model.PromptKind object Utils { + private const val TAG = "SysUIBiometricUtils" + /** Base set of layout flags for fingerprint overlay widgets. */ const val FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS = (WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or @@ -148,4 +152,39 @@ object Utils { draw(canvas) return bitmap } + + // LINT.IfChange + @JvmStatic + /** + * Checks if a client package is running in the background or it's a system app. + * + * @param clientPackage The name of the package to be checked. + * @param clientClassNameIfItIsConfirmDeviceCredentialActivity The class name of + * ConfirmDeviceCredentialActivity. + * @return Whether the client package is running in background + */ + fun ActivityTaskManager.isSystemAppOrInBackground( + context: Context, + clientPackage: String, + clientClassNameIfItIsConfirmDeviceCredentialActivity: String? + ): Boolean { + Log.v(TAG, "Checking if the authenticating is in background, clientPackage:$clientPackage") + val tasks = getTasks(Int.MAX_VALUE) + if (tasks == null || tasks.isEmpty()) { + Log.w(TAG, "No running tasks reported") + return false + } + + val topActivity = tasks[0].topActivity + val isSystemApp = isSystem(context, clientPackage) + val topPackageEqualsToClient = topActivity!!.packageName == clientPackage + val isClientConfirmDeviceCredentialActivity = + clientClassNameIfItIsConfirmDeviceCredentialActivity != null + // b/339532378: If it's ConfirmDeviceCredentialActivity, we need to check further on + // class name. + return !(isSystemApp || topPackageEqualsToClient) || + (isClientConfirmDeviceCredentialActivity && + topActivity.className != clientClassNameIfItIsConfirmDeviceCredentialActivity) + } + // LINT.ThenChange(frameworks/base/services/core/java/com/android/server/biometrics/Utils.java) } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl index 5d804cc22b39..6209ed82a0ad 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl @@ -172,5 +172,10 @@ interface ISystemUiProxy { */ oneway void onImeSwitcherLongPress() = 57; - // Next id = 58 + /** + * Updates contextual education stats when target gesture type is triggered. + */ + oneway void updateContextualEduStats(boolean isTrackpadGesture, String gestureType) = 58; + + // Next id = 59 } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java index 68d2eb358105..41ad4373455e 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java @@ -26,6 +26,7 @@ import android.content.res.Resources; import android.graphics.Color; import android.graphics.Rect; import android.inputmethodservice.InputMethodService; +import android.inputmethodservice.InputMethodService.BackDispositionMode; import android.os.Build; import android.os.Handler; import android.os.Message; @@ -105,8 +106,8 @@ public class Utilities { * @return updated set of flags from InputMethodService based off {@param oldHints} * Leaves original hints unmodified */ - public static int calculateBackDispositionHints(int oldHints, int backDisposition, - boolean imeShown, boolean showImeSwitcher) { + public static int calculateBackDispositionHints(int oldHints, + @BackDispositionMode int backDisposition, boolean imeShown, boolean showImeSwitcher) { int hints = oldHints; switch (backDisposition) { case InputMethodService.BACK_DISPOSITION_DEFAULT: diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java index 317201d2c2d9..f358ba2d3ccd 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java @@ -125,6 +125,7 @@ public class FloatingRotationButton implements RotationButton { taskbarMarginLeft, taskbarMarginBottom, floatingRotationButtonPositionLeft); final int diameter = res.getDimensionPixelSize(mButtonDiameterResource); + mKeyButtonView.setDiameter(diameter); mContainerSize = diameter + Math.max(defaultMargin, Math.max(taskbarMarginLeft, taskbarMarginBottom)); } @@ -195,6 +196,7 @@ public class FloatingRotationButton implements RotationButton { public void updateIcon(int lightIconColor, int darkIconColor) { mAnimatedDrawable = (AnimatedVectorDrawable) mKeyButtonView.getContext() .getDrawable(mRotationButtonController.getIconResId()); + mAnimatedDrawable.setBounds(0, 0, mKeyButtonView.getWidth(), mKeyButtonView.getHeight()); mKeyButtonView.setImageDrawable(mAnimatedDrawable); mKeyButtonView.setColors(lightIconColor, darkIconColor); } @@ -248,8 +250,14 @@ public class FloatingRotationButton implements RotationButton { updateDimensionResources(); if (mIsShowing) { + updateIcon(mRotationButtonController.getLightIconColor(), + mRotationButtonController.getDarkIconColor()); final LayoutParams layoutParams = adjustViewPositionAndCreateLayoutParams(); mWindowManager.updateViewLayout(mKeyButtonContainer, layoutParams); + if (mAnimatedDrawable != null) { + mAnimatedDrawable.reset(); + mAnimatedDrawable.start(); + } } } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonView.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonView.java index 2145166e9bc5..75412f94ccb1 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonView.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonView.java @@ -37,6 +37,7 @@ public class FloatingRotationButtonView extends ImageView { private static final float BACKGROUND_ALPHA = 0.92f; private KeyButtonRipple mRipple; + private int mDiameter; private final Paint mOvalBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); private final Configuration mLastConfiguration; @@ -93,10 +94,25 @@ public class FloatingRotationButtonView extends ImageView { mRipple.setDarkIntensity(darkIntensity); } + /** + * Sets the view's diameter. + * + * @param diameter the diameter value for the view + */ + void setDiameter(int diameter) { + mDiameter = diameter; + } + @Override public void draw(Canvas canvas) { int d = Math.min(getWidth(), getHeight()); canvas.drawOval(0, 0, d, d, mOvalBgPaint); super.draw(canvas); } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + setMeasuredDimension(mDiameter, mDiameter); + } } diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java index 781f6dda18e8..831543da3237 100644 --- a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java +++ b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java @@ -74,7 +74,6 @@ public abstract class ClockRegistryModule { clockBuffers, /* keepAllLoaded = */ false, /* subTag = */ "System", - /* isTransitClockEnabled = */ featureFlags.isEnabled(Flags.TRANSIT_CLOCK), new ThreadAssert()); registry.registerListeners(); return registry; diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java index e055e7c9683b..9b5d5b6eadca 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -66,6 +66,7 @@ import android.widget.FrameLayout; import androidx.annotation.VisibleForTesting; +import com.android.app.viewcapture.ViewCaptureAwareWindowManager; import com.android.internal.util.Preconditions; import com.android.settingslib.Utils; import com.android.systemui.biometrics.data.repository.FacePropertyRepository; @@ -168,7 +169,7 @@ public class ScreenDecorations implements ViewGroup mScreenDecorHwcWindow; @VisibleForTesting ScreenDecorHwcLayer mScreenDecorHwcLayer; - private WindowManager mWindowManager; + private ViewCaptureAwareWindowManager mWindowManager; private int mRotation; private UserSettingObserver mColorInversionSetting; @Nullable @@ -338,7 +339,8 @@ public class ScreenDecorations implements ScreenDecorationsLogger logger, FacePropertyRepository facePropertyRepository, JavaAdapter javaAdapter, - CameraProtectionLoader cameraProtectionLoader) { + CameraProtectionLoader cameraProtectionLoader, + ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) { mContext = context; mSecureSettings = secureSettings; mCommandRegistry = commandRegistry; @@ -353,6 +355,7 @@ public class ScreenDecorations implements mLogger = logger; mFacePropertyRepository = facePropertyRepository; mJavaAdapter = javaAdapter; + mWindowManager = viewCaptureAwareWindowManager; } private final ScreenDecorCommand.Callback mScreenDecorCommandCallback = (cmd, pw) -> { @@ -484,7 +487,6 @@ public class ScreenDecorations implements private void startOnScreenDecorationsThread() { Trace.beginSection("ScreenDecorations#startOnScreenDecorationsThread"); - mWindowManager = mContext.getSystemService(WindowManager.class); mContext.getDisplay().getDisplayInfo(mDisplayInfo); mRotation = mDisplayInfo.rotation; mDisplaySize.x = mDisplayInfo.getNaturalWidth(); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java index 592414952ea8..f4a1f0546135 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java @@ -29,6 +29,7 @@ import android.view.accessibility.AccessibilityManager; import androidx.annotation.MainThread; +import com.android.app.viewcapture.ViewCaptureAwareWindowManager; import com.android.internal.annotations.VisibleForTesting; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; @@ -54,6 +55,7 @@ public class AccessibilityFloatingMenuController implements private Context mContext; private final WindowManager mWindowManager; + private final ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager; private final DisplayManager mDisplayManager; private final AccessibilityManager mAccessibilityManager; @@ -97,6 +99,7 @@ public class AccessibilityFloatingMenuController implements @Inject public AccessibilityFloatingMenuController(Context context, WindowManager windowManager, + ViewCaptureAwareWindowManager viewCaptureAwareWindowManager, DisplayManager displayManager, AccessibilityManager accessibilityManager, AccessibilityButtonTargetsObserver accessibilityButtonTargetsObserver, @@ -106,6 +109,7 @@ public class AccessibilityFloatingMenuController implements DisplayTracker displayTracker) { mContext = context; mWindowManager = windowManager; + mViewCaptureAwareWindowManager = viewCaptureAwareWindowManager; mDisplayManager = displayManager; mAccessibilityManager = accessibilityManager; mAccessibilityButtonTargetsObserver = accessibilityButtonTargetsObserver; @@ -187,7 +191,7 @@ public class AccessibilityFloatingMenuController implements final Context windowContext = mContext.createWindowContext(defaultDisplay, TYPE_NAVIGATION_BAR_PANEL, /* options= */ null); mFloatingMenu = new MenuViewLayerController(windowContext, mWindowManager, - mAccessibilityManager, mSecureSettings); + mViewCaptureAwareWindowManager, mAccessibilityManager, mSecureSettings); } mFloatingMenu.show(); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java index 6b1240b87b72..623536f0f928 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java @@ -23,6 +23,7 @@ import android.graphics.PixelFormat; import android.view.WindowManager; import android.view.accessibility.AccessibilityManager; +import com.android.app.viewcapture.ViewCaptureAwareWindowManager; import com.android.systemui.util.settings.SecureSettings; /** @@ -30,13 +31,14 @@ import com.android.systemui.util.settings.SecureSettings; * of {@link IAccessibilityFloatingMenu}. */ class MenuViewLayerController implements IAccessibilityFloatingMenu { - private final WindowManager mWindowManager; + private final ViewCaptureAwareWindowManager mWindowManager; private final MenuViewLayer mMenuViewLayer; private boolean mIsShowing; MenuViewLayerController(Context context, WindowManager windowManager, + ViewCaptureAwareWindowManager viewCaptureAwareWindowManager, AccessibilityManager accessibilityManager, SecureSettings secureSettings) { - mWindowManager = windowManager; + mWindowManager = viewCaptureAwareWindowManager; MenuViewModel menuViewModel = new MenuViewModel( context, accessibilityManager, secureSettings); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java index 083f1db07886..d08653c3cf1b 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java @@ -228,7 +228,7 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, mHearingDeviceItemList = getHearingDevicesList(); if (mPresetsController != null) { activeHearingDevice = getActiveHearingDevice(mHearingDeviceItemList); - mPresetsController.setActiveHearingDevice(activeHearingDevice); + mPresetsController.setHearingDeviceIfSupportHap(activeHearingDevice); } else { activeHearingDevice = null; } @@ -336,7 +336,7 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, } final CachedBluetoothDevice activeHearingDevice = getActiveHearingDevice( mHearingDeviceItemList); - mPresetsController.setActiveHearingDevice(activeHearingDevice); + mPresetsController.setHearingDeviceIfSupportHap(activeHearingDevice); mPresetInfoAdapter = new ArrayAdapter<>(dialog.getContext(), R.layout.hearing_devices_preset_spinner_selected, @@ -499,7 +499,8 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, final List<ResolveInfo> resolved = packageManager.queryIntentActivities(LIVE_CAPTION_INTENT, /* flags= */ 0); if (!resolved.isEmpty()) { - return new ToolItem(context.getString(R.string.live_caption_title), + return new ToolItem( + context.getString(R.string.quick_settings_hearing_devices_live_caption_title), context.getDrawable(R.drawable.ic_volume_odi_captions), LIVE_CAPTION_INTENT); } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsController.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsController.java index f81124eeeb7f..aa95fd038260 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsController.java @@ -113,7 +113,7 @@ public class HearingDevicesPresetsController implements @Override public void onPresetSelectionForGroupFailed(int hapGroupId, int reason) { - if (mActiveHearingDevice == null) { + if (mActiveHearingDevice == null || mHapClientProfile == null) { return; } if (hapGroupId == mHapClientProfile.getHapGroup(mActiveHearingDevice.getDevice())) { @@ -137,7 +137,7 @@ public class HearingDevicesPresetsController implements @Override public void onSetPresetNameForGroupFailed(int hapGroupId, int reason) { - if (mActiveHearingDevice == null) { + if (mActiveHearingDevice == null || mHapClientProfile == null) { return; } if (hapGroupId == mHapClientProfile.getHapGroup(mActiveHearingDevice.getDevice())) { @@ -177,22 +177,33 @@ public class HearingDevicesPresetsController implements } /** - * Sets the hearing device for this controller to control the preset. + * Sets the hearing device for this controller to control the preset if it supports + * {@link HapClientProfile}. * * @param activeHearingDevice the {@link CachedBluetoothDevice} need to be hearing aid device + * and support {@link HapClientProfile}. */ - public void setActiveHearingDevice(CachedBluetoothDevice activeHearingDevice) { - mActiveHearingDevice = activeHearingDevice; + public void setHearingDeviceIfSupportHap(CachedBluetoothDevice activeHearingDevice) { + if (mHapClientProfile == null || activeHearingDevice == null) { + mActiveHearingDevice = null; + return; + } + if (activeHearingDevice.getProfiles().stream().anyMatch( + profile -> profile instanceof HapClientProfile)) { + mActiveHearingDevice = activeHearingDevice; + } else { + mActiveHearingDevice = null; + } } /** * Selects the currently active preset for {@code mActiveHearingDevice} individual device or - * the device group accoridng to whether it supports synchronized presets or not. + * the device group according to whether it supports synchronized presets or not. * * @param presetIndex an index of one of the available presets */ public void selectPreset(int presetIndex) { - if (mActiveHearingDevice == null) { + if (mActiveHearingDevice == null || mHapClientProfile == null) { return; } mSelectedPresetIndex = presetIndex; @@ -217,7 +228,7 @@ public class HearingDevicesPresetsController implements * @return a list of all known preset info */ public List<BluetoothHapPresetInfo> getAllPresetInfo() { - if (mActiveHearingDevice == null) { + if (mActiveHearingDevice == null || mHapClientProfile == null) { return emptyList(); } return mHapClientProfile.getAllPresetInfo(mActiveHearingDevice.getDevice()).stream().filter( @@ -230,14 +241,14 @@ public class HearingDevicesPresetsController implements * @return active preset index */ public int getActivePresetIndex() { - if (mActiveHearingDevice == null) { + if (mActiveHearingDevice == null || mHapClientProfile == null) { return BluetoothHapClient.PRESET_INDEX_UNAVAILABLE; } return mHapClientProfile.getActivePresetIndex(mActiveHearingDevice.getDevice()); } private void selectPresetSynchronously(int groupId, int presetIndex) { - if (mActiveHearingDevice == null) { + if (mActiveHearingDevice == null || mHapClientProfile == null) { return; } if (DEBUG) { @@ -250,7 +261,7 @@ public class HearingDevicesPresetsController implements } private void selectPresetIndependently(int presetIndex) { - if (mActiveHearingDevice == null) { + if (mActiveHearingDevice == null || mHapClientProfile == null) { return; } if (DEBUG) { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java index 9521be1f11a7..723587e60df1 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java @@ -17,11 +17,9 @@ package com.android.systemui.biometrics; import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE; -import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_POWER_BUTTON; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; import static com.android.internal.jank.InteractionJankMonitor.CUJ_BIOMETRIC_PROMPT_TRANSITION; -import static com.android.systemui.Flags.constraintBp; import android.animation.Animator; import android.annotation.IntDef; @@ -30,8 +28,6 @@ import android.annotation.Nullable; import android.app.AlertDialog; import android.content.Context; import android.content.res.Configuration; -import android.content.res.TypedArray; -import android.graphics.Color; import android.graphics.PixelFormat; import android.hardware.biometrics.BiometricAuthenticator.Modality; import android.hardware.biometrics.BiometricConstants; @@ -41,17 +37,11 @@ import android.hardware.biometrics.PromptInfo; import android.hardware.face.FaceSensorPropertiesInternal; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.os.Binder; -import android.os.Handler; import android.os.IBinder; -import android.os.Looper; import android.os.UserManager; import android.util.Log; -import android.view.Display; -import android.view.DisplayInfo; -import android.view.Gravity; import android.view.KeyEvent; import android.view.LayoutInflater; -import android.view.Surface; import android.view.View; import android.view.ViewGroup; import android.view.WindowInsets; @@ -60,7 +50,6 @@ import android.view.animation.Interpolator; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; -import android.widget.ScrollView; import android.window.OnBackInvokedCallback; import android.window.OnBackInvokedDispatcher; @@ -74,7 +63,6 @@ import com.android.systemui.biometrics.AuthController.ScaleFactorProvider; import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor; import com.android.systemui.biometrics.shared.model.BiometricModalities; import com.android.systemui.biometrics.shared.model.PromptKind; -import com.android.systemui.biometrics.ui.BiometricPromptLayout; import com.android.systemui.biometrics.ui.CredentialView; import com.android.systemui.biometrics.ui.binder.BiometricViewBinder; import com.android.systemui.biometrics.ui.binder.BiometricViewSizeBinder; @@ -111,7 +99,6 @@ public class AuthContainerView extends LinearLayout private static final int ANIMATION_DURATION_SHOW_MS = 250; private static final int ANIMATION_DURATION_AWAY_MS = 350; - private static final int ANIMATE_CREDENTIAL_START_DELAY_MS = 300; private static final int STATE_UNKNOWN = 0; private static final int STATE_ANIMATING_IN = 1; @@ -136,13 +123,11 @@ public class AuthContainerView extends LinearLayout private final Config mConfig; private final int mEffectiveUserId; - private final Handler mHandler; private final IBinder mWindowToken = new Binder(); private final WindowManager mWindowManager; private final Interpolator mLinearOutSlowIn; private final LockPatternUtils mLockPatternUtils; private final WakefulnessLifecycle mWakefulnessLifecycle; - private final AuthDialogPanelInteractionDetector mPanelInteractionDetector; private final InteractionJankMonitor mInteractionJankMonitor; private final CoroutineScope mApplicationCoroutineScope; @@ -159,10 +144,7 @@ public class AuthContainerView extends LinearLayout private final AuthPanelController mPanelController; private final ViewGroup mLayout; private final ImageView mBackgroundView; - private final ScrollView mBiometricScrollView; private final View mPanelView; - private final List<FingerprintSensorPropertiesInternal> mFpProps; - private final List<FaceSensorPropertiesInternal> mFaceProps; private final float mTranslationY; @VisibleForTesting @ContainerState int mContainerState = STATE_UNKNOWN; private final Set<Integer> mFailedModalities = new HashSet<Integer>(); @@ -229,13 +211,7 @@ public class AuthContainerView extends LinearLayout @Override public void onUseDeviceCredential() { mConfig.mCallback.onDeviceCredentialPressed(getRequestId()); - if (constraintBp()) { - addCredentialView(false /* animatePanel */, true /* animateContents */); - } else { - mHandler.postDelayed(() -> { - addCredentialView(false /* animatePanel */, true /* animateContents */); - }, mConfig.mSkipAnimation ? 0 : ANIMATE_CREDENTIAL_START_DELAY_MS); - } + addCredentialView(false /* animatePanel */, true /* animateContents */); // TODO(b/313469218): Remove Config mConfig.mPromptInfo.setAuthenticators(Authenticators.DEVICE_CREDENTIAL); @@ -303,36 +279,12 @@ public class AuthContainerView extends LinearLayout @Nullable List<FingerprintSensorPropertiesInternal> fpProps, @Nullable List<FaceSensorPropertiesInternal> faceProps, @NonNull WakefulnessLifecycle wakefulnessLifecycle, - @NonNull AuthDialogPanelInteractionDetector panelInteractionDetector, - @NonNull UserManager userManager, - @NonNull LockPatternUtils lockPatternUtils, - @NonNull InteractionJankMonitor jankMonitor, - @NonNull Provider<PromptSelectorInteractor> promptSelectorInteractor, - @NonNull PromptViewModel promptViewModel, - @NonNull Provider<CredentialViewModel> credentialViewModelProvider, - @NonNull @Background DelayableExecutor bgExecutor, - @NonNull VibratorHelper vibratorHelper) { - this(config, applicationCoroutineScope, fpProps, faceProps, - wakefulnessLifecycle, panelInteractionDetector, userManager, lockPatternUtils, - jankMonitor, promptSelectorInteractor, promptViewModel, - credentialViewModelProvider, new Handler(Looper.getMainLooper()), bgExecutor, - vibratorHelper); - } - - @VisibleForTesting - AuthContainerView(@NonNull Config config, - @NonNull CoroutineScope applicationCoroutineScope, - @Nullable List<FingerprintSensorPropertiesInternal> fpProps, - @Nullable List<FaceSensorPropertiesInternal> faceProps, - @NonNull WakefulnessLifecycle wakefulnessLifecycle, - @NonNull AuthDialogPanelInteractionDetector panelInteractionDetector, @NonNull UserManager userManager, @NonNull LockPatternUtils lockPatternUtils, @NonNull InteractionJankMonitor jankMonitor, @NonNull Provider<PromptSelectorInteractor> promptSelectorInteractorProvider, @NonNull PromptViewModel promptViewModel, @NonNull Provider<CredentialViewModel> credentialViewModelProvider, - @NonNull Handler mainHandler, @NonNull @Background DelayableExecutor bgExecutor, @NonNull VibratorHelper vibratorHelper) { super(config.mContext); @@ -340,10 +292,8 @@ public class AuthContainerView extends LinearLayout mConfig = config; mLockPatternUtils = lockPatternUtils; mEffectiveUserId = userManager.getCredentialOwnerProfile(mConfig.mUserId); - mHandler = mainHandler; mWindowManager = mContext.getSystemService(WindowManager.class); mWakefulnessLifecycle = wakefulnessLifecycle; - mPanelInteractionDetector = panelInteractionDetector; mApplicationCoroutineScope = applicationCoroutineScope; mPromptViewModel = promptViewModel; @@ -352,8 +302,6 @@ public class AuthContainerView extends LinearLayout mLinearOutSlowIn = Interpolators.LINEAR_OUT_SLOW_IN; mBiometricCallback = new BiometricCallback(); - mFpProps = fpProps; - mFaceProps = faceProps; final BiometricModalities biometricModalities = new BiometricModalities( Utils.findFirstSensorProperties(fpProps, mConfig.mSensorIds), Utils.findFirstSensorProperties(faceProps, mConfig.mSensorIds)); @@ -367,7 +315,7 @@ public class AuthContainerView extends LinearLayout final LayoutInflater layoutInflater = LayoutInflater.from(mContext); final PromptKind kind = mPromptViewModel.getPromptKind().getValue(); - if (constraintBp() && kind.isBiometric()) { + if (kind.isBiometric()) { if (kind.isTwoPaneLandscapeBiometric()) { mLayout = (ConstraintLayout) layoutInflater.inflate( R.layout.biometric_prompt_two_pane_layout, this, false /* attachToRoot */); @@ -379,26 +327,16 @@ public class AuthContainerView extends LinearLayout mLayout = (FrameLayout) layoutInflater.inflate( R.layout.auth_container_view, this, false /* attachToRoot */); } - mBiometricScrollView = mLayout.findViewById(R.id.biometric_scrollview); addView(mLayout); mBackgroundView = mLayout.findViewById(R.id.background); mPanelView = mLayout.findViewById(R.id.panel); - if (!constraintBp()) { - final TypedArray ta = mContext.obtainStyledAttributes(new int[]{ - android.R.attr.colorBackgroundFloating}); - mPanelView.setBackgroundColor(ta.getColor(0, Color.WHITE)); - ta.recycle(); - } mPanelController = new AuthPanelController(mContext, mPanelView); mBackgroundExecutor = bgExecutor; mInteractionJankMonitor = jankMonitor; mCredentialViewModelProvider = credentialViewModelProvider; - showPrompt(config, layoutInflater, promptViewModel, - Utils.findFirstSensorProperties(fpProps, mConfig.mSensorIds), - Utils.findFirstSensorProperties(faceProps, mConfig.mSensorIds), - vibratorHelper); + showPrompt(promptViewModel, vibratorHelper); // TODO: De-dupe the logic with AuthCredentialPasswordView setOnKeyListener((v, keyCode, event) -> { @@ -415,52 +353,25 @@ public class AuthContainerView extends LinearLayout requestFocus(); } - private void showPrompt(@NonNull Config config, @NonNull LayoutInflater layoutInflater, - @NonNull PromptViewModel viewModel, - @Nullable FingerprintSensorPropertiesInternal fpProps, - @Nullable FaceSensorPropertiesInternal faceProps, - @NonNull VibratorHelper vibratorHelper - ) { + private void showPrompt(@NonNull PromptViewModel viewModel, + @NonNull VibratorHelper vibratorHelper) { if (mPromptViewModel.getPromptKind().getValue().isBiometric()) { - addBiometricView(config, layoutInflater, viewModel, fpProps, faceProps, vibratorHelper); + addBiometricView(viewModel, vibratorHelper); } else if (mPromptViewModel.getPromptKind().getValue().isCredential()) { - if (constraintBp()) { - addCredentialView(true, false); - } + addCredentialView(true, false); } else { mPromptSelectorInteractorProvider.get().resetPrompt(getRequestId()); } } - private void addBiometricView(@NonNull Config config, @NonNull LayoutInflater layoutInflater, - @NonNull PromptViewModel viewModel, - @Nullable FingerprintSensorPropertiesInternal fpProps, - @Nullable FaceSensorPropertiesInternal faceProps, + private void addBiometricView(@NonNull PromptViewModel viewModel, @NonNull VibratorHelper vibratorHelper) { - - if (constraintBp()) { - mBiometricView = BiometricViewBinder.bind(mLayout, viewModel, null, - // TODO(b/201510778): This uses the wrong timeout in some cases - getJankListener(mLayout, TRANSIT, - BiometricViewSizeBinder.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS), - mBackgroundView, mBiometricCallback, mApplicationCoroutineScope, - vibratorHelper); - } else { - final BiometricPromptLayout view = (BiometricPromptLayout) layoutInflater.inflate( - R.layout.biometric_prompt_layout, null, false); - mBiometricView = BiometricViewBinder.bind(view, viewModel, mPanelController, - // TODO(b/201510778): This uses the wrong timeout in some cases - getJankListener(view, TRANSIT, - BiometricViewSizeBinder.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS), - mBackgroundView, mBiometricCallback, mApplicationCoroutineScope, - vibratorHelper); - - // TODO(b/251476085): migrate these dependencies - if (fpProps != null && fpProps.isAnyUdfpsType()) { - view.setUdfpsAdapter(new UdfpsDialogMeasureAdapter(view, fpProps), - config.mScaleProvider); - } - } + mBiometricView = BiometricViewBinder.bind(mLayout, viewModel, + // TODO(b/201510778): This uses the wrong timeout in some cases + getJankListener(mLayout, TRANSIT, + BiometricViewSizeBinder.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS), + mBackgroundView, mBiometricCallback, mApplicationCoroutineScope, + vibratorHelper); } @VisibleForTesting @@ -524,9 +435,6 @@ public class AuthContainerView extends LinearLayout @Override public void onOrientationChanged() { - if (!constraintBp()) { - updatePositionByCapability(true /* invalidate */); - } } @Override @@ -538,23 +446,6 @@ public class AuthContainerView extends LinearLayout } mWakefulnessLifecycle.addObserver(this); - if (constraintBp()) { - // Do nothing on attachment with constraintLayout - } else if (mPromptViewModel.getPromptKind().getValue().isBiometric()) { - mBiometricScrollView.addView(mBiometricView.asView()); - } else if (mPromptViewModel.getPromptKind().getValue().isCredential()) { - addCredentialView(true /* animatePanel */, false /* animateContents */); - } else { - throw new IllegalStateException("Unknown configuration: " - + mConfig.mPromptInfo.getAuthenticators()); - } - - if (!constraintBp()) { - mPanelInteractionDetector.enable( - () -> animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED)); - updatePositionByCapability(false /* invalidate */); - } - if (mConfig.mSkipIntro) { mContainerState = STATE_SHOWING; } else { @@ -618,120 +509,8 @@ public class AuthContainerView extends LinearLayout }; } - private void updatePositionByCapability(boolean forceInvalidate) { - final FingerprintSensorPropertiesInternal fpProp = Utils.findFirstSensorProperties( - mFpProps, mConfig.mSensorIds); - final FaceSensorPropertiesInternal faceProp = Utils.findFirstSensorProperties( - mFaceProps, mConfig.mSensorIds); - if (fpProp != null && fpProp.isAnyUdfpsType()) { - maybeUpdatePositionForUdfps(forceInvalidate /* invalidate */); - } - if (faceProp != null && mBiometricView != null && mBiometricView.isFaceOnly()) { - alwaysUpdatePositionAtScreenBottom(forceInvalidate /* invalidate */); - } - if (fpProp != null && fpProp.sensorType == TYPE_POWER_BUTTON) { - alwaysUpdatePositionAtScreenBottom(forceInvalidate /* invalidate */); - } - } - - private static boolean shouldUpdatePositionForUdfps(@NonNull View view) { - if (view instanceof BiometricPromptLayout) { - // this will force the prompt to align itself on the edge of the screen - // instead of centering (temporary workaround to prevent small implicit view - // from breaking due to the way gravity / margins are set in the legacy - // AuthPanelController - return true; - } - - return false; - } - - private boolean maybeUpdatePositionForUdfps(boolean invalidate) { - final Display display = getDisplay(); - if (display == null) { - return false; - } - - final DisplayInfo cachedDisplayInfo = new DisplayInfo(); - display.getDisplayInfo(cachedDisplayInfo); - if (mBiometricView == null || !shouldUpdatePositionForUdfps(mBiometricView.asView())) { - return false; - } - - final int displayRotation = cachedDisplayInfo.rotation; - switch (displayRotation) { - case Surface.ROTATION_0: - mPanelController.setPosition(AuthPanelController.POSITION_BOTTOM); - setScrollViewGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM); - break; - - case Surface.ROTATION_90: - mPanelController.setPosition(AuthPanelController.POSITION_RIGHT); - setScrollViewGravity(Gravity.CENTER_VERTICAL | Gravity.RIGHT); - break; - - case Surface.ROTATION_270: - mPanelController.setPosition(AuthPanelController.POSITION_LEFT); - setScrollViewGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT); - break; - - case Surface.ROTATION_180: - default: - Log.e(TAG, "Unsupported display rotation: " + displayRotation); - mPanelController.setPosition(AuthPanelController.POSITION_BOTTOM); - setScrollViewGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM); - break; - } - - if (invalidate) { - mPanelView.invalidateOutline(); - } - - return true; - } - - private boolean alwaysUpdatePositionAtScreenBottom(boolean invalidate) { - final Display display = getDisplay(); - if (display == null) { - return false; - } - if (mBiometricView == null || !shouldUpdatePositionForUdfps(mBiometricView.asView())) { - return false; - } - - final int displayRotation = display.getRotation(); - switch (displayRotation) { - case Surface.ROTATION_0: - case Surface.ROTATION_90: - case Surface.ROTATION_270: - case Surface.ROTATION_180: - mPanelController.setPosition(AuthPanelController.POSITION_BOTTOM); - setScrollViewGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM); - break; - default: - Log.e(TAG, "Unsupported display rotation: " + displayRotation); - mPanelController.setPosition(AuthPanelController.POSITION_BOTTOM); - setScrollViewGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM); - break; - } - - if (invalidate) { - mPanelView.invalidateOutline(); - } - - return true; - } - - private void setScrollViewGravity(int gravity) { - final FrameLayout.LayoutParams params = - (FrameLayout.LayoutParams) mBiometricScrollView.getLayoutParams(); - params.gravity = gravity; - mBiometricScrollView.setLayoutParams(params); - } - @Override public void onDetachedFromWindow() { - mPanelInteractionDetector.disable(); OnBackInvokedDispatcher dispatcher = findOnBackInvokedDispatcher(); if (dispatcher != null) { findOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(mBackCallback); @@ -834,6 +613,11 @@ public class AuthContainerView extends LinearLayout } @Override + public String getClassNameIfItIsConfirmDeviceCredentialActivity() { + return mConfig.mPromptInfo.getClassNameIfItIsConfirmDeviceCredentialActivity(); + } + + @Override public long getRequestId() { return mConfig.mRequestId; } @@ -878,7 +662,7 @@ public class AuthContainerView extends LinearLayout final Runnable endActionRunnable = () -> { setVisibility(View.INVISIBLE); - if (Flags.customBiometricPrompt() && constraintBp()) { + if (Flags.customBiometricPrompt()) { // TODO(b/288175645): resetPrompt calls should be lifecycle aware mPromptSelectorInteractorProvider.get().resetPrompt(getRequestId()); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index b466f31cc509..037f5b72aff1 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -22,7 +22,6 @@ import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_REAR import android.annotation.NonNull; import android.annotation.Nullable; -import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.app.TaskStackListener; import android.content.BroadcastReceiver; @@ -173,7 +172,6 @@ public class AuthController implements @NonNull private final SparseBooleanArray mSfpsEnrolledForUser; @NonNull private final SensorPrivacyManager mSensorPrivacyManager; private final WakefulnessLifecycle mWakefulnessLifecycle; - private final AuthDialogPanelInteractionDetector mPanelInteractionDetector; private boolean mAllFingerprintAuthenticatorsRegistered; @NonNull private final UserManager mUserManager; @NonNull private final LockPatternUtils mLockPatternUtils; @@ -187,7 +185,7 @@ public class AuthController implements final TaskStackListener mTaskStackListener = new TaskStackListener() { @Override public void onTaskStackChanged() { - if (!isOwnerInForeground()) { + if (isOwnerInBackground()) { mHandler.post(AuthController.this::cancelIfOwnerIsNotInForeground); } } @@ -227,21 +225,20 @@ public class AuthController implements } } - private boolean isOwnerInForeground() { + private boolean isOwnerInBackground() { if (mCurrentDialog != null) { final String clientPackage = mCurrentDialog.getOpPackageName(); - final List<ActivityManager.RunningTaskInfo> runningTasks = - mActivityTaskManager.getTasks(1); - if (!runningTasks.isEmpty()) { - final String topPackage = runningTasks.get(0).topActivity.getPackageName(); - if (!topPackage.contentEquals(clientPackage) - && !Utils.isSystem(mContext, clientPackage)) { - Log.w(TAG, "Evicting client due to: " + topPackage); - return false; - } + final String clientClassNameIfItIsConfirmDeviceCredentialActivity = + mCurrentDialog.getClassNameIfItIsConfirmDeviceCredentialActivity(); + final boolean isInBackground = Utils.isSystemAppOrInBackground(mActivityTaskManager, + mContext, clientPackage, + clientClassNameIfItIsConfirmDeviceCredentialActivity); + if (isInBackground) { + Log.w(TAG, "Evicting client due to top activity is not : " + clientPackage); } + return isInBackground; } - return true; + return false; } private void cancelIfOwnerIsNotInForeground() { @@ -728,7 +725,6 @@ public class AuthController implements Provider<UdfpsController> udfpsControllerFactory, @NonNull DisplayManager displayManager, @NonNull WakefulnessLifecycle wakefulnessLifecycle, - @NonNull AuthDialogPanelInteractionDetector panelInteractionDetector, @NonNull UserManager userManager, @NonNull LockPatternUtils lockPatternUtils, @NonNull Lazy<UdfpsLogger> udfpsLogger, @@ -779,7 +775,6 @@ public class AuthController implements }); mWakefulnessLifecycle = wakefulnessLifecycle; - mPanelInteractionDetector = panelInteractionDetector; mFaceProps = mFaceManager != null ? mFaceManager.getSensorPropertiesInternal() : null; @@ -1229,7 +1224,6 @@ public class AuthController implements operationId, requestId, mWakefulnessLifecycle, - mPanelInteractionDetector, mUserManager, mLockPatternUtils, viewModel); @@ -1259,9 +1253,9 @@ public class AuthController implements } mCurrentDialog = newDialog; - // TODO(b/339532378): We should check whether |allowBackgroundAuthentication| should be + // TODO(b/353597496): We should check whether |allowBackgroundAuthentication| should be // removed. - if (!promptInfo.isAllowBackgroundAuthentication() && !isOwnerInForeground()) { + if (!promptInfo.isAllowBackgroundAuthentication() && isOwnerInBackground()) { cancelIfOwnerIsNotInForeground(); } else { mCurrentDialog.show(mWindowManager); @@ -1306,7 +1300,6 @@ public class AuthController implements PromptInfo promptInfo, boolean requireConfirmation, int userId, int[] sensorIds, String opPackageName, boolean skipIntro, long operationId, long requestId, @NonNull WakefulnessLifecycle wakefulnessLifecycle, - @NonNull AuthDialogPanelInteractionDetector panelInteractionDetector, @NonNull UserManager userManager, @NonNull LockPatternUtils lockPatternUtils, @NonNull PromptViewModel viewModel) { @@ -1323,7 +1316,7 @@ public class AuthController implements config.mSensorIds = sensorIds; config.mScaleProvider = this::getScaleFactor; return new AuthContainerView(config, mApplicationCoroutineScope, mFpProps, mFaceProps, - wakefulnessLifecycle, panelInteractionDetector, userManager, lockPatternUtils, + wakefulnessLifecycle, userManager, lockPatternUtils, mInteractionJankMonitor, mPromptSelectorInteractor, viewModel, mCredentialViewModelProvider, bgExecutor, mVibratorHelper); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java index 3fd488c34121..861191671ba9 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java @@ -94,6 +94,12 @@ public interface AuthDialog extends Dumpable { */ String getOpPackageName(); + /** + * Get the class name of ConfirmDeviceCredentialActivity. Returns null if the direct caller is + * not ConfirmDeviceCredentialActivity. + */ + String getClassNameIfItIsConfirmDeviceCredentialActivity(); + /** The requestId of the underlying operation within the framework. */ long getRequestId(); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt deleted file mode 100644 index 04c2351c1a3e..000000000000 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.biometrics - -import android.annotation.MainThread -import android.util.Log -import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.shade.domain.interactor.ShadeInteractor -import dagger.Lazy -import javax.inject.Inject -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.launch - -class AuthDialogPanelInteractionDetector -@Inject -constructor( - @Application private val scope: CoroutineScope, - private val shadeInteractorLazy: Lazy<ShadeInteractor>, -) { - private var shadeExpansionCollectorJob: Job? = null - - @MainThread - fun enable(onShadeInteraction: Runnable) { - if (shadeExpansionCollectorJob != null) { - Log.e(TAG, "Already enabled") - return - } - //TODO(b/313957306) delete this check - if (shadeInteractorLazy.get().isUserInteracting.value) { - // Workaround for b/311266890. This flow is in an error state that breaks this. - Log.e(TAG, "isUserInteracting already true, skipping enable") - return - } - shadeExpansionCollectorJob = - scope.launch { - Log.i(TAG, "Enable detector") - // wait for it to emit true once - shadeInteractorLazy.get().isUserInteracting.first { it } - Log.i(TAG, "Detector detected shade interaction") - onShadeInteraction.run() - } - shadeExpansionCollectorJob?.invokeOnCompletion { shadeExpansionCollectorJob = null } - } - - @MainThread - fun disable() { - Log.i(TAG, "Disable detector") - shadeExpansionCollectorJob?.cancel() - } -} - -private const val TAG = "AuthDialogPanelInteractionDetector" diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java deleted file mode 100644 index 02eae9cedf74..000000000000 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java +++ /dev/null @@ -1,386 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.biometrics; - -import android.annotation.IdRes; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.graphics.Insets; -import android.graphics.Rect; -import android.hardware.biometrics.SensorLocationInternal; -import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; -import android.os.Build; -import android.util.Log; -import android.view.Surface; -import android.view.View; -import android.view.View.MeasureSpec; -import android.view.ViewGroup; -import android.view.WindowInsets; -import android.view.WindowManager; -import android.view.WindowMetrics; -import android.widget.FrameLayout; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.systemui.res.R; - -/** - * Adapter that remeasures an auth dialog view to ensure that it matches the location of a physical - * under-display fingerprint sensor (UDFPS). - */ -public class UdfpsDialogMeasureAdapter { - private static final String TAG = "UdfpsDialogMeasurementAdapter"; - private static final boolean DEBUG = Build.IS_USERDEBUG || Build.IS_ENG; - - @NonNull private final ViewGroup mView; - @NonNull private final FingerprintSensorPropertiesInternal mSensorProps; - @Nullable private WindowManager mWindowManager; - private int mBottomSpacerHeight; - - public UdfpsDialogMeasureAdapter( - @NonNull ViewGroup view, @NonNull FingerprintSensorPropertiesInternal sensorProps) { - mView = view; - mSensorProps = sensorProps; - mWindowManager = mView.getContext().getSystemService(WindowManager.class); - } - - @NonNull - FingerprintSensorPropertiesInternal getSensorProps() { - return mSensorProps; - } - - @NonNull - public AuthDialog.LayoutParams onMeasureInternal( - int width, int height, @NonNull AuthDialog.LayoutParams layoutParams, - float scaleFactor) { - - final int displayRotation = mView.getDisplay().getRotation(); - switch (displayRotation) { - case Surface.ROTATION_0: - return onMeasureInternalPortrait(width, height, scaleFactor); - case Surface.ROTATION_90: - case Surface.ROTATION_270: - return onMeasureInternalLandscape(width, height, scaleFactor); - default: - Log.e(TAG, "Unsupported display rotation: " + displayRotation); - return layoutParams; - } - } - - /** - * @return the actual (and possibly negative) bottom spacer height. If negative, this indicates - * that the UDFPS sensor is too low. Our current xml and custom measurement logic is very hard - * too cleanly support this case. So, let's have the onLayout code translate the sensor location - * instead. - */ - public int getBottomSpacerHeight() { - return mBottomSpacerHeight; - } - - /** - * @return sensor diameter size as scaleFactor - */ - public int getSensorDiameter(float scaleFactor) { - return (int) (scaleFactor * mSensorProps.getLocation().sensorRadius * 2); - } - - @NonNull - private AuthDialog.LayoutParams onMeasureInternalPortrait(int width, int height, - float scaleFactor) { - final WindowMetrics windowMetrics = mWindowManager.getMaximumWindowMetrics(); - - // Figure out where the bottom of the sensor anim should be. - final int textIndicatorHeight = getViewHeightPx(R.id.indicator); - final int buttonBarHeight = getViewHeightPx(R.id.button_bar); - final int dialogMargin = getDialogMarginPx(); - final int displayHeight = getMaximumWindowBounds(windowMetrics).height(); - final Insets navbarInsets = getNavbarInsets(windowMetrics); - mBottomSpacerHeight = calculateBottomSpacerHeightForPortrait( - mSensorProps, displayHeight, textIndicatorHeight, buttonBarHeight, - dialogMargin, navbarInsets.bottom, scaleFactor); - - // Go through each of the children and do the custom measurement. - int totalHeight = 0; - final int numChildren = mView.getChildCount(); - final int sensorDiameter = getSensorDiameter(scaleFactor); - for (int i = 0; i < numChildren; i++) { - final View child = mView.getChildAt(i); - if (child.getId() == R.id.biometric_icon_frame) { - final FrameLayout iconFrame = (FrameLayout) child; - final View icon = iconFrame.getChildAt(0); - // Create a frame that's exactly the height of the sensor circle. - iconFrame.measure( - MeasureSpec.makeMeasureSpec( - child.getLayoutParams().width, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.EXACTLY)); - - // Ensure that the icon is never larger than the sensor. - icon.measure( - MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.AT_MOST), - MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.AT_MOST)); - } else if (child.getId() == R.id.space_above_icon - || child.getId() == R.id.space_above_content - || child.getId() == R.id.button_bar) { - child.measure( - MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec( - child.getLayoutParams().height, MeasureSpec.EXACTLY)); - } else if (child.getId() == R.id.space_below_icon) { - // Set the spacer height so the fingerprint icon is on the physical sensor area - final int clampedSpacerHeight = Math.max(mBottomSpacerHeight, 0); - child.measure( - MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(clampedSpacerHeight, MeasureSpec.EXACTLY)); - } else if (child.getId() == R.id.description - || child.getId() == R.id.customized_view_container) { - //skip description view and compute later - continue; - } else if (child.getId() == R.id.logo) { - child.measure( - MeasureSpec.makeMeasureSpec(child.getLayoutParams().width, - MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(child.getLayoutParams().height, - MeasureSpec.EXACTLY)); - } else { - child.measure( - MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST)); - } - - if (child.getVisibility() != View.GONE) { - totalHeight += child.getMeasuredHeight(); - } - } - - //re-calculate the height of body content - View description = mView.findViewById(R.id.description); - View contentView = mView.findViewById(R.id.customized_view_container); - if (description != null && description.getVisibility() != View.GONE) { - totalHeight += measureDescription(description, displayHeight, width, totalHeight); - } else if (contentView != null && contentView.getVisibility() != View.GONE) { - totalHeight += measureDescription(contentView, displayHeight, width, totalHeight); - } - - return new AuthDialog.LayoutParams(width, totalHeight); - } - - private int measureDescription(View bodyContent, int displayHeight, int currWidth, - int currHeight) { - int newHeight = bodyContent.getMeasuredHeight() + currHeight; - int limit = (int) (displayHeight * 0.75); - if (newHeight > limit) { - bodyContent.measure( - MeasureSpec.makeMeasureSpec(currWidth, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(limit - currHeight, MeasureSpec.EXACTLY)); - } - return bodyContent.getMeasuredHeight(); - } - - @NonNull - private AuthDialog.LayoutParams onMeasureInternalLandscape(int width, int height, - float scaleFactor) { - final WindowMetrics windowMetrics = mWindowManager.getMaximumWindowMetrics(); - - // Find the spacer height needed to vertically align the icon with the sensor. - final int titleHeight = getViewHeightPx(R.id.title); - final int subtitleHeight = getViewHeightPx(R.id.subtitle); - final int descriptionHeight = getViewHeightPx(R.id.description); - final int topSpacerHeight = getViewHeightPx(R.id.space_above_icon); - final int textIndicatorHeight = getViewHeightPx(R.id.indicator); - final int buttonBarHeight = getViewHeightPx(R.id.button_bar); - - final Insets navbarInsets = getNavbarInsets(windowMetrics); - final int bottomSpacerHeight = calculateBottomSpacerHeightForLandscape(titleHeight, - subtitleHeight, descriptionHeight, topSpacerHeight, textIndicatorHeight, - buttonBarHeight, navbarInsets.bottom); - - // Find the spacer width needed to horizontally align the icon with the sensor. - final int displayWidth = getMaximumWindowBounds(windowMetrics).width(); - final int dialogMargin = getDialogMarginPx(); - final int horizontalInset = navbarInsets.left + navbarInsets.right; - final int horizontalSpacerWidth = calculateHorizontalSpacerWidthForLandscape( - mSensorProps, displayWidth, dialogMargin, horizontalInset, scaleFactor); - - final int sensorDiameter = getSensorDiameter(scaleFactor); - final int remeasuredWidth = sensorDiameter + 2 * horizontalSpacerWidth; - - int remeasuredHeight = 0; - final int numChildren = mView.getChildCount(); - for (int i = 0; i < numChildren; i++) { - final View child = mView.getChildAt(i); - if (child.getId() == R.id.biometric_icon_frame) { - final FrameLayout iconFrame = (FrameLayout) child; - final View icon = iconFrame.getChildAt(0); - // Create a frame that's exactly the height of the sensor circle. - iconFrame.measure( - MeasureSpec.makeMeasureSpec(remeasuredWidth, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.EXACTLY)); - - // Ensure that the icon is never larger than the sensor. - icon.measure( - MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.AT_MOST), - MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.AT_MOST)); - } else if (child.getId() == R.id.space_above_icon) { - // Adjust the width and height of the top spacer if necessary. - final int newTopSpacerHeight = child.getLayoutParams().height - - Math.min(bottomSpacerHeight, 0); - child.measure( - MeasureSpec.makeMeasureSpec(remeasuredWidth, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(newTopSpacerHeight, MeasureSpec.EXACTLY)); - } else if (child.getId() == R.id.button_bar) { - // Adjust the width of the button bar while preserving its height. - child.measure( - MeasureSpec.makeMeasureSpec(remeasuredWidth, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec( - child.getLayoutParams().height, MeasureSpec.EXACTLY)); - } else if (child.getId() == R.id.space_below_icon) { - // Adjust the bottom spacer height to align the fingerprint icon with the sensor. - final int newBottomSpacerHeight = Math.max(bottomSpacerHeight, 0); - child.measure( - MeasureSpec.makeMeasureSpec(remeasuredWidth, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(newBottomSpacerHeight, MeasureSpec.EXACTLY)); - } else { - // Use the remeasured width for all other child views. - child.measure( - MeasureSpec.makeMeasureSpec(remeasuredWidth, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST)); - } - - if (child.getVisibility() != View.GONE) { - remeasuredHeight += child.getMeasuredHeight(); - } - } - - return new AuthDialog.LayoutParams(remeasuredWidth, remeasuredHeight); - } - - private int getViewHeightPx(@IdRes int viewId) { - final View view = mView.findViewById(viewId); - return view != null && view.getVisibility() != View.GONE ? view.getMeasuredHeight() : 0; - } - - private int getDialogMarginPx() { - return mView.getResources().getDimensionPixelSize(R.dimen.biometric_dialog_border_padding); - } - - @NonNull - private static Insets getNavbarInsets(@Nullable WindowMetrics windowMetrics) { - return windowMetrics != null - ? windowMetrics.getWindowInsets().getInsets(WindowInsets.Type.navigationBars()) - : Insets.NONE; - } - - @NonNull - private static Rect getMaximumWindowBounds(@Nullable WindowMetrics windowMetrics) { - return windowMetrics != null ? windowMetrics.getBounds() : new Rect(); - } - - /** - * For devices in portrait orientation where the sensor is too high up, calculates the amount of - * padding necessary to center the biometric icon within the sensor's physical location. - */ - @VisibleForTesting - static int calculateBottomSpacerHeightForPortrait( - @NonNull FingerprintSensorPropertiesInternal sensorProperties, int displayHeightPx, - int textIndicatorHeightPx, int buttonBarHeightPx, int dialogMarginPx, - int navbarBottomInsetPx, float scaleFactor) { - final SensorLocationInternal location = sensorProperties.getLocation(); - final int sensorDistanceFromBottom = displayHeightPx - - (int) (scaleFactor * location.sensorLocationY) - - (int) (scaleFactor * location.sensorRadius); - - final int spacerHeight = sensorDistanceFromBottom - - textIndicatorHeightPx - - buttonBarHeightPx - - dialogMarginPx - - navbarBottomInsetPx; - - if (DEBUG) { - Log.d(TAG, "Display height: " + displayHeightPx - + ", Distance from bottom: " + sensorDistanceFromBottom - + ", Bottom margin: " + dialogMarginPx - + ", Navbar bottom inset: " + navbarBottomInsetPx - + ", Bottom spacer height (portrait): " + spacerHeight - + ", Scale Factor: " + scaleFactor); - } - - return spacerHeight; - } - - /** - * For devices in landscape orientation where the sensor is too high up, calculates the amount - * of padding necessary to center the biometric icon within the sensor's physical location. - */ - @VisibleForTesting - static int calculateBottomSpacerHeightForLandscape(int titleHeightPx, int subtitleHeightPx, - int descriptionHeightPx, int topSpacerHeightPx, int textIndicatorHeightPx, - int buttonBarHeightPx, int navbarBottomInsetPx) { - - final int dialogHeightAboveIcon = titleHeightPx - + subtitleHeightPx - + descriptionHeightPx - + topSpacerHeightPx; - - final int dialogHeightBelowIcon = textIndicatorHeightPx + buttonBarHeightPx; - - final int bottomSpacerHeight = dialogHeightAboveIcon - - dialogHeightBelowIcon - - navbarBottomInsetPx; - - if (DEBUG) { - Log.d(TAG, "Title height: " + titleHeightPx - + ", Subtitle height: " + subtitleHeightPx - + ", Description height: " + descriptionHeightPx - + ", Top spacer height: " + topSpacerHeightPx - + ", Text indicator height: " + textIndicatorHeightPx - + ", Button bar height: " + buttonBarHeightPx - + ", Navbar bottom inset: " + navbarBottomInsetPx - + ", Bottom spacer height (landscape): " + bottomSpacerHeight); - } - - return bottomSpacerHeight; - } - - /** - * For devices in landscape orientation where the sensor is too left/right, calculates the - * amount of padding necessary to center the biometric icon within the sensor's physical - * location. - */ - @VisibleForTesting - static int calculateHorizontalSpacerWidthForLandscape( - @NonNull FingerprintSensorPropertiesInternal sensorProperties, int displayWidthPx, - int dialogMarginPx, int navbarHorizontalInsetPx, float scaleFactor) { - final SensorLocationInternal location = sensorProperties.getLocation(); - final int sensorDistanceFromEdge = displayWidthPx - - (int) (scaleFactor * location.sensorLocationY) - - (int) (scaleFactor * location.sensorRadius); - - final int horizontalPadding = sensorDistanceFromEdge - - dialogMarginPx - - navbarHorizontalInsetPx; - - if (DEBUG) { - Log.d(TAG, "Display width: " + displayWidthPx - + ", Distance from edge: " + sensorDistanceFromEdge - + ", Dialog margin: " + dialogMarginPx - + ", Navbar horizontal inset: " + navbarHorizontalInsetPx - + ", Horizontal spacer width (landscape): " + horizontalPadding - + ", Scale Factor: " + scaleFactor); - } - - return horizontalPadding; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt index 5e2b5ff5c1ac..6da5e42c12d9 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt @@ -188,7 +188,6 @@ constructor( val hasCredentialViewShown = promptKind.value.isCredential() val showBpForCredential = Flags.customBiometricPrompt() && - com.android.systemui.Flags.constraintBp() && !Utils.isBiometricAllowed(promptInfo) && isDeviceCredentialAllowed(promptInfo) && promptInfo.contentView != null && diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt index 348b4234a430..695707d782b7 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt @@ -44,7 +44,7 @@ sealed class BiometricPromptRequest( val logoDescription: String? = info.logoDescription val negativeButtonText: String = info.negativeButtonText?.toString() ?: "" val componentNameForConfirmDeviceCredentialActivity: ComponentName? = - info.componentNameForConfirmDeviceCredentialActivity + info.realCallerForConfirmDeviceCredentialActivity val allowBackgroundAuthentication = info.isAllowBackgroundAuthentication } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java b/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java deleted file mode 100644 index b450896729b7..000000000000 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.biometrics.ui; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.content.Context; -import android.graphics.Insets; -import android.util.AttributeSet; -import android.util.Log; -import android.view.View; -import android.view.WindowInsets; -import android.view.WindowManager; -import android.widget.FrameLayout; -import android.widget.LinearLayout; -import android.widget.TextView; - -import com.android.systemui.biometrics.AuthController; -import com.android.systemui.biometrics.AuthDialog; -import com.android.systemui.biometrics.UdfpsDialogMeasureAdapter; -import com.android.systemui.res.R; - -import kotlin.Pair; - -/** - * Contains the Biometric views (title, subtitle, icon, buttons, etc.). - * - * TODO(b/251476085): get the udfps junk out of here, at a minimum. Likely can be replaced with a - * normal LinearLayout. - */ -public class BiometricPromptLayout extends LinearLayout { - - private static final String TAG = "BiometricPromptLayout"; - - @NonNull - private final WindowManager mWindowManager; - @Nullable - private AuthController.ScaleFactorProvider mScaleFactorProvider; - @Nullable - private UdfpsDialogMeasureAdapter mUdfpsAdapter; - - private final boolean mUseCustomBpSize; - private final int mCustomBpWidth; - private final int mCustomBpHeight; - - public BiometricPromptLayout(Context context) { - this(context, null); - } - - public BiometricPromptLayout(Context context, AttributeSet attrs) { - super(context, attrs); - - mWindowManager = context.getSystemService(WindowManager.class); - - mUseCustomBpSize = getResources().getBoolean(R.bool.use_custom_bp_size); - mCustomBpWidth = getResources().getDimensionPixelSize(R.dimen.biometric_dialog_width); - mCustomBpHeight = getResources().getDimensionPixelSize(R.dimen.biometric_dialog_height); - } - - @Deprecated - public void setUdfpsAdapter(@NonNull UdfpsDialogMeasureAdapter adapter, - @NonNull AuthController.ScaleFactorProvider scaleProvider) { - mUdfpsAdapter = adapter; - mScaleFactorProvider = scaleProvider != null ? scaleProvider : () -> 1.0f; - } - - @Deprecated - public boolean isUdfps() { - return mUdfpsAdapter != null; - } - - @Deprecated - public Pair<Integer, Integer> getUpdatedFingerprintAffordanceSize() { - if (mUdfpsAdapter != null) { - final int sensorDiameter = mUdfpsAdapter.getSensorDiameter( - mScaleFactorProvider.provide()); - return new Pair(sensorDiameter, sensorDiameter); - } - return null; - } - - @NonNull - private AuthDialog.LayoutParams onMeasureInternal(int width, int height) { - int totalHeight = 0; - final int numChildren = getChildCount(); - for (int i = 0; i < numChildren; i++) { - final View child = getChildAt(i); - - if (child.getId() == R.id.space_above_icon - || child.getId() == R.id.space_above_content - || child.getId() == R.id.space_below_icon - || child.getId() == R.id.button_bar) { - child.measure( - MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(child.getLayoutParams().height, - MeasureSpec.EXACTLY)); - } else if (child.getId() == R.id.biometric_icon_frame) { - final View iconView = findViewById(R.id.biometric_icon); - child.measure( - MeasureSpec.makeMeasureSpec(iconView.getLayoutParams().width, - MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(iconView.getLayoutParams().height, - MeasureSpec.EXACTLY)); - } else if (child.getId() == R.id.logo) { - child.measure( - MeasureSpec.makeMeasureSpec(child.getLayoutParams().width, - MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(child.getLayoutParams().height, - MeasureSpec.EXACTLY)); - } else if (child.getId() == R.id.biometric_icon) { - child.measure( - MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST), - MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST)); - } else { - child.measure( - MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST)); - } - - if (child.getVisibility() != View.GONE) { - totalHeight += child.getMeasuredHeight(); - } - } - - final AuthDialog.LayoutParams params = new AuthDialog.LayoutParams(width, totalHeight); - if (mUdfpsAdapter != null) { - return mUdfpsAdapter.onMeasureInternal(width, height, params, - (mScaleFactorProvider != null) ? mScaleFactorProvider.provide() : 1.0f); - } else { - return params; - } - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - int width = MeasureSpec.getSize(widthMeasureSpec); - int height = MeasureSpec.getSize(heightMeasureSpec); - - if (mUseCustomBpSize) { - width = mCustomBpWidth; - height = mCustomBpHeight; - } else { - width = Math.min(width, height); - } - - // add nav bar insets since the parent AuthContainerView - // uses LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS - final Insets insets = mWindowManager.getMaximumWindowMetrics().getWindowInsets() - .getInsets(WindowInsets.Type.navigationBars()); - final AuthDialog.LayoutParams params = onMeasureInternal(width, height); - setMeasuredDimension(params.mMediumWidth + insets.left + insets.right, - params.mMediumHeight + insets.bottom); - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - - if (mUdfpsAdapter != null) { - // Move the UDFPS icon and indicator text if necessary. This probably only needs to - // happen for devices where the UDFPS sensor is too low. - // TODO(b/201510778): Update this logic to support cases where the sensor or text - // overlap the button bar area. - final float bottomSpacerHeight = mUdfpsAdapter.getBottomSpacerHeight(); - Log.w(TAG, "bottomSpacerHeight: " + bottomSpacerHeight); - if (bottomSpacerHeight < 0) { - final FrameLayout iconFrame = findViewById(R.id.biometric_icon_frame); - iconFrame.setTranslationY(-bottomSpacerHeight); - final TextView indicator = findViewById(R.id.indicator); - indicator.setTranslationY(-bottomSpacerHeight); - } - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt index 7ccac03bcac6..0b474f8092a4 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt @@ -38,14 +38,13 @@ import android.widget.LinearLayout import android.widget.Space import android.widget.TextView import com.android.settingslib.Utils -import com.android.systemui.biometrics.ui.BiometricPromptLayout import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.res.R import kotlin.math.ceil private const val TAG = "BiometricCustomizedViewBinder" -/** Sub-binder for [BiometricPromptLayout.customized_view_container]. */ +/** Sub-binder for Biometric Prompt Customized View */ object BiometricCustomizedViewBinder { fun bind( customizedViewContainer: LinearLayout, diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt index 43ba097684d6..a20a17f13a42 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt @@ -45,13 +45,10 @@ import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import com.airbnb.lottie.LottieAnimationView import com.airbnb.lottie.LottieCompositionFactory -import com.android.systemui.Flags.constraintBp -import com.android.systemui.biometrics.AuthPanelController import com.android.systemui.biometrics.shared.model.BiometricModalities import com.android.systemui.biometrics.shared.model.BiometricModality import com.android.systemui.biometrics.shared.model.PromptKind import com.android.systemui.biometrics.shared.model.asBiometricModality -import com.android.systemui.biometrics.ui.BiometricPromptLayout import com.android.systemui.biometrics.ui.viewmodel.FingerprintStartMode import com.android.systemui.biometrics.ui.viewmodel.PromptMessage import com.android.systemui.biometrics.ui.viewmodel.PromptSize @@ -72,28 +69,18 @@ private const val MAX_LOGO_DESCRIPTION_CHARACTER_NUMBER = 30 /** Top-most view binder for BiometricPrompt views. */ object BiometricViewBinder { - /** Binds a [BiometricPromptLayout] to a [PromptViewModel]. */ + /** Binds a Biometric Prompt View to a [PromptViewModel]. */ @SuppressLint("ClickableViewAccessibility") @JvmStatic fun bind( view: View, viewModel: PromptViewModel, - panelViewController: AuthPanelController?, jankListener: BiometricJankListener, backgroundView: View, legacyCallback: Spaghetti.Callback, applicationScope: CoroutineScope, vibratorHelper: VibratorHelper, ): Spaghetti { - /** - * View is only set visible in BiometricViewSizeBinder once PromptSize is determined that - * accounts for iconView size, to prevent prompt resizing being visible to the user. - * - * TODO(b/288175072): May be able to remove this once constraint layout is implemented - */ - if (!constraintBp()) { - view.visibility = View.INVISIBLE - } val accessibilityManager = view.context.getSystemService(AccessibilityManager::class.java)!! val textColorError = @@ -104,9 +91,7 @@ object BiometricViewBinder { R.style.TextAppearance_AuthCredential_Indicator, intArrayOf(android.R.attr.textColor) ) - val textColorHint = - if (constraintBp()) attributes.getColor(0, 0) - else view.resources.getColor(R.color.biometric_dialog_gray, view.context.theme) + val textColorHint = attributes.getColor(0, 0) attributes.recycle() val logoView = view.requireViewById<ImageView>(R.id.logo) @@ -116,12 +101,7 @@ object BiometricViewBinder { val descriptionView = view.requireViewById<TextView>(R.id.description) val customizedViewContainer = view.requireViewById<LinearLayout>(R.id.customized_view_container) - val udfpsGuidanceView = - if (constraintBp()) { - view.requireViewById<View>(R.id.panel) - } else { - backgroundView - } + val udfpsGuidanceView = view.requireViewById<View>(R.id.panel) // set selected to enable marquee unless a screen reader is enabled titleView.isSelected = @@ -130,14 +110,6 @@ object BiometricViewBinder { !accessibilityManager.isEnabled || !accessibilityManager.isTouchExplorationEnabled val iconView = view.requireViewById<LottieAnimationView>(R.id.biometric_icon) - - val iconSizeOverride = - if (constraintBp()) { - null - } else { - (view as BiometricPromptLayout).updatedFingerprintAffordanceSize - } - val indicatorMessageView = view.requireViewById<TextView>(R.id.indicator) // Negative-side (left) buttons @@ -213,7 +185,7 @@ object BiometricViewBinder { subtitleView.text = viewModel.subtitle.first() descriptionView.text = viewModel.description.first() - if (Flags.customBiometricPrompt() && constraintBp()) { + if (Flags.customBiometricPrompt()) { BiometricCustomizedViewBinder.bind( customizedViewContainer, viewModel.contentView.first(), @@ -250,22 +222,6 @@ object BiometricViewBinder { descriptionView, customizedViewContainer, ), - viewsToFadeInOnSizeChange = - listOf( - logoView, - logoDescriptionView, - titleView, - subtitleView, - descriptionView, - customizedViewContainer, - indicatorMessageView, - negativeButton, - cancelButton, - retryButton, - confirmationButton, - credentialFallbackButton, - ), - panelViewController = panelViewController, jankListener = jankListener, ) } @@ -275,7 +231,6 @@ object BiometricViewBinder { if (!showWithoutIcon) { PromptIconViewBinder.bind( iconView, - iconSizeOverride, viewModel, ) } @@ -329,20 +284,6 @@ object BiometricViewBinder { } } - // set padding - launch { - viewModel.promptPadding.collect { promptPadding -> - if (!constraintBp()) { - view.setPadding( - promptPadding.left, - promptPadding.top, - promptPadding.right, - promptPadding.bottom - ) - } - } - } - // configure & hide/disable buttons launch { viewModel.credentialKind @@ -546,24 +487,6 @@ class Spaghetti( fun onAuthenticatedAndConfirmed() } - @Deprecated("TODO(b/330788871): remove after replacing AuthContainerView") - enum class BiometricState { - /** Authentication hardware idle. */ - STATE_IDLE, - /** UI animating in, authentication hardware active. */ - STATE_AUTHENTICATING_ANIMATING_IN, - /** UI animated in, authentication hardware active. */ - STATE_AUTHENTICATING, - /** UI animated in, authentication hardware active. */ - STATE_HELP, - /** Hard error, e.g. ERROR_TIMEOUT. Authentication hardware idle. */ - STATE_ERROR, - /** Authenticated, waiting for user confirmation. Authentication hardware idle. */ - STATE_PENDING_CONFIRMATION, - /** Authenticated, dialog animating away soon. */ - STATE_AUTHENTICATED, - } - private var lifecycleScope: CoroutineScope? = null private var modalities: BiometricModalities = BiometricModalities() private var legacyCallback: Callback? = null @@ -699,15 +622,8 @@ class Spaghetti( } fun startTransitionToCredentialUI(isError: Boolean) { - if (!constraintBp()) { - applicationScope.launch { - viewModel.onSwitchToCredential() - legacyCallback?.onUseDeviceCredential() - } - } else { - viewModel.onSwitchToCredential() - legacyCallback?.onUseDeviceCredential() - } + viewModel.onSwitchToCredential() + legacyCallback?.onUseDeviceCredential() } fun cancelAnimation() { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt index b9ec2de63269..85c3ae3f214e 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt @@ -38,10 +38,7 @@ import androidx.constraintlayout.widget.ConstraintSet import androidx.constraintlayout.widget.Guideline import androidx.core.animation.addListener import androidx.core.view.doOnLayout -import androidx.core.view.isGone import androidx.lifecycle.lifecycleScope -import com.android.systemui.Flags.constraintBp -import com.android.systemui.biometrics.AuthPanelController import com.android.systemui.biometrics.Utils import com.android.systemui.biometrics.ui.viewmodel.PromptPosition import com.android.systemui.biometrics.ui.viewmodel.PromptSize @@ -49,7 +46,6 @@ import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel import com.android.systemui.biometrics.ui.viewmodel.isLarge import com.android.systemui.biometrics.ui.viewmodel.isLeft import com.android.systemui.biometrics.ui.viewmodel.isMedium -import com.android.systemui.biometrics.ui.viewmodel.isNullOrNotSmall import com.android.systemui.biometrics.ui.viewmodel.isSmall import com.android.systemui.biometrics.ui.viewmodel.isTop import com.android.systemui.lifecycle.repeatWhenAttached @@ -71,8 +67,6 @@ object BiometricViewSizeBinder { view: View, viewModel: PromptViewModel, viewsToHideWhenSmall: List<View>, - viewsToFadeInOnSizeChange: List<View>, - panelViewController: AuthPanelController?, jankListener: BiometricJankListener, ) { val windowManager = requireNotNull(view.context.getSystemService(WindowManager::class.java)) @@ -92,553 +86,366 @@ object BiometricViewSizeBinder { } } - if (constraintBp()) { - val leftGuideline = view.requireViewById<Guideline>(R.id.leftGuideline) - val topGuideline = view.requireViewById<Guideline>(R.id.topGuideline) - val rightGuideline = view.requireViewById<Guideline>(R.id.rightGuideline) - val midGuideline = view.findViewById<Guideline>(R.id.midGuideline) - - val iconHolderView = view.requireViewById<View>(R.id.biometric_icon) - val panelView = view.requireViewById<View>(R.id.panel) - val cornerRadius = view.resources.getDimension(R.dimen.biometric_dialog_corner_size) - val pxToDp = - TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, - 1f, - view.resources.displayMetrics - ) - val cornerRadiusPx = (pxToDp * cornerRadius).toInt() - - var currentSize: PromptSize? = null - var currentPosition: PromptPosition = PromptPosition.Bottom - panelView.outlineProvider = - object : ViewOutlineProvider() { - override fun getOutline(view: View, outline: Outline) { - when (currentPosition) { - PromptPosition.Right -> { - outline.setRoundRect( - 0, - 0, - view.width + cornerRadiusPx, - view.height, - cornerRadiusPx.toFloat() - ) - } - PromptPosition.Left -> { - outline.setRoundRect( - -cornerRadiusPx, - 0, - view.width, - view.height, - cornerRadiusPx.toFloat() - ) - } - PromptPosition.Bottom, - PromptPosition.Top -> { - outline.setRoundRect( - 0, - 0, - view.width, - view.height + cornerRadiusPx, - cornerRadiusPx.toFloat() - ) - } + val leftGuideline = view.requireViewById<Guideline>(R.id.leftGuideline) + val topGuideline = view.requireViewById<Guideline>(R.id.topGuideline) + val rightGuideline = view.requireViewById<Guideline>(R.id.rightGuideline) + val midGuideline = view.findViewById<Guideline>(R.id.midGuideline) + + val iconHolderView = view.requireViewById<View>(R.id.biometric_icon) + val panelView = view.requireViewById<View>(R.id.panel) + val cornerRadius = view.resources.getDimension(R.dimen.biometric_dialog_corner_size) + val pxToDp = + TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + 1f, + view.resources.displayMetrics + ) + val cornerRadiusPx = (pxToDp * cornerRadius).toInt() + + var currentSize: PromptSize? = null + var currentPosition: PromptPosition = PromptPosition.Bottom + panelView.outlineProvider = + object : ViewOutlineProvider() { + override fun getOutline(view: View, outline: Outline) { + when (currentPosition) { + PromptPosition.Right -> { + outline.setRoundRect( + 0, + 0, + view.width + cornerRadiusPx, + view.height, + cornerRadiusPx.toFloat() + ) } - } - } - - // ConstraintSets for animating between prompt sizes - val mediumConstraintSet = ConstraintSet() - mediumConstraintSet.clone(view as ConstraintLayout) - - val smallConstraintSet = ConstraintSet() - smallConstraintSet.clone(mediumConstraintSet) - - val largeConstraintSet = ConstraintSet() - largeConstraintSet.clone(mediumConstraintSet) - largeConstraintSet.constrainMaxWidth(R.id.panel, 0) - largeConstraintSet.setGuidelineBegin(R.id.leftGuideline, 0) - largeConstraintSet.setGuidelineEnd(R.id.rightGuideline, 0) - - // TODO: Investigate better way to handle 180 rotations - val flipConstraintSet = ConstraintSet() - - view.doOnLayout { - fun setVisibilities(hideSensorIcon: Boolean, size: PromptSize) { - viewsToHideWhenSmall.forEach { it.showContentOrHide(forceHide = size.isSmall) } - largeConstraintSet.setVisibility(iconHolderView.id, View.GONE) - largeConstraintSet.setVisibility(R.id.indicator, View.GONE) - largeConstraintSet.setVisibility(R.id.scrollView, View.GONE) - - if (hideSensorIcon) { - smallConstraintSet.setVisibility(iconHolderView.id, View.GONE) - smallConstraintSet.setVisibility(R.id.indicator, View.GONE) - mediumConstraintSet.setVisibility(iconHolderView.id, View.GONE) - mediumConstraintSet.setVisibility(R.id.indicator, View.GONE) - } - } - - view.repeatWhenAttached { - lifecycleScope.launch { - viewModel.iconPosition.collect { position -> - if (position != Rect()) { - val iconParams = - iconHolderView.layoutParams as ConstraintLayout.LayoutParams - - if (position.left != 0) { - iconParams.endToEnd = ConstraintSet.UNSET - iconParams.leftMargin = position.left - mediumConstraintSet.clear( - R.id.biometric_icon, - ConstraintSet.RIGHT - ) - mediumConstraintSet.connect( - R.id.biometric_icon, - ConstraintSet.LEFT, - ConstraintSet.PARENT_ID, - ConstraintSet.LEFT - ) - mediumConstraintSet.setMargin( - R.id.biometric_icon, - ConstraintSet.LEFT, - position.left - ) - smallConstraintSet.clear( - R.id.biometric_icon, - ConstraintSet.RIGHT - ) - smallConstraintSet.connect( - R.id.biometric_icon, - ConstraintSet.LEFT, - ConstraintSet.PARENT_ID, - ConstraintSet.LEFT - ) - smallConstraintSet.setMargin( - R.id.biometric_icon, - ConstraintSet.LEFT, - position.left - ) - } - if (position.top != 0) { - iconParams.bottomToBottom = ConstraintSet.UNSET - iconParams.topMargin = position.top - mediumConstraintSet.clear( - R.id.biometric_icon, - ConstraintSet.BOTTOM - ) - mediumConstraintSet.setMargin( - R.id.biometric_icon, - ConstraintSet.TOP, - position.top - ) - smallConstraintSet.clear( - R.id.biometric_icon, - ConstraintSet.BOTTOM - ) - smallConstraintSet.setMargin( - R.id.biometric_icon, - ConstraintSet.TOP, - position.top - ) - } - if (position.right != 0) { - iconParams.startToStart = ConstraintSet.UNSET - iconParams.rightMargin = position.right - mediumConstraintSet.clear( - R.id.biometric_icon, - ConstraintSet.LEFT - ) - mediumConstraintSet.connect( - R.id.biometric_icon, - ConstraintSet.RIGHT, - ConstraintSet.PARENT_ID, - ConstraintSet.RIGHT - ) - mediumConstraintSet.setMargin( - R.id.biometric_icon, - ConstraintSet.RIGHT, - position.right - ) - smallConstraintSet.clear( - R.id.biometric_icon, - ConstraintSet.LEFT - ) - smallConstraintSet.connect( - R.id.biometric_icon, - ConstraintSet.RIGHT, - ConstraintSet.PARENT_ID, - ConstraintSet.RIGHT - ) - smallConstraintSet.setMargin( - R.id.biometric_icon, - ConstraintSet.RIGHT, - position.right - ) - } - if (position.bottom != 0) { - iconParams.topToTop = ConstraintSet.UNSET - iconParams.bottomMargin = position.bottom - mediumConstraintSet.clear( - R.id.biometric_icon, - ConstraintSet.TOP - ) - mediumConstraintSet.setMargin( - R.id.biometric_icon, - ConstraintSet.BOTTOM, - position.bottom - ) - smallConstraintSet.clear(R.id.biometric_icon, ConstraintSet.TOP) - smallConstraintSet.setMargin( - R.id.biometric_icon, - ConstraintSet.BOTTOM, - position.bottom - ) - } - iconHolderView.layoutParams = iconParams - } + PromptPosition.Left -> { + outline.setRoundRect( + -cornerRadiusPx, + 0, + view.width, + view.height, + cornerRadiusPx.toFloat() + ) } - } - - lifecycleScope.launch { - viewModel.iconSize.collect { iconSize -> - iconHolderView.layoutParams.width = iconSize.first - iconHolderView.layoutParams.height = iconSize.second - mediumConstraintSet.constrainWidth(R.id.biometric_icon, iconSize.first) - mediumConstraintSet.constrainHeight( - R.id.biometric_icon, - iconSize.second + PromptPosition.Bottom, + PromptPosition.Top -> { + outline.setRoundRect( + 0, + 0, + view.width, + view.height + cornerRadiusPx, + cornerRadiusPx.toFloat() ) } } + } + } - lifecycleScope.launch { - viewModel.guidelineBounds.collect { bounds -> - val bottomInset = - windowManager.maximumWindowMetrics.windowInsets - .getInsets(WindowInsets.Type.navigationBars()) - .bottom - mediumConstraintSet.setGuidelineEnd(R.id.bottomGuideline, bottomInset) - - if (bounds.left >= 0) { - mediumConstraintSet.setGuidelineBegin(leftGuideline.id, bounds.left) - smallConstraintSet.setGuidelineBegin(leftGuideline.id, bounds.left) - } else if (bounds.left < 0) { - mediumConstraintSet.setGuidelineEnd( - leftGuideline.id, - abs(bounds.left) + // ConstraintSets for animating between prompt sizes + val mediumConstraintSet = ConstraintSet() + mediumConstraintSet.clone(view as ConstraintLayout) + + val smallConstraintSet = ConstraintSet() + smallConstraintSet.clone(mediumConstraintSet) + + val largeConstraintSet = ConstraintSet() + largeConstraintSet.clone(mediumConstraintSet) + largeConstraintSet.constrainMaxWidth(R.id.panel, 0) + largeConstraintSet.setGuidelineBegin(R.id.leftGuideline, 0) + largeConstraintSet.setGuidelineEnd(R.id.rightGuideline, 0) + + // TODO: Investigate better way to handle 180 rotations + val flipConstraintSet = ConstraintSet() + + view.doOnLayout { + fun setVisibilities(hideSensorIcon: Boolean, size: PromptSize) { + viewsToHideWhenSmall.forEach { it.showContentOrHide(forceHide = size.isSmall) } + largeConstraintSet.setVisibility(iconHolderView.id, View.GONE) + largeConstraintSet.setVisibility(R.id.biometric_icon_overlay, View.GONE) + largeConstraintSet.setVisibility(R.id.indicator, View.GONE) + largeConstraintSet.setVisibility(R.id.scrollView, View.GONE) + + if (hideSensorIcon) { + smallConstraintSet.setVisibility(iconHolderView.id, View.GONE) + smallConstraintSet.setVisibility(R.id.biometric_icon_overlay, View.GONE) + smallConstraintSet.setVisibility(R.id.indicator, View.GONE) + mediumConstraintSet.setVisibility(iconHolderView.id, View.GONE) + mediumConstraintSet.setVisibility(R.id.biometric_icon_overlay, View.GONE) + mediumConstraintSet.setVisibility(R.id.indicator, View.GONE) + } + } + + view.repeatWhenAttached { + lifecycleScope.launch { + viewModel.iconPosition.collect { position -> + if (position != Rect()) { + val iconParams = + iconHolderView.layoutParams as ConstraintLayout.LayoutParams + + if (position.left != 0) { + iconParams.endToEnd = ConstraintSet.UNSET + iconParams.leftMargin = position.left + mediumConstraintSet.clear(R.id.biometric_icon, ConstraintSet.RIGHT) + mediumConstraintSet.connect( + R.id.biometric_icon, + ConstraintSet.LEFT, + ConstraintSet.PARENT_ID, + ConstraintSet.LEFT ) - smallConstraintSet.setGuidelineEnd( - leftGuideline.id, - abs(bounds.left) + mediumConstraintSet.setMargin( + R.id.biometric_icon, + ConstraintSet.LEFT, + position.left ) - } - - if (bounds.right >= 0) { - mediumConstraintSet.setGuidelineEnd(rightGuideline.id, bounds.right) - smallConstraintSet.setGuidelineEnd(rightGuideline.id, bounds.right) - } else if (bounds.right < 0) { - mediumConstraintSet.setGuidelineBegin( - rightGuideline.id, - abs(bounds.right) + smallConstraintSet.clear(R.id.biometric_icon, ConstraintSet.RIGHT) + smallConstraintSet.connect( + R.id.biometric_icon, + ConstraintSet.LEFT, + ConstraintSet.PARENT_ID, + ConstraintSet.LEFT ) - smallConstraintSet.setGuidelineBegin( - rightGuideline.id, - abs(bounds.right) + smallConstraintSet.setMargin( + R.id.biometric_icon, + ConstraintSet.LEFT, + position.left ) } - - if (bounds.top >= 0) { - mediumConstraintSet.setGuidelineBegin(topGuideline.id, bounds.top) - smallConstraintSet.setGuidelineBegin(topGuideline.id, bounds.top) - } else if (bounds.top < 0) { - mediumConstraintSet.setGuidelineEnd( - topGuideline.id, - abs(bounds.top) + if (position.top != 0) { + iconParams.bottomToBottom = ConstraintSet.UNSET + iconParams.topMargin = position.top + mediumConstraintSet.clear(R.id.biometric_icon, ConstraintSet.BOTTOM) + mediumConstraintSet.setMargin( + R.id.biometric_icon, + ConstraintSet.TOP, + position.top ) - smallConstraintSet.setGuidelineEnd(topGuideline.id, abs(bounds.top)) - } - - if (midGuideline != null) { - val left = - if (bounds.left >= 0) { - abs(bounds.left) - } else { - view.width - abs(bounds.left) - } - val right = - if (bounds.right >= 0) { - view.width - abs(bounds.right) - } else { - abs(bounds.right) - } - val mid = (left + right) / 2 - mediumConstraintSet.setGuidelineBegin(midGuideline.id, mid) - } - } - } - - lifecycleScope.launch { - combine(viewModel.hideSensorIcon, viewModel.size, ::Pair).collect { - (hideSensorIcon, size) -> - setVisibilities(hideSensorIcon, size) - } - } - - lifecycleScope.launch { - combine(viewModel.position, viewModel.size, ::Pair).collect { - (position, size) -> - if (position.isLeft) { - if (size.isSmall) { - flipConstraintSet.clone(smallConstraintSet) - } else { - flipConstraintSet.clone(mediumConstraintSet) - } - - // Move all content to other panel - flipConstraintSet.connect( - R.id.scrollView, - ConstraintSet.LEFT, - R.id.midGuideline, - ConstraintSet.LEFT + smallConstraintSet.clear(R.id.biometric_icon, ConstraintSet.BOTTOM) + smallConstraintSet.setMargin( + R.id.biometric_icon, + ConstraintSet.TOP, + position.top ) - flipConstraintSet.connect( - R.id.scrollView, + } + if (position.right != 0) { + iconParams.startToStart = ConstraintSet.UNSET + iconParams.rightMargin = position.right + mediumConstraintSet.clear(R.id.biometric_icon, ConstraintSet.LEFT) + mediumConstraintSet.connect( + R.id.biometric_icon, ConstraintSet.RIGHT, - R.id.rightGuideline, + ConstraintSet.PARENT_ID, ConstraintSet.RIGHT ) - } else if (position.isTop) { - // Top position is only used for 180 rotation Udfps - // Requires repositioning due to sensor location at top of screen - mediumConstraintSet.connect( - R.id.scrollView, - ConstraintSet.TOP, - R.id.indicator, - ConstraintSet.BOTTOM + mediumConstraintSet.setMargin( + R.id.biometric_icon, + ConstraintSet.RIGHT, + position.right ) - mediumConstraintSet.connect( - R.id.scrollView, - ConstraintSet.BOTTOM, - R.id.button_bar, - ConstraintSet.TOP + smallConstraintSet.clear(R.id.biometric_icon, ConstraintSet.LEFT) + smallConstraintSet.connect( + R.id.biometric_icon, + ConstraintSet.RIGHT, + ConstraintSet.PARENT_ID, + ConstraintSet.RIGHT ) - mediumConstraintSet.connect( - R.id.panel, - ConstraintSet.TOP, + smallConstraintSet.setMargin( R.id.biometric_icon, - ConstraintSet.TOP + ConstraintSet.RIGHT, + position.right ) + } + if (position.bottom != 0) { + iconParams.topToTop = ConstraintSet.UNSET + iconParams.bottomMargin = position.bottom + mediumConstraintSet.clear(R.id.biometric_icon, ConstraintSet.TOP) mediumConstraintSet.setMargin( - R.id.panel, - ConstraintSet.TOP, - (-24 * pxToDp).toInt() + R.id.biometric_icon, + ConstraintSet.BOTTOM, + position.bottom + ) + smallConstraintSet.clear(R.id.biometric_icon, ConstraintSet.TOP) + smallConstraintSet.setMargin( + R.id.biometric_icon, + ConstraintSet.BOTTOM, + position.bottom ) - mediumConstraintSet.setVerticalBias(R.id.scrollView, 0f) } + iconHolderView.layoutParams = iconParams + } + } + } - when { - size.isSmall -> { - if (position.isLeft) { - flipConstraintSet.applyTo(view) - } else { - smallConstraintSet.applyTo(view) - } - } - size.isMedium && currentSize.isSmall -> { - val autoTransition = AutoTransition() - autoTransition.setDuration( - ANIMATE_SMALL_TO_MEDIUM_DURATION_MS.toLong() - ) - - TransitionManager.beginDelayedTransition(view, autoTransition) - - if (position.isLeft) { - flipConstraintSet.applyTo(view) - } else { - mediumConstraintSet.applyTo(view) - } - } - size.isMedium -> { - if (position.isLeft) { - flipConstraintSet.applyTo(view) - } else { - mediumConstraintSet.applyTo(view) - } - } - size.isLarge && currentSize.isMedium -> { - val autoTransition = AutoTransition() - autoTransition.setDuration( - ANIMATE_MEDIUM_TO_LARGE_DURATION_MS.toLong() - ) - - TransitionManager.beginDelayedTransition(view, autoTransition) - largeConstraintSet.applyTo(view) - } - } + lifecycleScope.launch { + viewModel.iconSize.collect { iconSize -> + iconHolderView.layoutParams.width = iconSize.first + iconHolderView.layoutParams.height = iconSize.second + mediumConstraintSet.constrainWidth(R.id.biometric_icon, iconSize.first) + mediumConstraintSet.constrainHeight(R.id.biometric_icon, iconSize.second) + } + } + + lifecycleScope.launch { + viewModel.guidelineBounds.collect { bounds -> + val bottomInset = + windowManager.maximumWindowMetrics.windowInsets + .getInsets(WindowInsets.Type.navigationBars()) + .bottom + mediumConstraintSet.setGuidelineEnd(R.id.bottomGuideline, bottomInset) + + if (bounds.left >= 0) { + mediumConstraintSet.setGuidelineBegin(leftGuideline.id, bounds.left) + smallConstraintSet.setGuidelineBegin(leftGuideline.id, bounds.left) + } else if (bounds.left < 0) { + mediumConstraintSet.setGuidelineEnd(leftGuideline.id, abs(bounds.left)) + smallConstraintSet.setGuidelineEnd(leftGuideline.id, abs(bounds.left)) + } + + if (bounds.right >= 0) { + mediumConstraintSet.setGuidelineEnd(rightGuideline.id, bounds.right) + smallConstraintSet.setGuidelineEnd(rightGuideline.id, bounds.right) + } else if (bounds.right < 0) { + mediumConstraintSet.setGuidelineBegin( + rightGuideline.id, + abs(bounds.right) + ) + smallConstraintSet.setGuidelineBegin( + rightGuideline.id, + abs(bounds.right) + ) + } - currentSize = size - currentPosition = position - notifyAccessibilityChanged() + if (bounds.top >= 0) { + mediumConstraintSet.setGuidelineBegin(topGuideline.id, bounds.top) + smallConstraintSet.setGuidelineBegin(topGuideline.id, bounds.top) + } else if (bounds.top < 0) { + mediumConstraintSet.setGuidelineEnd(topGuideline.id, abs(bounds.top)) + smallConstraintSet.setGuidelineEnd(topGuideline.id, abs(bounds.top)) + } - panelView.invalidateOutline() - view.invalidate() - view.requestLayout() + if (midGuideline != null) { + val left = + if (bounds.left >= 0) { + abs(bounds.left) + } else { + view.width - abs(bounds.left) + } + val right = + if (bounds.right >= 0) { + view.width - abs(bounds.right) + } else { + abs(bounds.right) + } + val mid = (left + right) / 2 + mediumConstraintSet.setGuidelineBegin(midGuideline.id, mid) } } } - } - } else if (panelViewController != null) { - val iconHolderView = view.requireViewById<View>(R.id.biometric_icon_frame) - val iconPadding = view.resources.getDimension(R.dimen.biometric_dialog_icon_padding) - val fullSizeYOffset = - view.resources.getDimension( - R.dimen.biometric_dialog_medium_to_large_translation_offset - ) - - // cache the original position of the icon view (as done in legacy view) - // this must happen before any size changes can be made - view.doOnLayout { - // TODO(b/251476085): this old way of positioning has proven itself unreliable - // remove this and associated thing like (UdfpsDialogMeasureAdapter) and - // pin to the physical sensor - val iconHolderOriginalY = iconHolderView.y - - // bind to prompt - // TODO(b/251476085): migrate the legacy panel controller and simplify this - view.repeatWhenAttached { - var currentSize: PromptSize? = null - lifecycleScope.launch { - /** - * View is only set visible in BiometricViewSizeBinder once PromptSize is - * determined that accounts for iconView size, to prevent prompt resizing - * being visible to the user. - * - * TODO(b/288175072): May be able to remove isIconViewLoaded once constraint - * layout is implemented - */ - combine(viewModel.isIconViewLoaded, viewModel.size, ::Pair).collect { - (isIconViewLoaded, size) -> - if (!isIconViewLoaded) { - return@collect - } - // prepare for animated size transitions - for (v in viewsToHideWhenSmall) { - v.showContentOrHide(forceHide = size.isSmall) - } + lifecycleScope.launch { + combine(viewModel.hideSensorIcon, viewModel.size, ::Pair).collect { + (hideSensorIcon, size) -> + setVisibilities(hideSensorIcon, size) + } + } - if (viewModel.hideSensorIcon.first()) { - iconHolderView.visibility = View.GONE + lifecycleScope.launch { + combine(viewModel.position, viewModel.size, ::Pair).collect { (position, size) + -> + if (position.isLeft) { + if (size.isSmall) { + flipConstraintSet.clone(smallConstraintSet) + } else { + flipConstraintSet.clone(mediumConstraintSet) } - if (currentSize == null && size.isSmall) { - iconHolderView.alpha = 0f - } - if ((currentSize.isSmall && size.isMedium) || size.isSmall) { - viewsToFadeInOnSizeChange.forEach { it.alpha = 0f } + // Move all content to other panel + flipConstraintSet.connect( + R.id.scrollView, + ConstraintSet.LEFT, + R.id.midGuideline, + ConstraintSet.LEFT + ) + flipConstraintSet.connect( + R.id.scrollView, + ConstraintSet.RIGHT, + R.id.rightGuideline, + ConstraintSet.RIGHT + ) + } else if (position.isTop) { + // Top position is only used for 180 rotation Udfps + // Requires repositioning due to sensor location at top of screen + mediumConstraintSet.connect( + R.id.scrollView, + ConstraintSet.TOP, + R.id.indicator, + ConstraintSet.BOTTOM + ) + mediumConstraintSet.connect( + R.id.scrollView, + ConstraintSet.BOTTOM, + R.id.button_bar, + ConstraintSet.TOP + ) + mediumConstraintSet.connect( + R.id.panel, + ConstraintSet.TOP, + R.id.biometric_icon, + ConstraintSet.TOP + ) + mediumConstraintSet.setMargin( + R.id.panel, + ConstraintSet.TOP, + (-24 * pxToDp).toInt() + ) + mediumConstraintSet.setVerticalBias(R.id.scrollView, 0f) + } + + when { + size.isSmall -> { + if (position.isLeft) { + flipConstraintSet.applyTo(view) + } else { + smallConstraintSet.applyTo(view) + } } + size.isMedium && currentSize.isSmall -> { + val autoTransition = AutoTransition() + autoTransition.setDuration( + ANIMATE_SMALL_TO_MEDIUM_DURATION_MS.toLong() + ) - // propagate size changes to legacy panel controller and animate - // transitions - view.doOnLayout { - val width = view.measuredWidth - val height = view.measuredHeight - - when { - size.isSmall -> { - iconHolderView.alpha = 1f - val bottomInset = - windowManager.maximumWindowMetrics.windowInsets - .getInsets(WindowInsets.Type.navigationBars()) - .bottom - iconHolderView.y = - if (view.isLandscape()) { - (view.height - - iconHolderView.height - - bottomInset) / 2f - } else { - view.height - - iconHolderView.height - - iconPadding - - bottomInset - } - val newHeight = - iconHolderView.height + (2 * iconPadding.toInt()) - - iconHolderView.paddingTop - - iconHolderView.paddingBottom - panelViewController.updateForContentDimensions( - width, - newHeight + bottomInset, - 0, /* animateDurationMs */ - ) - } - size.isMedium && currentSize.isSmall -> { - val duration = ANIMATE_SMALL_TO_MEDIUM_DURATION_MS - panelViewController.updateForContentDimensions( - width, - height, - duration, - ) - startMonitoredAnimation( - listOf( - iconHolderView.asVerticalAnimator( - duration = duration.toLong(), - toY = - iconHolderOriginalY - - viewsToHideWhenSmall - .filter { it.isGone } - .sumOf { it.height }, - ), - viewsToFadeInOnSizeChange.asFadeInAnimator( - duration = duration.toLong(), - delay = duration.toLong(), - ), - ) - ) - } - size.isMedium && currentSize.isNullOrNotSmall -> { - panelViewController.updateForContentDimensions( - width, - height, - 0, /* animateDurationMs */ - ) - } - size.isLarge -> { - val duration = ANIMATE_MEDIUM_TO_LARGE_DURATION_MS - panelViewController.setUseFullScreen(true) - panelViewController.updateForContentDimensions( - panelViewController.containerWidth, - panelViewController.containerHeight, - duration, - ) - - startMonitoredAnimation( - listOf( - view.asVerticalAnimator( - duration.toLong() * 2 / 3, - toY = view.y - fullSizeYOffset - ), - listOf(view) - .asFadeInAnimator( - duration = duration.toLong() / 2, - delay = duration.toLong(), - ), - ) - ) - // TODO(b/251476085): clean up (copied from legacy) - if (view.isAttachedToWindow) { - val parent = view.parent as? ViewGroup - parent?.removeView(view) - } - } + TransitionManager.beginDelayedTransition(view, autoTransition) + + if (position.isLeft) { + flipConstraintSet.applyTo(view) + } else { + mediumConstraintSet.applyTo(view) + } + } + size.isMedium -> { + if (position.isLeft) { + flipConstraintSet.applyTo(view) + } else { + mediumConstraintSet.applyTo(view) } + } + size.isLarge && currentSize.isMedium -> { + val autoTransition = AutoTransition() + autoTransition.setDuration( + ANIMATE_MEDIUM_TO_LARGE_DURATION_MS.toLong() + ) - currentSize = size - view.visibility = View.VISIBLE - viewModel.setIsIconViewLoaded(false) - notifyAccessibilityChanged() + TransitionManager.beginDelayedTransition(view, autoTransition) + largeConstraintSet.applyTo(view) } } + + currentSize = size + currentPosition = position + notifyAccessibilityChanged() + + panelView.invalidateOutline() + view.invalidate() + view.requestLayout() } } } @@ -646,17 +453,6 @@ object BiometricViewSizeBinder { } } -private fun View.isLandscape(): Boolean { - val r = context.display.rotation - return if ( - context.resources.getBoolean(com.android.internal.R.bool.config_reverseDefaultRotation) - ) { - r == Surface.ROTATION_0 || r == Surface.ROTATION_180 - } else { - r == Surface.ROTATION_90 || r == Surface.ROTATION_270 - } -} - private fun View.showContentOrHide(forceHide: Boolean = false) { val isTextViewWithBlankText = this is TextView && this.text.isBlank() val isImageViewWithoutImage = this is ImageView && this.drawable == null @@ -667,26 +463,3 @@ private fun View.showContentOrHide(forceHide: Boolean = false) { View.VISIBLE } } - -private fun View.asVerticalAnimator( - duration: Long, - toY: Float, - fromY: Float = this.y -): ValueAnimator { - val animator = ValueAnimator.ofFloat(fromY, toY) - animator.duration = duration - animator.addUpdateListener { y = it.animatedValue as Float } - return animator -} - -private fun List<View>.asFadeInAnimator(duration: Long, delay: Long): ValueAnimator { - forEach { it.alpha = 0f } - val animator = ValueAnimator.ofFloat(0f, 1f) - animator.duration = duration - animator.startDelay = delay - animator.addUpdateListener { - val alpha = it.animatedValue as Float - forEach { view -> view.alpha = alpha } - } - return animator -} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt index 18e2a56e5e78..49f4b05e2cd9 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt @@ -10,7 +10,6 @@ import android.widget.TextView import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.app.animation.Interpolators -import com.android.systemui.Flags.constraintBp import com.android.systemui.biometrics.AuthPanelController import com.android.systemui.biometrics.ui.CredentialPasswordView import com.android.systemui.biometrics.ui.CredentialPatternView @@ -82,7 +81,7 @@ object CredentialViewBinder { subtitleView.textOrHide = header.subtitle descriptionView.textOrHide = header.description - if (Flags.customBiometricPrompt() && constraintBp()) { + if (Flags.customBiometricPrompt()) { BiometricCustomizedViewBinder.bind( customizedViewContainer, header.contentView, diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt index 9e4aaaa44085..eab3b26e9b68 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt @@ -22,9 +22,7 @@ import android.util.Log import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.airbnb.lottie.LottieAnimationView -import com.airbnb.lottie.LottieOnCompositionLoadedListener import com.android.settingslib.widget.LottieColorUtils -import com.android.systemui.Flags.constraintBp import com.android.systemui.biometrics.ui.viewmodel.PromptIconViewModel import com.android.systemui.biometrics.ui.viewmodel.PromptIconViewModel.AuthType import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel @@ -44,55 +42,12 @@ object PromptIconViewBinder { @JvmStatic fun bind( iconView: LottieAnimationView, - iconViewLayoutParamSizeOverride: Pair<Int, Int>?, promptViewModel: PromptViewModel ) { val viewModel = promptViewModel.iconViewModel iconView.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.onConfigurationChanged(iconView.context.resources.configuration) - if (iconViewLayoutParamSizeOverride != null) { - iconView.layoutParams.width = iconViewLayoutParamSizeOverride.first - iconView.layoutParams.height = iconViewLayoutParamSizeOverride.second - } - - if (!constraintBp()) { - launch { - var lottieOnCompositionLoadedListener: LottieOnCompositionLoadedListener? = - null - - viewModel.iconSize.collect { iconSize -> - /** - * When we bind the BiometricPrompt View and ViewModel in - * [BiometricViewBinder], the view is set invisible and - * [isIconViewLoaded] is set to false. We configure the iconView with a - * LottieOnCompositionLoadedListener that sets [isIconViewLoaded] to - * true, in order to wait for the iconView to load before determining - * the prompt size, and prevent any prompt resizing from being visible - * to the user. - * - * TODO(b/288175072): May be able to remove this once constraint layout - * is unflagged - */ - if (lottieOnCompositionLoadedListener != null) { - iconView.removeLottieOnCompositionLoadedListener( - lottieOnCompositionLoadedListener!! - ) - } - lottieOnCompositionLoadedListener = LottieOnCompositionLoadedListener { - promptViewModel.setIsIconViewLoaded(true) - } - iconView.addLottieOnCompositionLoadedListener( - lottieOnCompositionLoadedListener!! - ) - - if (iconViewLayoutParamSizeOverride == null) { - iconView.layoutParams.width = iconSize.first - iconView.layoutParams.height = iconSize.second - } - } - } - } launch { viewModel.iconAsset @@ -154,7 +109,7 @@ fun LottieAnimationView.updateAsset( setAnimation(asset) if (animatingFromSfpsAuthenticating(asset)) { // Skipping to error / success / unlock segment of animation - setMinFrame(151) + setMinFrame(158) } else { frame = 0 } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt index 31af126eb3f0..761c3da77a4d 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt @@ -6,7 +6,6 @@ import android.hardware.biometrics.Flags.customBiometricPrompt import android.hardware.biometrics.PromptContentView import android.text.InputType import com.android.internal.widget.LockPatternView -import com.android.systemui.Flags.constraintBp import com.android.systemui.biometrics.Utils import com.android.systemui.biometrics.domain.interactor.CredentialStatus import com.android.systemui.biometrics.domain.interactor.PromptCredentialInteractor @@ -39,7 +38,7 @@ constructor( credentialInteractor.prompt.filterIsInstance<BiometricPromptRequest.Credential>(), credentialInteractor.showTitleOnly ) { request, showTitleOnly -> - val flagEnabled = customBiometricPrompt() && constraintBp() + val flagEnabled = customBiometricPrompt() val showTitleOnlyForCredential = showTitleOnly && flagEnabled BiometricPromptHeaderViewModelImpl( request, @@ -82,8 +81,8 @@ constructor( val errorMessage: Flow<String> = combine(credentialInteractor.verificationError, credentialInteractor.prompt) { error, p -> when (error) { - is CredentialStatus.Fail.Error -> error.error - ?: applicationContext.asBadCredentialErrorMessage(p) + is CredentialStatus.Fail.Error -> + error.error ?: applicationContext.asBadCredentialErrorMessage(p) is CredentialStatus.Fail.Throttled -> error.error null -> "" } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt index 214420d45560..25d43d972fe2 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt @@ -36,7 +36,6 @@ import android.util.RotationUtils import android.view.HapticFeedbackConstants import android.view.MotionEvent import com.android.launcher3.icons.IconProvider -import com.android.systemui.Flags.constraintBp import com.android.systemui.biometrics.UdfpsUtils import com.android.systemui.biometrics.Utils import com.android.systemui.biometrics.Utils.isSystem @@ -470,7 +469,7 @@ constructor( promptSelectorInteractor.prompt .map { when { - !(customBiometricPrompt() && constraintBp()) || it == null -> Pair(null, "") + !(customBiometricPrompt()) || it == null -> Pair(null, "") else -> context.getUserBadgedLogoInfo(it, iconProvider, activityTaskManager) } } @@ -487,7 +486,7 @@ constructor( /** Custom content view for the prompt. */ val contentView: Flow<PromptContentView?> = promptSelectorInteractor.prompt - .map { if (customBiometricPrompt() && constraintBp()) it?.contentView else null } + .map { if (customBiometricPrompt()) it?.contentView else null } .distinctUntilChanged() private val originalDescription = @@ -1045,7 +1044,7 @@ private fun BiometricPromptRequest.Biometric.getApplicationInfo( val packageName = when { componentNameForLogo != null -> componentNameForLogo.packageName - // TODO(b/339532378): We should check whether |allowBackgroundAuthentication| should be + // TODO(b/353597496): We should check whether |allowBackgroundAuthentication| should be // removed. // This is being consistent with the check in [AuthController.showDialog()]. allowBackgroundAuthentication || isSystem(context, opPackageName) -> opPackageName diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt index 19ea007fc60f..c2a4ee36dec6 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt @@ -28,7 +28,6 @@ import android.view.WindowManager import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY import com.airbnb.lottie.model.KeyPath -import com.android.systemui.Flags.constraintBp import com.android.systemui.biometrics.Utils import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor import com.android.systemui.biometrics.domain.interactor.SideFpsSensorInteractor @@ -152,21 +151,6 @@ constructor( -> val topLeft = Point(sensorLocation.left, sensorLocation.top) - if (!constraintBp()) { - if (sensorLocation.isSensorVerticalInDefaultOrientation) { - if (displayRotation == DisplayRotation.ROTATION_0) { - topLeft.x -= bounds!!.width() - } else if (displayRotation == DisplayRotation.ROTATION_270) { - topLeft.y -= bounds!!.height() - } - } else { - if (displayRotation == DisplayRotation.ROTATION_180) { - topLeft.y -= bounds!!.height() - } else if (displayRotation == DisplayRotation.ROTATION_270) { - topLeft.x -= bounds!!.width() - } - } - } defaultOverlayViewParams.apply { x = topLeft.x y = topLeft.y diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt index e44f0543fc87..817f2d7f6f70 100644 --- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt @@ -34,7 +34,9 @@ import kotlinx.coroutines.flow.stateIn internal sealed class AudioSharingButtonState { object Gone : AudioSharingButtonState() - data class Visible(@StringRes val resId: Int) : AudioSharingButtonState() + + data class Visible(@StringRes val resId: Int, val isActive: Boolean) : + AudioSharingButtonState() } /** Holds business logic for the audio sharing state. */ @@ -73,7 +75,8 @@ constructor( // Show sharing audio when broadcasting BluetoothUtils.isBroadcasting(localBluetoothManager) -> AudioSharingButtonState.Visible( - R.string.quick_settings_bluetooth_audio_sharing_button_sharing + R.string.quick_settings_bluetooth_audio_sharing_button_sharing, + isActive = true ) // When not broadcasting, don't show button if there's connected source in any device deviceItem.any { @@ -85,7 +88,8 @@ constructor( // Show audio sharing when there's a connected LE audio device deviceItem.any { BluetoothUtils.isActiveLeAudioDevice(it.cachedBluetoothDevice) } -> AudioSharingButtonState.Visible( - R.string.quick_settings_bluetooth_audio_sharing_button + R.string.quick_settings_bluetooth_audio_sharing_button, + isActive = false ) else -> AudioSharingButtonState.Gone } diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt index f5b9a050f33e..2808dbe669ab 100644 --- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt @@ -212,11 +212,13 @@ internal constructor( internal fun onAudioSharingButtonUpdated( dialog: SystemUIDialog, visibility: Int, - label: String? + label: String?, + isActive: Boolean ) { getAudioSharingButtonView(dialog).apply { this.visibility = visibility label?.let { text = it } + this.isActivated = isActive } } @@ -370,6 +372,7 @@ internal constructor( private val iconView = view.requireViewById<ImageView>(R.id.bluetooth_device_icon) private val iconGear = view.requireViewById<ImageView>(R.id.gear_icon_image) private val gearView = view.requireViewById<View>(R.id.gear_icon) + private val divider = view.requireViewById<View>(R.id.divider) internal fun bind( item: DeviceItem, @@ -402,6 +405,8 @@ internal constructor( iconGear.apply { drawable?.let { it.mutate()?.setTint(tintColor) } } + divider.setBackgroundColor(tintColor) + // update text styles nameView.setTextAppearance( if (item.isActive) R.style.BluetoothTileDialog_DeviceName_Active diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt index eaddc42dcd5a..985b1588807b 100644 --- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt @@ -104,8 +104,7 @@ constructor( ) controller?.let { dialogTransitionAnimator.show(dialog, it, animateBackgroundBoundsChange = true) - } - ?: dialog.show() + } ?: dialog.show() updateDeviceItemJob = launch { deviceItemInteractor.updateDeviceItems(context, DeviceFetchTrigger.FIRST_LOAD) @@ -149,14 +148,23 @@ constructor( if (BluetoothUtils.isAudioSharingEnabled()) { audioSharingInteractor.audioSharingButtonStateUpdate .onEach { - if (it is AudioSharingButtonState.Visible) { - dialogDelegate.onAudioSharingButtonUpdated( - dialog, - VISIBLE, - context.getString(it.resId) - ) - } else { - dialogDelegate.onAudioSharingButtonUpdated(dialog, GONE, null) + when (it) { + is AudioSharingButtonState.Visible -> { + dialogDelegate.onAudioSharingButtonUpdated( + dialog, + VISIBLE, + context.getString(it.resId), + it.isActive + ) + } + is AudioSharingButtonState.Gone -> { + dialogDelegate.onAudioSharingButtonUpdated( + dialog, + GONE, + label = null, + isActive = false + ) + } } } .launchIn(this) @@ -305,6 +313,7 @@ constructor( companion object { private const val INTERACTION_JANK_TAG = "bluetooth_tile_dialog" private const val CONTENT_HEIGHT_PREF_KEY = Prefs.Key.BLUETOOTH_TILE_DIALOG_CONTENT_HEIGHT + private fun getSubtitleResId(isBluetoothEnabled: Boolean) = if (isBluetoothEnabled) R.string.quick_settings_bluetooth_tile_subtitle else R.string.bt_is_off @@ -336,7 +345,10 @@ constructor( interface BluetoothTileDialogCallback { fun onDeviceItemGearClicked(deviceItem: DeviceItem, view: View) + fun onSeeAllClicked(view: View) + fun onPairNewDeviceClicked(view: View) + fun onAudioSharingButtonClicked(view: View) } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt index a4016005a756..8b9c0a9a0bdd 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt @@ -145,8 +145,7 @@ class PatternBouncerViewModel( ) } } - } - ?: emptyList() + } ?: emptyList() _selectedDots.value = linkedSetOf<PatternDotViewModel>().apply { diff --git a/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt index 718ef51aa161..7d518f4f7e78 100644 --- a/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt +++ b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt @@ -23,19 +23,20 @@ import android.os.SystemProperties import android.view.Surface import android.view.View import android.view.WindowManager +import com.android.app.viewcapture.ViewCaptureAwareWindowManager import com.android.internal.annotations.VisibleForTesting import com.android.internal.logging.UiEvent import com.android.internal.logging.UiEventLogger import com.android.settingslib.Utils -import com.android.systemui.res.R import com.android.systemui.dagger.SysUISingleton import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags -import com.android.systemui.surfaceeffects.ripple.RippleView +import com.android.systemui.res.R import com.android.systemui.statusbar.commandline.Command import com.android.systemui.statusbar.commandline.CommandRegistry import com.android.systemui.statusbar.policy.BatteryController import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.surfaceeffects.ripple.RippleView import com.android.systemui.util.time.SystemClock import java.io.PrintWriter import javax.inject.Inject @@ -57,6 +58,7 @@ class WiredChargingRippleController @Inject constructor( featureFlags: FeatureFlags, private val context: Context, private val windowManager: WindowManager, + private val viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager, private val systemClock: SystemClock, private val uiEventLogger: UiEventLogger ) { @@ -161,12 +163,12 @@ class WiredChargingRippleController @Inject constructor( override fun onViewAttachedToWindow(view: View) { layoutRipple() rippleView.startRipple(Runnable { - windowManager.removeView(rippleView) + viewCaptureAwareWindowManager.removeView(rippleView) }) rippleView.removeOnAttachStateChangeListener(this) } }) - windowManager.addView(rippleView, windowLayoutParams) + viewCaptureAwareWindowManager.addView(rippleView, windowLayoutParams) uiEventLogger.log(WiredChargingRippleEvent.CHARGING_RIPPLE_PLAYED) } diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt index 6aaaf3d4ce9a..fbeb71514db4 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt @@ -184,7 +184,11 @@ constructor( keyguardTransitionInteractor.startedKeyguardTransitionStep .filter { step -> step.to == KeyguardState.OCCLUDED } .combine(isCommunalAvailable, ::Pair) - .map { (step, available) -> available && step.from == KeyguardState.GLANCEABLE_HUB } + .map { (step, available) -> + available && + (step.from == KeyguardState.GLANCEABLE_HUB || + step.from == KeyguardState.DREAMING) + } .flowOn(bgDispatcher) .stateIn( scope = applicationScope, diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalAppWidgetHostViewBinder.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalAppWidgetHostViewBinder.kt new file mode 100644 index 000000000000..7a0567190bd0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalAppWidgetHostViewBinder.kt @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.ui.binder + +import android.content.Context +import android.util.SizeF +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import com.android.app.tracing.coroutines.launch +import com.android.systemui.communal.domain.model.CommunalContentModel +import com.android.systemui.communal.util.WidgetViewFactory +import com.android.systemui.util.kotlin.DisposableHandles +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.DisposableHandle + +object CommunalAppWidgetHostViewBinder { + private const val TAG = "CommunalAppWidgetHostViewBinder" + + fun bind( + context: Context, + applicationScope: CoroutineScope, + container: FrameLayout, + model: CommunalContentModel.WidgetContent.Widget, + size: SizeF, + factory: WidgetViewFactory, + ): DisposableHandle { + val disposables = DisposableHandles() + + val loadingJob = + applicationScope.launch("$TAG#createWidgetView") { + val widget = factory.createWidget(context, model, size) + // TODO(b/358662507): Remove this workaround + (container.parent as? ViewGroup)?.let { parent -> + val index = parent.indexOfChild(container) + parent.removeView(container) + parent.addView(container, index) + } + container.setView(widget) + } + + disposables += DisposableHandle { loadingJob.cancel() } + disposables += DisposableHandle { container.removeAllViews() } + + return disposables + } +} + +private fun ViewGroup.setView(view: View) { + if (view.parent == this) { + return + } + (view.parent as? ViewGroup)?.removeView(view) + addView(view) +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/CommunalAppWidgetSection.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/CommunalAppWidgetSection.kt new file mode 100644 index 000000000000..56b769e7bc13 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/CommunalAppWidgetSection.kt @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.ui.view.layout.sections + +import android.util.SizeF +import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_AUTO +import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS +import android.widget.FrameLayout +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.viewinterop.AndroidView +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.android.systemui.communal.domain.model.CommunalContentModel +import com.android.systemui.communal.ui.binder.CommunalAppWidgetHostViewBinder +import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel +import com.android.systemui.communal.util.WidgetViewFactory +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.res.R +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.DisposableHandle + +class CommunalAppWidgetSection +@Inject +constructor( + @Application private val applicationScope: CoroutineScope, + private val factory: WidgetViewFactory, +) { + + private companion object { + val DISPOSABLE_TAG = R.id.communal_widget_disposable_tag + } + + @Composable + fun Widget( + viewModel: BaseCommunalViewModel, + model: CommunalContentModel.WidgetContent.Widget, + size: SizeF, + modifier: Modifier = Modifier, + ) { + val isFocusable by viewModel.isFocusable.collectAsStateWithLifecycle(initialValue = false) + + AndroidView( + factory = { context -> + FrameLayout(context).apply { + layoutParams = + FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.MATCH_PARENT, + ) + + // Need to attach the disposable handle to the view here instead of storing + // the state in the composable in order to properly support lazy lists. In a + // lazy list, when the composable is no longer in view - it will exit + // composition and any state inside the composable will be lost. However, + // the View instance will be re-used. Therefore we can store data on the view + // in order to preserve it. + setTag( + DISPOSABLE_TAG, + CommunalAppWidgetHostViewBinder.bind( + context = context, + container = this, + model = model, + size = size, + factory = factory, + applicationScope = applicationScope, + ) + ) + + accessibilityDelegate = viewModel.widgetAccessibilityDelegate + } + }, + update = { container -> + container.importantForAccessibility = + if (isFocusable) { + IMPORTANT_FOR_ACCESSIBILITY_AUTO + } else { + IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS + } + }, + onRelease = { view -> + val disposable = (view.getTag(DISPOSABLE_TAG) as? DisposableHandle) + disposable?.dispose() + }, + modifier = modifier, + // For reusing composition in lazy lists. + onReset = {}, + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/util/WidgetViewFactory.kt b/packages/SystemUI/src/com/android/systemui/communal/util/WidgetViewFactory.kt new file mode 100644 index 000000000000..0e39a99765bd --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/util/WidgetViewFactory.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.communal.util + +import android.content.Context +import android.os.Bundle +import android.util.SizeF +import com.android.app.tracing.coroutines.withContext +import com.android.systemui.communal.domain.model.CommunalContentModel +import com.android.systemui.communal.widgets.CommunalAppWidgetHost +import com.android.systemui.communal.widgets.CommunalAppWidgetHostView +import com.android.systemui.dagger.qualifiers.UiBackground +import javax.inject.Inject +import kotlin.coroutines.CoroutineContext + +/** Factory for creating [CommunalAppWidgetHostView] in a background thread. */ +class WidgetViewFactory +@Inject +constructor( + @UiBackground private val uiBgContext: CoroutineContext, + private val appWidgetHost: CommunalAppWidgetHost, +) { + suspend fun createWidget( + context: Context, + model: CommunalContentModel.WidgetContent.Widget, + size: SizeF, + ): CommunalAppWidgetHostView = + withContext("$TAG#createWidget", uiBgContext) { + appWidgetHost + .createViewForCommunal(context, model.appWidgetId, model.providerInfo) + .apply { + updateAppWidgetSize( + /* newOptions = */ Bundle(), + /* minWidth = */ size.width.toInt(), + /* minHeight = */ size.height.toInt(), + /* maxWidth = */ size.width.toInt(), + /* maxHeight = */ size.height.toInt(), + /* ignorePadding = */ true, + ) + } + } + + private companion object { + const val TAG = "WidgetViewFactory" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt index 2bcbc9aa74ac..668fef6130bb 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt @@ -42,6 +42,7 @@ import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.communal.shared.model.CommunalTransitionKeys import com.android.systemui.communal.shared.model.EditModeState import com.android.systemui.communal.ui.compose.CommunalHub +import com.android.systemui.communal.ui.view.layout.sections.CommunalAppWidgetSection import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel import com.android.systemui.communal.util.WidgetPickerIntentUtils.getWidgetExtraFromIntent import com.android.systemui.keyguard.shared.model.KeyguardState @@ -60,6 +61,7 @@ constructor( private var windowManagerService: IWindowManager? = null, private val uiEventLogger: UiEventLogger, private val widgetConfiguratorFactory: WidgetConfigurationController.Factory, + private val widgetSection: CommunalAppWidgetSection, @CommunalLog logBuffer: LogBuffer, ) : ComponentActivity() { companion object { @@ -204,6 +206,7 @@ constructor( onOpenWidgetPicker = ::onOpenWidgetPicker, widgetConfigurator = widgetConfigurator, onEditDone = ::onEditDone, + widgetSection = widgetSection, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java index 32731117a8f6..79f4568d73be 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java @@ -33,6 +33,7 @@ import com.android.systemui.display.ui.viewmodel.ConnectingDisplayViewModel; import com.android.systemui.dock.DockManager; import com.android.systemui.dock.DockManagerImpl; import com.android.systemui.doze.DozeHost; +import com.android.systemui.inputdevice.tutorial.KeyboardTouchpadTutorialModule; import com.android.systemui.keyboard.shortcut.ShortcutHelperModule; import com.android.systemui.keyguard.ui.composable.blueprint.DefaultBlueprintModule; import com.android.systemui.keyguard.ui.view.layout.blueprints.KeyguardBlueprintModule; @@ -77,7 +78,7 @@ import com.android.systemui.statusbar.policy.IndividualSensorPrivacyControllerIm import com.android.systemui.statusbar.policy.SensorPrivacyController; import com.android.systemui.statusbar.policy.SensorPrivacyControllerImpl; import com.android.systemui.toast.ToastModule; -import com.android.systemui.touchpad.tutorial.TouchpadKeyboardTutorialModule; +import com.android.systemui.touchpad.tutorial.TouchpadTutorialModule; import com.android.systemui.unfold.SysUIUnfoldStartableModule; import com.android.systemui.unfold.UnfoldTransitionModule; import com.android.systemui.util.kotlin.SysUICoroutinesModule; @@ -122,6 +123,7 @@ import javax.inject.Named; KeyboardShortcutsModule.class, KeyguardBlueprintModule.class, KeyguardSectionsModule.class, + KeyboardTouchpadTutorialModule.class, MediaModule.class, MediaMuteAwaitConnectionCli.StartableModule.class, MultiUserUtilsModule.class, @@ -142,7 +144,7 @@ import javax.inject.Named; SysUIUnfoldStartableModule.class, UnfoldTransitionModule.Startables.class, ToastModule.class, - TouchpadKeyboardTutorialModule.class, + TouchpadTutorialModule.class, VolumeModule.class, WallpaperModule.class, ShortcutHelperModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt index b0f2c18db565..cbea87676d3a 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt @@ -258,13 +258,6 @@ abstract class SystemUICoreStartableModule { @Binds @IntoMap - @ClassKey(KeyboardTouchpadTutorialCoreStartable::class) - abstract fun bindKeyboardTouchpadTutorialCoreStartable( - listener: KeyboardTouchpadTutorialCoreStartable - ): CoreStartable - - @Binds - @IntoMap @ClassKey(PhysicalKeyboardCoreStartable::class) abstract fun bindKeyboardCoreStartable(listener: PhysicalKeyboardCoreStartable): CoreStartable diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt index 9b95ac4797c0..39f4e31fa3cd 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt @@ -119,21 +119,24 @@ constructor( * Note: `true` doesn't mean the lockscreen is visible. It may be occluded or covered by other * UI. */ - val canSwipeToEnter: StateFlow<Boolean?> = + val canSwipeToEnter: StateFlow<Boolean?> by lazy { combine( authenticationInteractor.authenticationMethod.map { it == AuthenticationMethodModel.None }, isLockscreenEnabled, deviceUnlockedInteractor.deviceUnlockStatus, - isDeviceEntered - ) { isNoneAuthMethod, isLockscreenEnabled, deviceUnlockStatus, isDeviceEntered -> - val isSwipeAuthMethod = isNoneAuthMethod && isLockscreenEnabled - (isSwipeAuthMethod || - (deviceUnlockStatus.isUnlocked && - deviceUnlockStatus.deviceUnlockSource?.dismissesLockscreen == false)) && - !isDeviceEntered - } + isDeviceEntered) { + isNoneAuthMethod, + isLockscreenEnabled, + deviceUnlockStatus, + isDeviceEntered -> + val isSwipeAuthMethod = isNoneAuthMethod && isLockscreenEnabled + (isSwipeAuthMethod || + (deviceUnlockStatus.isUnlocked && + deviceUnlockStatus.deviceUnlockSource?.dismissesLockscreen == false)) && + !isDeviceEntered + } .stateIn( scope = applicationScope, started = SharingStarted.Eagerly, @@ -142,6 +145,7 @@ constructor( // from upstream data sources. initialValue = null, ) + } /** * Attempt to enter the device and dismiss the lockscreen. If authentication is required to diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeBrightnessHostForwarder.java b/packages/SystemUI/src/com/android/systemui/doze/DozeBrightnessHostForwarder.java index 0b336143c34a..6fd4d33806a3 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeBrightnessHostForwarder.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeBrightnessHostForwarder.java @@ -16,8 +16,6 @@ package com.android.systemui.doze; -import java.util.concurrent.Executor; - /** * Forwards the currently used brightness to {@link DozeHost}. */ @@ -25,9 +23,8 @@ public class DozeBrightnessHostForwarder extends DozeMachine.Service.Delegate { private final DozeHost mHost; - public DozeBrightnessHostForwarder(DozeMachine.Service wrappedService, DozeHost host, - Executor bgExecutor) { - super(wrappedService, bgExecutor); + public DozeBrightnessHostForwarder(DozeMachine.Service wrappedService, DozeHost host) { + super(wrappedService); mHost = host; } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java index 5bfcc975d02d..cdcb03e59e8a 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java @@ -253,9 +253,11 @@ public class DozeLog implements Dumpable { /** * Appends display state changed event to the logs * @param displayState new DozeMachine state + * @param afterRequest whether the request has successfully been sent else false for it's + * about to be requested */ - public void traceDisplayState(int displayState) { - mLogger.logDisplayStateChanged(displayState); + public void traceDisplayState(int displayState, boolean afterRequest) { + mLogger.logDisplayStateChanged(displayState, afterRequest); } /** @@ -402,18 +404,22 @@ public class DozeLog implements Dumpable { /** * Appends new AOD screen brightness to logs * @param brightness display brightness setting between 1 and 255 + * @param afterRequest whether the request has successfully been sent else false for it's + * about to be requested */ - public void traceDozeScreenBrightness(int brightness) { - mLogger.logDozeScreenBrightness(brightness); + public void traceDozeScreenBrightness(int brightness, boolean afterRequest) { + mLogger.logDozeScreenBrightness(brightness, afterRequest); } /** * Appends new AOD screen brightness to logs * @param brightness display brightness setting between {@link PowerManager#BRIGHTNESS_MIN} and * {@link PowerManager#BRIGHTNESS_MAX} + * @param afterRequest whether the request has successfully been sent else false for it's + * about to be requested */ - public void traceDozeScreenBrightnessFloat(float brightness) { - mLogger.logDozeScreenBrightnessFloat(brightness); + public void traceDozeScreenBrightnessFloat(float brightness, boolean afterRequest) { + mLogger.logDozeScreenBrightnessFloat(brightness, afterRequest); } /** diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt index a31dbec242fe..71287314102c 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt @@ -32,23 +32,18 @@ import java.util.Locale import javax.inject.Inject /** Interface for logging messages to the [DozeLog]. */ -class DozeLogger @Inject constructor( - @DozeLog private val buffer: LogBuffer -) { +class DozeLogger @Inject constructor(@DozeLog private val buffer: LogBuffer) { fun logPickupWakeup(isWithinVibrationThreshold: Boolean) { - buffer.log(TAG, DEBUG, { - bool1 = isWithinVibrationThreshold - }, { - "PickupWakeup withinVibrationThreshold=$bool1" - }) + buffer.log( + TAG, + DEBUG, + { bool1 = isWithinVibrationThreshold }, + { "PickupWakeup withinVibrationThreshold=$bool1" } + ) } fun logPulseStart(@Reason reason: Int) { - buffer.log(TAG, INFO, { - int1 = reason - }, { - "Pulse start, reason=${reasonToString(int1)}" - }) + buffer.log(TAG, INFO, { int1 = reason }, { "Pulse start, reason=${reasonToString(int1)}" }) } fun logPulseFinish() { @@ -60,52 +55,51 @@ class DozeLogger @Inject constructor( } fun logDozing(isDozing: Boolean) { - buffer.log(TAG, INFO, { - bool1 = isDozing - }, { - "Dozing=$bool1" - }) + buffer.log(TAG, INFO, { bool1 = isDozing }, { "Dozing=$bool1" }) } fun logDozingChanged(isDozing: Boolean) { - buffer.log(TAG, INFO, { - bool1 = isDozing - }, { - "Dozing changed dozing=$bool1" - }) + buffer.log(TAG, INFO, { bool1 = isDozing }, { "Dozing changed dozing=$bool1" }) } fun logPowerSaveChanged(powerSaveActive: Boolean, nextState: DozeMachine.State) { - buffer.log(TAG, INFO, { - bool1 = powerSaveActive - str1 = nextState.name - }, { - "Power save active=$bool1 nextState=$str1" - }) + buffer.log( + TAG, + INFO, + { + bool1 = powerSaveActive + str1 = nextState.name + }, + { "Power save active=$bool1 nextState=$str1" } + ) } fun logAlwaysOnSuppressedChange(isAodSuppressed: Boolean, nextState: DozeMachine.State) { - buffer.log(TAG, INFO, { - bool1 = isAodSuppressed - str1 = nextState.name - }, { - "Always on (AOD) suppressed changed, suppressed=$bool1 nextState=$str1" - }) - } - - fun logFling( - expand: Boolean, - aboveThreshold: Boolean, - screenOnFromTouch: Boolean - ) { - buffer.log(TAG, DEBUG, { - bool1 = expand - bool2 = aboveThreshold - bool4 = screenOnFromTouch - }, { - "Fling expand=$bool1 aboveThreshold=$bool2 thresholdNeeded=$bool3 " + - "screenOnFromTouch=$bool4" - }) + buffer.log( + TAG, + INFO, + { + bool1 = isAodSuppressed + str1 = nextState.name + }, + { "Always on (AOD) suppressed changed, suppressed=$bool1 nextState=$str1" } + ) + } + + fun logFling(expand: Boolean, aboveThreshold: Boolean, screenOnFromTouch: Boolean) { + buffer.log( + TAG, + DEBUG, + { + bool1 = expand + bool2 = aboveThreshold + bool4 = screenOnFromTouch + }, + { + "Fling expand=$bool1 aboveThreshold=$bool2 thresholdNeeded=$bool3 " + + "screenOnFromTouch=$bool4" + } + ) } fun logEmergencyCall() { @@ -113,280 +107,314 @@ class DozeLogger @Inject constructor( } fun logKeyguardBouncerChanged(isShowing: Boolean) { - buffer.log(TAG, INFO, { - bool1 = isShowing - }, { - "Keyguard bouncer changed, showing=$bool1" - }) + buffer.log(TAG, INFO, { bool1 = isShowing }, { "Keyguard bouncer changed, showing=$bool1" }) } fun logScreenOn(isPulsing: Boolean) { - buffer.log(TAG, INFO, { - bool1 = isPulsing - }, { - "Screen on, pulsing=$bool1" - }) + buffer.log(TAG, INFO, { bool1 = isPulsing }, { "Screen on, pulsing=$bool1" }) } fun logScreenOff(why: Int) { - buffer.log(TAG, INFO, { - int1 = why - }, { - "Screen off, why=$int1" - }) + buffer.log(TAG, INFO, { int1 = why }, { "Screen off, why=$int1" }) } fun logMissedTick(delay: String) { - buffer.log(TAG, ERROR, { - str1 = delay - }, { - "Missed AOD time tick by $str1" - }) + buffer.log(TAG, ERROR, { str1 = delay }, { "Missed AOD time tick by $str1" }) } fun logTimeTickScheduled(whenAt: Long, triggerAt: Long) { - buffer.log(TAG, DEBUG, { - long1 = whenAt - long2 = triggerAt - }, { - "Time tick scheduledAt=${DATE_FORMAT.format(Date(long1))} " + - "triggerAt=${DATE_FORMAT.format(Date(long2))}" - }) + buffer.log( + TAG, + DEBUG, + { + long1 = whenAt + long2 = triggerAt + }, + { + "Time tick scheduledAt=${DATE_FORMAT.format(Date(long1))} " + + "triggerAt=${DATE_FORMAT.format(Date(long2))}" + } + ) } fun logKeyguardVisibilityChange(isVisible: Boolean) { - buffer.log(TAG, INFO, { - bool1 = isVisible - }, { - "Keyguard visibility change, isVisible=$bool1" - }) + buffer.log( + TAG, + INFO, + { bool1 = isVisible }, + { "Keyguard visibility change, isVisible=$bool1" } + ) } fun logPendingUnscheduleTimeTick(isPending: Boolean, isTimeTickScheduled: Boolean) { - buffer.log(TAG, INFO, { - bool1 = isPending - bool2 = isTimeTickScheduled - }, { - "Pending unschedule time tick, isPending=$bool1, isTimeTickScheduled:$bool2" - }) + buffer.log( + TAG, + INFO, + { + bool1 = isPending + bool2 = isTimeTickScheduled + }, + { "Pending unschedule time tick, isPending=$bool1, isTimeTickScheduled:$bool2" } + ) } fun logDozeStateChanged(state: DozeMachine.State) { - buffer.log(TAG, INFO, { - str1 = state.name - }, { - "Doze state changed to $str1" - }) + buffer.log(TAG, INFO, { str1 = state.name }, { "Doze state changed to $str1" }) } fun logStateChangedSent(state: DozeMachine.State) { - buffer.log(TAG, INFO, { - str1 = state.name - }, { - "Doze state sent to all DozeMachineParts stateSent=$str1" - }) + buffer.log( + TAG, + INFO, + { str1 = state.name }, + { "Doze state sent to all DozeMachineParts stateSent=$str1" } + ) } fun logDisplayStateDelayedByUdfps(delayedDisplayState: Int) { - buffer.log(TAG, INFO, { - str1 = Display.stateToString(delayedDisplayState) - }, { - "Delaying display state change to: $str1 due to UDFPS activity" - }) - } - - fun logDisplayStateChanged(displayState: Int) { - buffer.log(TAG, INFO, { - str1 = Display.stateToString(displayState) - }, { - "Display state changed to $str1" - }) + buffer.log( + TAG, + INFO, + { str1 = Display.stateToString(delayedDisplayState) }, + { "Delaying display state change to: $str1 due to UDFPS activity" } + ) + } + + fun logDisplayStateChanged(displayState: Int, afterRequest: Boolean) { + buffer.log( + TAG, + INFO, + { + str1 = Display.stateToString(displayState) + bool1 = afterRequest + }, + { "Display state ${if (bool1) "changed" else "requested"} to $str1" } + ) } fun logWakeDisplay(isAwake: Boolean, @Reason reason: Int) { - buffer.log(TAG, DEBUG, { - bool1 = isAwake - int1 = reason - }, { - "Display wakefulness changed, isAwake=$bool1, reason=${reasonToString(int1)}" - }) + buffer.log( + TAG, + DEBUG, + { + bool1 = isAwake + int1 = reason + }, + { "Display wakefulness changed, isAwake=$bool1, reason=${reasonToString(int1)}" } + ) } fun logProximityResult(isNear: Boolean, millis: Long, @Reason reason: Int) { - buffer.log(TAG, DEBUG, { - bool1 = isNear - long1 = millis - int1 = reason - }, { - "Proximity result reason=${reasonToString(int1)} near=$bool1 millis=$long1" - }) + buffer.log( + TAG, + DEBUG, + { + bool1 = isNear + long1 = millis + int1 = reason + }, + { "Proximity result reason=${reasonToString(int1)} near=$bool1 millis=$long1" } + ) } fun logPostureChanged(posture: Int, partUpdated: String) { - buffer.log(TAG, INFO, { - int1 = posture - str1 = partUpdated - }, { - "Posture changed, posture=${DevicePostureController.devicePostureToString(int1)}" + - " partUpdated=$str1" - }) + buffer.log( + TAG, + INFO, + { + int1 = posture + str1 = partUpdated + }, + { + "Posture changed, posture=${DevicePostureController.devicePostureToString(int1)}" + + " partUpdated=$str1" + } + ) } /** - * Log why a pulse was dropped and the current doze machine state. The state can be null - * if the DozeMachine is the middle of transitioning between states. + * Log why a pulse was dropped and the current doze machine state. The state can be null if the + * DozeMachine is the middle of transitioning between states. */ fun logPulseDropped(from: String, state: DozeMachine.State?) { - buffer.log(TAG, INFO, { - str1 = from - str2 = state?.name - }, { - "Pulse dropped, cannot pulse from=$str1 state=$str2" - }) + buffer.log( + TAG, + INFO, + { + str1 = from + str2 = state?.name + }, + { "Pulse dropped, cannot pulse from=$str1 state=$str2" } + ) } fun logSensorEventDropped(sensorEvent: Int, reason: String) { - buffer.log(TAG, INFO, { - int1 = sensorEvent - str1 = reason - }, { - "SensorEvent [$int1] dropped, reason=$str1" - }) + buffer.log( + TAG, + INFO, + { + int1 = sensorEvent + str1 = reason + }, + { "SensorEvent [$int1] dropped, reason=$str1" } + ) } fun logPulseEvent(pulseEvent: String, dozing: Boolean, pulseReason: String) { - buffer.log(TAG, DEBUG, { - str1 = pulseEvent - bool1 = dozing - str2 = pulseReason - }, { - "Pulse-$str1 dozing=$bool1 pulseReason=$str2" - }) + buffer.log( + TAG, + DEBUG, + { + str1 = pulseEvent + bool1 = dozing + str2 = pulseReason + }, + { "Pulse-$str1 dozing=$bool1 pulseReason=$str2" } + ) } fun logPulseDropped(reason: String) { - buffer.log(TAG, INFO, { - str1 = reason - }, { - "Pulse dropped, why=$str1" - }) + buffer.log(TAG, INFO, { str1 = reason }, { "Pulse dropped, why=$str1" }) } fun logPulseTouchDisabledByProx(disabled: Boolean) { - buffer.log(TAG, DEBUG, { - bool1 = disabled - }, { - "Pulse touch modified by prox, disabled=$bool1" - }) + buffer.log( + TAG, + DEBUG, + { bool1 = disabled }, + { "Pulse touch modified by prox, disabled=$bool1" } + ) } fun logSensorTriggered(@Reason reason: Int) { - buffer.log(TAG, DEBUG, { - int1 = reason - }, { - "Sensor triggered, type=${reasonToString(int1)}" - }) + buffer.log( + TAG, + DEBUG, + { int1 = reason }, + { "Sensor triggered, type=${reasonToString(int1)}" } + ) } fun logAlwaysOnSuppressed(state: DozeMachine.State, reason: String) { - buffer.log(TAG, INFO, { - str1 = state.name - str2 = reason - }, { - "Always-on state suppressed, suppressed state=$str1 reason=$str2" - }) + buffer.log( + TAG, + INFO, + { + str1 = state.name + str2 = reason + }, + { "Always-on state suppressed, suppressed state=$str1 reason=$str2" } + ) } fun logImmediatelyEndDoze(reason: String) { - buffer.log(TAG, INFO, { - str1 = reason - }, { - "Doze immediately ended due to $str1" - }) - } - - fun logDozeScreenBrightness(brightness: Int) { - buffer.log(TAG, INFO, { - int1 = brightness - }, { - "Doze screen brightness set (int), brightness=$int1" - }) - } - - fun logDozeScreenBrightnessFloat(brightness: Float) { - buffer.log(TAG, INFO, { - double1 = brightness.toDouble() - }, { - "Doze screen brightness set (float), brightness=$double1" - }) + buffer.log(TAG, INFO, { str1 = reason }, { "Doze immediately ended due to $str1" }) + } + + fun logDozeScreenBrightness(brightness: Int, afterRequest: Boolean) { + buffer.log( + TAG, + INFO, + { + int1 = brightness + bool1 = afterRequest + }, + { + "Doze screen brightness ${if (bool1) "set" else "requested"}" + + " (int), brightness=$int1" + } + ) + } + + fun logDozeScreenBrightnessFloat(brightness: Float, afterRequest: Boolean) { + buffer.log( + TAG, + INFO, + { + double1 = brightness.toDouble() + bool1 = afterRequest + }, + { + "Doze screen brightness ${if (bool1) "set" else "requested"}" + + " (float), brightness=$double1" + } + ) } fun logSetAodDimmingScrim(scrimOpacity: Long) { - buffer.log(TAG, INFO, { - long1 = scrimOpacity - }, { - "Doze aod dimming scrim opacity set, opacity=$long1" - }) + buffer.log( + TAG, + INFO, + { long1 = scrimOpacity }, + { "Doze aod dimming scrim opacity set, opacity=$long1" } + ) } fun logCarModeEnded() { - buffer.log(TAG, INFO, {}, { - "Doze car mode ended" - }) + buffer.log(TAG, INFO, {}, { "Doze car mode ended" }) } fun logCarModeStarted() { - buffer.log(TAG, INFO, {}, { - "Doze car mode started" - }) + buffer.log(TAG, INFO, {}, { "Doze car mode started" }) } fun logSensorRegisterAttempt(sensorInfo: String, successfulRegistration: Boolean) { - buffer.log(TAG, INFO, { - str1 = sensorInfo - bool1 = successfulRegistration - }, { - "Register sensor. Success=$bool1 sensor=$str1" - }) + buffer.log( + TAG, + INFO, + { + str1 = sensorInfo + bool1 = successfulRegistration + }, + { "Register sensor. Success=$bool1 sensor=$str1" } + ) } fun logSensorUnregisterAttempt(sensorInfo: String, successfulUnregister: Boolean) { - buffer.log(TAG, INFO, { - str1 = sensorInfo - bool1 = successfulUnregister - }, { - "Unregister sensor. Success=$bool1 sensor=$str1" - }) + buffer.log( + TAG, + INFO, + { + str1 = sensorInfo + bool1 = successfulUnregister + }, + { "Unregister sensor. Success=$bool1 sensor=$str1" } + ) } fun logSensorUnregisterAttempt( - sensorInfo: String, - successfulUnregister: Boolean, - reason: String + sensorInfo: String, + successfulUnregister: Boolean, + reason: String ) { - buffer.log(TAG, INFO, { - str1 = sensorInfo - bool1 = successfulUnregister - str2 = reason - }, { - "Unregister sensor. reason=$str2. Success=$bool1 sensor=$str1" - }) + buffer.log( + TAG, + INFO, + { + str1 = sensorInfo + bool1 = successfulUnregister + str2 = reason + }, + { "Unregister sensor. reason=$str2. Success=$bool1 sensor=$str1" } + ) } fun logSkipSensorRegistration(sensor: String) { - buffer.log(TAG, DEBUG, { - str1 = sensor - }, { - "Skipping sensor registration because its already registered. sensor=$str1" - }) + buffer.log( + TAG, + DEBUG, + { str1 = sensor }, + { "Skipping sensor registration because its already registered. sensor=$str1" } + ) } fun logSetIgnoreTouchWhilePulsing(ignoreTouchWhilePulsing: Boolean) { - buffer.log(TAG, DEBUG, { - bool1 = ignoreTouchWhilePulsing - }, { - "Prox changed while pulsing. setIgnoreTouchWhilePulsing=$bool1" - }) + buffer.log( + TAG, + DEBUG, + { bool1 = ignoreTouchWhilePulsing }, + { "Prox changed while pulsing. setIgnoreTouchWhilePulsing=$bool1" } + ) } fun log(@CompileTimeConstant msg: String) { diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java index 8198ef41bb49..e02e3fbc339b 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java @@ -39,7 +39,6 @@ import com.android.systemui.util.wakelock.WakeLock; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.concurrent.Executor; import javax.inject.Inject; @@ -232,7 +231,6 @@ public class DozeMachine { } void onScreenState(int state) { - mDozeLog.traceDisplayState(state); for (Part part : mParts) { part.onScreenState(state); } @@ -516,11 +514,9 @@ public class DozeMachine { class Delegate implements Service { private final Service mDelegate; - private final Executor mBgExecutor; - public Delegate(Service delegate, Executor bgExecutor) { + public Delegate(Service delegate) { mDelegate = delegate; - mBgExecutor = bgExecutor; } @Override @@ -540,16 +536,12 @@ public class DozeMachine { @Override public void setDozeScreenBrightness(int brightness) { - mBgExecutor.execute(() -> { - mDelegate.setDozeScreenBrightness(brightness); - }); + mDelegate.setDozeScreenBrightness(brightness); } @Override public void setDozeScreenBrightnessFloat(float brightness) { - mBgExecutor.execute(() -> { - mDelegate.setDozeScreenBrightnessFloat(brightness); - }); + mDelegate.setDozeScreenBrightnessFloat(brightness); } } } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenStatePreventingAdapter.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenStatePreventingAdapter.java index 8d4447285af2..25c2c39f3e25 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenStatePreventingAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenStatePreventingAdapter.java @@ -22,16 +22,14 @@ import androidx.annotation.VisibleForTesting; import com.android.systemui.statusbar.phone.DozeParameters; -import java.util.concurrent.Executor; - /** * Prevents usage of doze screen states on devices that don't support them. */ public class DozeScreenStatePreventingAdapter extends DozeMachine.Service.Delegate { @VisibleForTesting - DozeScreenStatePreventingAdapter(DozeMachine.Service inner, Executor bgExecutor) { - super(inner, bgExecutor); + DozeScreenStatePreventingAdapter(DozeMachine.Service inner) { + super(inner); } @Override @@ -49,8 +47,8 @@ public class DozeScreenStatePreventingAdapter extends DozeMachine.Service.Delega * return a new instance of {@link DozeScreenStatePreventingAdapter} wrapping {@code inner}. */ public static DozeMachine.Service wrapIfNeeded(DozeMachine.Service inner, - DozeParameters params, Executor bgExecutor) { - return isNeeded(params) ? new DozeScreenStatePreventingAdapter(inner, bgExecutor) : inner; + DozeParameters params) { + return isNeeded(params) ? new DozeScreenStatePreventingAdapter(inner) : inner; } private static boolean isNeeded(DozeParameters params) { diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java index ba38ab0583d4..2e372ff435c2 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java @@ -23,6 +23,7 @@ import android.os.SystemClock; import android.service.dreams.DreamService; import android.util.Log; +import com.android.systemui.dagger.qualifiers.UiBackground; import com.android.systemui.doze.dagger.DozeComponent; import com.android.systemui.plugins.DozeServicePlugin; import com.android.systemui.plugins.DozeServicePlugin.RequestDoze; @@ -31,6 +32,7 @@ import com.android.systemui.plugins.PluginManager; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.concurrent.Executor; import javax.inject.Inject; @@ -43,9 +45,14 @@ public class DozeService extends DreamService private DozeMachine mDozeMachine; private DozeServicePlugin mDozePlugin; private PluginManager mPluginManager; + private DozeLog mDozeLog; + private Executor mBgExecutor; @Inject - public DozeService(DozeComponent.Builder dozeComponentBuilder, PluginManager pluginManager) { + public DozeService(DozeComponent.Builder dozeComponentBuilder, PluginManager pluginManager, + DozeLog dozeLog, @UiBackground Executor bgExecutor) { + mDozeLog = dozeLog; + mBgExecutor = bgExecutor; mDozeComponentBuilder = dozeComponentBuilder; setDebug(DEBUG); mPluginManager = pluginManager; @@ -143,9 +150,29 @@ public class DozeService extends DreamService @Override public void setDozeScreenState(int state) { + mDozeLog.traceDisplayState(state, /* afterRequest */ false); super.setDozeScreenState(state); + mDozeLog.traceDisplayState(state, /* afterRequest */ true); if (mDozeMachine != null) { mDozeMachine.onScreenState(state); } } + + @Override + public void setDozeScreenBrightness(int brightness) { + mBgExecutor.execute(() -> { + mDozeLog.traceDozeScreenBrightness(brightness, /* afterRequest */ false); + super.setDozeScreenBrightness(brightness); + mDozeLog.traceDozeScreenBrightness(brightness, /* afterRequest */ true); + }); + } + + @Override + public void setDozeScreenBrightnessFloat(float brightness) { + mBgExecutor.execute(() -> { + mDozeLog.traceDozeScreenBrightnessFloat(brightness, /* afterRequest */ false); + super.setDozeScreenBrightnessFloat(brightness); + mDozeLog.traceDozeScreenBrightnessFloat(brightness, /* afterRequest */ true); + }); + } } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapter.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapter.java index f7773f1888b3..cfc952df83e8 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapter.java @@ -22,16 +22,14 @@ import androidx.annotation.VisibleForTesting; import com.android.systemui.statusbar.phone.DozeParameters; -import java.util.concurrent.Executor; - /** * Prevents usage of doze screen states on devices that don't support them. */ public class DozeSuspendScreenStatePreventingAdapter extends DozeMachine.Service.Delegate { @VisibleForTesting - DozeSuspendScreenStatePreventingAdapter(DozeMachine.Service inner, Executor bgExecutor) { - super(inner, bgExecutor); + DozeSuspendScreenStatePreventingAdapter(DozeMachine.Service inner) { + super(inner); } @Override @@ -47,8 +45,8 @@ public class DozeSuspendScreenStatePreventingAdapter extends DozeMachine.Service * return a new instance of {@link DozeSuspendScreenStatePreventingAdapter} wrapping {@code inner}. */ public static DozeMachine.Service wrapIfNeeded(DozeMachine.Service inner, - DozeParameters params, Executor bgExecutor) { - return isNeeded(params) ? new DozeSuspendScreenStatePreventingAdapter(inner, bgExecutor) + DozeParameters params) { + return isNeeded(params) ? new DozeSuspendScreenStatePreventingAdapter(inner) : inner; } diff --git a/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java b/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java index 8c3de4bd1928..f383a04eb3ee 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java +++ b/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java @@ -19,7 +19,6 @@ package com.android.systemui.doze.dagger; import android.content.Context; import android.hardware.Sensor; -import com.android.systemui.dagger.qualifiers.UiBackground; import com.android.systemui.doze.DozeAuthRemover; import com.android.systemui.doze.DozeBrightnessHostForwarder; import com.android.systemui.doze.DozeDockHandler; @@ -51,7 +50,6 @@ import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Optional; -import java.util.concurrent.Executor; /** Dagger module for use with {@link com.android.systemui.doze.dagger.DozeComponent}. */ @Module @@ -60,13 +58,13 @@ public abstract class DozeModule { @DozeScope @WrappedService static DozeMachine.Service providesWrappedService(DozeMachine.Service dozeMachineService, - DozeHost dozeHost, DozeParameters dozeParameters, @UiBackground Executor bgExecutor) { + DozeHost dozeHost, DozeParameters dozeParameters) { DozeMachine.Service wrappedService = dozeMachineService; - wrappedService = new DozeBrightnessHostForwarder(wrappedService, dozeHost, bgExecutor); + wrappedService = new DozeBrightnessHostForwarder(wrappedService, dozeHost); wrappedService = DozeScreenStatePreventingAdapter.wrapIfNeeded( - wrappedService, dozeParameters, bgExecutor); + wrappedService, dozeParameters); wrappedService = DozeSuspendScreenStatePreventingAdapter.wrapIfNeeded( - wrappedService, dozeParameters, bgExecutor); + wrappedService, dozeParameters); return wrappedService; } diff --git a/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt b/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt index 096556fed258..7e2c9f89fa67 100644 --- a/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt +++ b/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt @@ -26,6 +26,7 @@ import com.android.systemui.education.domain.interactor.ContextualEducationInter import com.android.systemui.education.domain.interactor.KeyboardTouchpadEduInteractor import com.android.systemui.education.domain.interactor.KeyboardTouchpadEduStatsInteractor import com.android.systemui.education.domain.interactor.KeyboardTouchpadEduStatsInteractorImpl +import com.android.systemui.education.ui.view.ContextualEduUiCoordinator import dagger.Binds import dagger.Lazy import dagger.Module @@ -74,7 +75,7 @@ interface ContextualEducationModule { implLazy.get() } else { // No-op implementation when the flag is disabled. - return NoOpContextualEducationInteractor + return NoOpCoreStartable } } @@ -91,6 +92,8 @@ interface ContextualEducationModule { } @Provides + @IntoMap + @ClassKey(KeyboardTouchpadEduInteractor::class) fun provideKeyboardTouchpadEduInteractor( implLazy: Lazy<KeyboardTouchpadEduInteractor> ): CoreStartable { @@ -98,22 +101,32 @@ interface ContextualEducationModule { implLazy.get() } else { // No-op implementation when the flag is disabled. - return NoOpKeyboardTouchpadEduInteractor + return NoOpCoreStartable } } - } - - private object NoOpKeyboardTouchpadEduStatsInteractor : KeyboardTouchpadEduStatsInteractor { - override fun incrementSignalCount(gestureType: GestureType) {} - override fun updateShortcutTriggerTime(gestureType: GestureType) {} + @Provides + @IntoMap + @ClassKey(ContextualEduUiCoordinator::class) + fun provideContextualEduUiCoordinator( + implLazy: Lazy<ContextualEduUiCoordinator> + ): CoreStartable { + return if (Flags.keyboardTouchpadContextualEducation()) { + implLazy.get() + } else { + // No-op implementation when the flag is disabled. + return NoOpCoreStartable + } + } } +} - private object NoOpContextualEducationInteractor : CoreStartable { - override fun start() {} - } +private object NoOpKeyboardTouchpadEduStatsInteractor : KeyboardTouchpadEduStatsInteractor { + override fun incrementSignalCount(gestureType: GestureType) {} - private object NoOpKeyboardTouchpadEduInteractor : CoreStartable { - override fun start() {} - } + override fun updateShortcutTriggerTime(gestureType: GestureType) {} +} + +private object NoOpCoreStartable : CoreStartable { + override fun start() {} } diff --git a/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduUiCoordinator.kt b/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduUiCoordinator.kt new file mode 100644 index 000000000000..b446ea2bcb38 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduUiCoordinator.kt @@ -0,0 +1,68 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.education.ui.view + +import android.content.Context +import android.widget.Toast +import com.android.systemui.CoreStartable +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.education.shared.model.EducationUiType +import com.android.systemui.education.ui.viewmodel.ContextualEduContentViewModel +import com.android.systemui.education.ui.viewmodel.ContextualEduViewModel +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +/** + * A class to show contextual education on UI based on the edu produced from + * [ContextualEduViewModel] + */ +@SysUISingleton +class ContextualEduUiCoordinator +constructor( + @Application private val applicationScope: CoroutineScope, + private val viewModel: ContextualEduViewModel, + private val createToast: (String) -> Toast +) : CoreStartable { + + @Inject + constructor( + @Application applicationScope: CoroutineScope, + context: Context, + viewModel: ContextualEduViewModel, + ) : this( + applicationScope, + viewModel, + createToast = { message -> Toast.makeText(context, message, Toast.LENGTH_LONG) } + ) + + override fun start() { + applicationScope.launch { + viewModel.eduContent.collect { contentModel -> + if (contentModel.type == EducationUiType.Toast) { + showToast(contentModel) + } + } + } + } + + private fun showToast(model: ContextualEduContentViewModel) { + val toast = createToast(model.message) + toast.show() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduContentViewModel.kt new file mode 100644 index 000000000000..3cba4c8fb110 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduContentViewModel.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.education.ui.viewmodel + +import com.android.systemui.education.shared.model.EducationUiType + +data class ContextualEduContentViewModel(val message: String, val type: EducationUiType) diff --git a/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduViewModel.kt b/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduViewModel.kt new file mode 100644 index 000000000000..58276e0759f6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduViewModel.kt @@ -0,0 +1,51 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.education.ui.viewmodel + +import android.content.res.Resources +import com.android.systemui.contextualeducation.GestureType +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.education.domain.interactor.KeyboardTouchpadEduInteractor +import com.android.systemui.education.shared.model.EducationInfo +import com.android.systemui.res.R +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.map + +@SysUISingleton +class ContextualEduViewModel +@Inject +constructor(@Main private val resources: Resources, interactor: KeyboardTouchpadEduInteractor) { + val eduContent: Flow<ContextualEduContentViewModel> = + interactor.educationTriggered.filterNotNull().map { + ContextualEduContentViewModel(getEduContent(it), it.educationUiType) + } + + private fun getEduContent(educationInfo: EducationInfo): String { + // Todo: also check UiType in educationInfo to determine the string + val resourceId = + when (educationInfo.gestureType) { + GestureType.BACK -> R.string.back_edu_toast_content + GestureType.HOME -> R.string.home_edu_toast_content + GestureType.OVERVIEW -> R.string.overview_edu_toast_content + GestureType.ALL_APPS -> R.string.all_apps_edu_toast_content + } + return resources.getString(resourceId) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 8990505bc5db..e5f3a57de157 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -152,9 +152,6 @@ object Flags { // TODO(b/286563884): Tracking bug @JvmField val KEYGUARD_TALKBACK_FIX = unreleasedFlag("keyguard_talkback_fix") - // TODO(b/287268101): Tracking bug. - @JvmField val TRANSIT_CLOCK = releasedFlag("lockscreen_custom_transit_clock") - /** Enables preview loading animation in the wallpaper picker. */ // TODO(b/274443705): Tracking Bug @JvmField diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/KeyboardTouchpadTutorialModule.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/KeyboardTouchpadTutorialModule.kt new file mode 100644 index 000000000000..8e6cb077a25e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/KeyboardTouchpadTutorialModule.kt @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.inputdevice.tutorial + +import android.app.Activity +import com.android.systemui.CoreStartable +import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity +import com.android.systemui.touchpad.tutorial.domain.interactor.TouchpadGesturesInteractor +import dagger.Binds +import dagger.BindsOptionalOf +import dagger.Module +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +@Module +interface KeyboardTouchpadTutorialModule { + + @Binds + @IntoMap + @ClassKey(KeyboardTouchpadTutorialCoreStartable::class) + fun bindKeyboardTouchpadTutorialCoreStartable( + listener: KeyboardTouchpadTutorialCoreStartable + ): CoreStartable + + @Binds + @IntoMap + @ClassKey(KeyboardTouchpadTutorialActivity::class) + fun activity(impl: KeyboardTouchpadTutorialActivity): Activity + + // TouchpadModule dependencies below + // all should be optional to not introduce touchpad dependency in all sysui variants + + @BindsOptionalOf fun touchpadScreensProvider(): TouchpadTutorialScreensProvider + + @BindsOptionalOf fun touchpadGesturesInteractor(): TouchpadGesturesInteractor +} diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/TouchpadKeyboardTutorialModule.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/TouchpadTutorialScreensProvider.kt index 8ba8db498a36..bd3e771f40bc 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/TouchpadKeyboardTutorialModule.kt +++ b/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/TouchpadTutorialScreensProvider.kt @@ -14,20 +14,13 @@ * limitations under the License. */ -package com.android.systemui.touchpad.tutorial +package com.android.systemui.inputdevice.tutorial -import android.app.Activity -import com.android.systemui.touchpad.tutorial.ui.view.TouchpadTutorialActivity -import dagger.Binds -import dagger.Module -import dagger.multibindings.ClassKey -import dagger.multibindings.IntoMap +import androidx.compose.runtime.Composable -@Module -interface TouchpadKeyboardTutorialModule { +interface TouchpadTutorialScreensProvider { - @Binds - @IntoMap - @ClassKey(TouchpadTutorialActivity::class) - fun activity(impl: TouchpadTutorialActivity): Activity + @Composable fun BackGesture(onDoneButtonClicked: () -> Unit, onBack: () -> Unit) + + @Composable fun HomeGesture(onDoneButtonClicked: () -> Unit, onBack: () -> Unit) } diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/composable/ActionKeyTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/composable/ActionKeyTutorialScreen.kt new file mode 100644 index 000000000000..c5b0ca78d65a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/composable/ActionKeyTutorialScreen.kt @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.inputdevice.tutorial.ui.composable + +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.key.Key +import androidx.compose.ui.input.key.KeyEvent +import androidx.compose.ui.input.key.KeyEventType +import androidx.compose.ui.input.key.key +import androidx.compose.ui.input.key.onKeyEvent +import androidx.compose.ui.input.key.type +import com.airbnb.lottie.compose.rememberLottieDynamicProperties +import com.android.compose.theme.LocalAndroidColorScheme +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.FINISHED +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.NOT_STARTED +import com.android.systemui.res.R + +@Composable +fun ActionKeyTutorialScreen( + onDoneButtonClicked: () -> Unit, + onBack: () -> Unit, +) { + BackHandler(onBack = onBack) + val screenConfig = buildScreenConfig() + var actionState by remember { mutableStateOf(NOT_STARTED) } + Box( + modifier = + Modifier.fillMaxSize().onKeyEvent { keyEvent: KeyEvent -> + // temporary before we can access Action/Meta key + if (keyEvent.key == Key.AltLeft && keyEvent.type == KeyEventType.KeyUp) { + actionState = FINISHED + } + true + } + ) { + ActionTutorialContent(actionState, onDoneButtonClicked, screenConfig) + } +} + +@Composable +private fun buildScreenConfig() = + TutorialScreenConfig( + colors = rememberScreenColors(), + strings = + TutorialScreenConfig.Strings( + titleResId = R.string.tutorial_action_key_title, + bodyResId = R.string.tutorial_action_key_guidance, + titleSuccessResId = R.string.tutorial_action_key_success_title, + bodySuccessResId = R.string.tutorial_action_key_success_body + ), + animations = + TutorialScreenConfig.Animations( + educationResId = R.raw.action_key_edu, + successResId = R.raw.action_key_success + ) + ) + +@Composable +private fun rememberScreenColors(): TutorialScreenConfig.Colors { + val primaryFixedDim = LocalAndroidColorScheme.current.primaryFixedDim + val secondaryFixedDim = LocalAndroidColorScheme.current.secondaryFixedDim + val onSecondaryFixed = LocalAndroidColorScheme.current.onSecondaryFixed + val onSecondaryFixedVariant = LocalAndroidColorScheme.current.onSecondaryFixedVariant + val surfaceContainer = MaterialTheme.colorScheme.surfaceContainer + val dynamicProperties = + rememberLottieDynamicProperties( + rememberColorFilterProperty(".primaryFixedDim", primaryFixedDim), + rememberColorFilterProperty(".secondaryFixedDim", secondaryFixedDim), + rememberColorFilterProperty(".onSecondaryFixed", onSecondaryFixed), + rememberColorFilterProperty(".onSecondaryFixedVariant", onSecondaryFixedVariant) + ) + val screenColors = + remember(surfaceContainer, dynamicProperties) { + TutorialScreenConfig.Colors( + background = onSecondaryFixed, + successBackground = surfaceContainer, + title = primaryFixedDim, + animationColors = dynamicProperties, + ) + } + return screenColors +} diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/composable/ActionTutorialContent.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/composable/ActionTutorialContent.kt new file mode 100644 index 000000000000..c50b7dc06265 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/composable/ActionTutorialContent.kt @@ -0,0 +1,237 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.inputdevice.tutorial.ui.composable + +import android.graphics.ColorFilter +import android.graphics.PorterDuff +import android.graphics.PorterDuffColorFilter +import androidx.annotation.RawRes +import androidx.annotation.StringRes +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.EnterTransition +import androidx.compose.animation.ExitTransition +import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.snap +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.togetherWith +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.airbnb.lottie.LottieProperty +import com.airbnb.lottie.compose.LottieAnimation +import com.airbnb.lottie.compose.LottieCompositionSpec +import com.airbnb.lottie.compose.LottieConstants +import com.airbnb.lottie.compose.LottieDynamicProperties +import com.airbnb.lottie.compose.LottieDynamicProperty +import com.airbnb.lottie.compose.animateLottieCompositionAsState +import com.airbnb.lottie.compose.rememberLottieComposition +import com.airbnb.lottie.compose.rememberLottieDynamicProperty +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.FINISHED +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.IN_PROGRESS +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.NOT_STARTED + +enum class TutorialActionState { + NOT_STARTED, + IN_PROGRESS, + FINISHED +} + +@Composable +fun ActionTutorialContent( + actionState: TutorialActionState, + onDoneButtonClicked: () -> Unit, + config: TutorialScreenConfig +) { + val animatedColor by + animateColorAsState( + targetValue = + if (actionState == FINISHED) config.colors.successBackground + else config.colors.background, + animationSpec = tween(durationMillis = 150, easing = LinearEasing), + label = "backgroundColor" + ) + Column( + verticalArrangement = Arrangement.Center, + modifier = + Modifier.fillMaxSize() + .drawBehind { drawRect(animatedColor) } + .padding(start = 48.dp, top = 124.dp, end = 48.dp, bottom = 48.dp) + ) { + Row(modifier = Modifier.fillMaxWidth().weight(1f)) { + TutorialDescription( + titleTextId = + if (actionState == FINISHED) config.strings.titleSuccessResId + else config.strings.titleResId, + titleColor = config.colors.title, + bodyTextId = + if (actionState == FINISHED) config.strings.bodySuccessResId + else config.strings.bodyResId, + modifier = Modifier.weight(1f) + ) + Spacer(modifier = Modifier.width(76.dp)) + TutorialAnimation( + actionState, + config, + modifier = Modifier.weight(1f).padding(top = 8.dp) + ) + } + DoneButton(onDoneButtonClicked = onDoneButtonClicked) + } +} + +@Composable +fun TutorialDescription( + @StringRes titleTextId: Int, + titleColor: Color, + @StringRes bodyTextId: Int, + modifier: Modifier = Modifier +) { + Column(verticalArrangement = Arrangement.Top, modifier = modifier) { + Text( + text = stringResource(id = titleTextId), + style = MaterialTheme.typography.displayLarge, + color = titleColor + ) + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = stringResource(id = bodyTextId), + style = MaterialTheme.typography.bodyLarge, + color = Color.White + ) + } +} + +@Composable +fun TutorialAnimation( + actionState: TutorialActionState, + config: TutorialScreenConfig, + modifier: Modifier = Modifier +) { + Box(modifier = modifier.fillMaxWidth()) { + AnimatedContent( + targetState = actionState, + transitionSpec = { + if (initialState == NOT_STARTED) { + val transitionDurationMillis = 150 + fadeIn(animationSpec = tween(transitionDurationMillis, easing = LinearEasing)) + .togetherWith( + fadeOut(animationSpec = snap(delayMillis = transitionDurationMillis)) + ) + // we explicitly don't want size transform because when targetState + // animation is loaded for the first time, AnimatedContent thinks target + // size is smaller and tries to shrink initial state animation + .using(sizeTransform = null) + } else { + // empty transition works because all remaining transitions are from IN_PROGRESS + // state which shares initial animation frame with both FINISHED and NOT_STARTED + EnterTransition.None togetherWith ExitTransition.None + } + } + ) { state -> + when (state) { + NOT_STARTED -> + EducationAnimation( + config.animations.educationResId, + config.colors.animationColors + ) + IN_PROGRESS -> + FrozenSuccessAnimation( + config.animations.successResId, + config.colors.animationColors + ) + FINISHED -> + SuccessAnimation(config.animations.successResId, config.colors.animationColors) + } + } + } +} + +@Composable +private fun FrozenSuccessAnimation( + @RawRes successAnimationId: Int, + animationProperties: LottieDynamicProperties +) { + val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(successAnimationId)) + LottieAnimation( + composition = composition, + progress = { 0f }, // animation should freeze on 1st frame + dynamicProperties = animationProperties, + ) +} + +@Composable +private fun EducationAnimation( + @RawRes educationAnimationId: Int, + animationProperties: LottieDynamicProperties +) { + val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(educationAnimationId)) + val progress by + animateLottieCompositionAsState(composition, iterations = LottieConstants.IterateForever) + LottieAnimation( + composition = composition, + progress = { progress }, + dynamicProperties = animationProperties, + ) +} + +@Composable +private fun SuccessAnimation( + @RawRes successAnimationId: Int, + animationProperties: LottieDynamicProperties +) { + val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(successAnimationId)) + val progress by animateLottieCompositionAsState(composition, iterations = 1) + LottieAnimation( + composition = composition, + progress = { progress }, + dynamicProperties = animationProperties, + ) +} + +@Composable +fun rememberColorFilterProperty( + layerName: String, + color: Color +): LottieDynamicProperty<ColorFilter> { + return rememberLottieDynamicProperty( + LottieProperty.COLOR_FILTER, + value = PorterDuffColorFilter(color.toArgb(), PorterDuff.Mode.SRC_ATOP), + // "**" below means match zero or more layers, so ** layerName ** means find layer with that + // name at any depth + keyPath = arrayOf("**", layerName, "**") + ) +} diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialComponents.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/composable/TutorialComponents.kt index f2276c8be71d..01ad585019d2 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialComponents.kt +++ b/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/composable/TutorialComponents.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.touchpad.tutorial.ui.composable +package com.android.systemui.inputdevice.tutorial.ui.composable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialScreenConfig.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/composable/TutorialScreenConfig.kt index d76ceb9380cd..0406bb9e6fef 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialScreenConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/composable/TutorialScreenConfig.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.touchpad.tutorial.ui.composable +package com.android.systemui.inputdevice.tutorial.ui.composable import androidx.annotation.RawRes import androidx.annotation.StringRes diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/view/KeyboardTouchpadTutorialActivity.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/view/KeyboardTouchpadTutorialActivity.kt new file mode 100644 index 000000000000..3e382d669e5d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/view/KeyboardTouchpadTutorialActivity.kt @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.inputdevice.tutorial.ui.view + +import android.os.Bundle +import android.view.WindowManager +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import androidx.activity.viewModels +import androidx.compose.runtime.Composable +import com.android.compose.theme.PlatformTheme +import com.android.systemui.inputdevice.tutorial.TouchpadTutorialScreensProvider +import com.android.systemui.inputdevice.tutorial.ui.viewmodel.KeyboardTouchpadTutorialViewModel +import java.util.Optional +import javax.inject.Inject + +/** + * Activity for out of the box experience for keyboard and touchpad. Note that it's possible that + * either of them are actually not connected when this is launched + */ +class KeyboardTouchpadTutorialActivity +@Inject +constructor( + private val viewModelFactory: KeyboardTouchpadTutorialViewModel.Factory, + private val touchpadTutorialScreensProvider: Optional<TouchpadTutorialScreensProvider>, +) : ComponentActivity() { + + private val vm by + viewModels<KeyboardTouchpadTutorialViewModel>(factoryProducer = { viewModelFactory }) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + enableEdgeToEdge() + setContent { + PlatformTheme { + KeyboardTouchpadTutorialContainer(vm, touchpadTutorialScreensProvider) { finish() } + } + } + // required to handle 3+ fingers on touchpad + window.addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY) + } + + override fun onResume() { + super.onResume() + vm.onOpened() + } + + override fun onPause() { + super.onPause() + vm.onClosed() + } +} + +@Composable +fun KeyboardTouchpadTutorialContainer( + vm: KeyboardTouchpadTutorialViewModel, + touchpadTutorialScreensProvider: Optional<TouchpadTutorialScreensProvider>, + closeTutorial: () -> Unit +) {} diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/viewmodel/KeyboardTouchpadTutorialViewModel.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/viewmodel/KeyboardTouchpadTutorialViewModel.kt new file mode 100644 index 000000000000..39b1ec0f0390 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/viewmodel/KeyboardTouchpadTutorialViewModel.kt @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.inputdevice.tutorial.ui.viewmodel + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import com.android.systemui.touchpad.tutorial.domain.interactor.TouchpadGesturesInteractor +import java.util.Optional +import javax.inject.Inject +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow + +class KeyboardTouchpadTutorialViewModel( + private val gesturesInteractor: Optional<TouchpadGesturesInteractor> +) : ViewModel() { + + private val _screen = MutableStateFlow(Screen.BACK_GESTURE) + val screen: StateFlow<Screen> = _screen + + fun goTo(screen: Screen) { + _screen.value = screen + } + + fun onOpened() { + gesturesInteractor.ifPresent { it.disableGestures() } + } + + fun onClosed() { + gesturesInteractor.ifPresent { it.enableGestures() } + } + + class Factory + @Inject + constructor(private val gesturesInteractor: Optional<TouchpadGesturesInteractor>) : + ViewModelProvider.Factory { + + @Suppress("UNCHECKED_CAST") + override fun <T : ViewModel> create(modelClass: Class<T>): T { + return KeyboardTouchpadTutorialViewModel(gesturesInteractor) as T + } + } +} + +enum class Screen { + BACK_GESTURE, + HOME_GESTURE, + ACTION_KEY +} diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/model/TutorialSchedulerInfo.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/model/TutorialSchedulerInfo.kt index cfe64e269c95..9f46846f0d91 100644 --- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/model/TutorialSchedulerInfo.kt +++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/model/TutorialSchedulerInfo.kt @@ -16,11 +16,6 @@ package com.android.systemui.inputdevice.tutorial.data.model -data class TutorialSchedulerInfo( - val keyboard: DeviceSchedulerInfo = DeviceSchedulerInfo(), - val touchpad: DeviceSchedulerInfo = DeviceSchedulerInfo() -) - data class DeviceSchedulerInfo(var isLaunched: Boolean = false, var connectTime: Long? = null) { val wasEverConnected: Boolean get() = connectTime != null diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/repository/TutorialSchedulerRepository.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/repository/TutorialSchedulerRepository.kt index 31ff01836428..b9b38954784e 100644 --- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/repository/TutorialSchedulerRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/repository/TutorialSchedulerRepository.kt @@ -25,21 +25,32 @@ import androidx.datastore.preferences.core.longPreferencesKey import androidx.datastore.preferences.preferencesDataStore import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.inputdevice.tutorial.data.model.DeviceSchedulerInfo -import com.android.systemui.inputdevice.tutorial.data.model.TutorialSchedulerInfo import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map @SysUISingleton class TutorialSchedulerRepository @Inject -constructor(@Application private val applicationContext: Context) { +constructor( + @Application private val applicationContext: Context, + @Background private val backgroundScope: CoroutineScope +) { private val Context.dataStore: DataStore<Preferences> by - preferencesDataStore(name = DATASTORE_NAME) + preferencesDataStore(name = DATASTORE_NAME, scope = backgroundScope) - suspend fun loadData(): TutorialSchedulerInfo { + suspend fun isLaunched(deviceType: DeviceType): Boolean = loadData()[deviceType]!!.isLaunched + + suspend fun wasEverConnected(deviceType: DeviceType): Boolean = + loadData()[deviceType]!!.wasEverConnected + + suspend fun connectTime(deviceType: DeviceType): Long = loadData()[deviceType]!!.connectTime!! + + private suspend fun loadData(): Map<DeviceType, DeviceSchedulerInfo> { return applicationContext.dataStore.data.map { pref -> getSchedulerInfo(pref) }.first() } @@ -51,10 +62,10 @@ constructor(@Application private val applicationContext: Context) { applicationContext.dataStore.edit { pref -> pref[getLaunchedKey(device)] = true } } - private fun getSchedulerInfo(pref: Preferences): TutorialSchedulerInfo { - return TutorialSchedulerInfo( - keyboard = getDeviceSchedulerInfo(pref, DeviceType.KEYBOARD), - touchpad = getDeviceSchedulerInfo(pref, DeviceType.TOUCHPAD) + private fun getSchedulerInfo(pref: Preferences): Map<DeviceType, DeviceSchedulerInfo> { + return mapOf( + DeviceType.KEYBOARD to getDeviceSchedulerInfo(pref, DeviceType.KEYBOARD), + DeviceType.TOUCHPAD to getDeviceSchedulerInfo(pref, DeviceType.TOUCHPAD) ) } diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt index 05e104468f67..b3b8f21a4a4b 100644 --- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt @@ -16,23 +16,25 @@ package com.android.systemui.inputdevice.tutorial.domain.interactor -import android.content.Context -import android.content.Intent +import android.os.SystemProperties +import android.util.Log import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.inputdevice.tutorial.data.model.DeviceSchedulerInfo +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType +import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType.KEYBOARD +import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType.TOUCHPAD import com.android.systemui.inputdevice.tutorial.data.repository.TutorialSchedulerRepository import com.android.systemui.keyboard.data.repository.KeyboardRepository import com.android.systemui.touchpad.data.repository.TouchpadRepository -import java.time.Duration import java.time.Instant import javax.inject.Inject +import kotlin.time.Duration.Companion.hours import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.merge import kotlinx.coroutines.launch /** @@ -43,62 +45,72 @@ import kotlinx.coroutines.launch class TutorialSchedulerInteractor @Inject constructor( - @Application private val context: Context, - @Application private val applicationScope: CoroutineScope, - private val keyboardRepository: KeyboardRepository, - private val touchpadRepository: TouchpadRepository, - private val tutorialSchedulerRepository: TutorialSchedulerRepository + @Background private val backgroundScope: CoroutineScope, + keyboardRepository: KeyboardRepository, + touchpadRepository: TouchpadRepository, + private val repo: TutorialSchedulerRepository ) { + private val isAnyDeviceConnected = + mapOf( + KEYBOARD to keyboardRepository.isAnyKeyboardConnected, + TOUCHPAD to touchpadRepository.isAnyTouchpadConnected + ) + fun start() { - applicationScope.launch { - val info = tutorialSchedulerRepository.loadData() - if (!info.keyboard.isLaunched) { - applicationScope.launch { - schedule( - keyboardRepository.isAnyKeyboardConnected, - info.keyboard, - DeviceType.KEYBOARD - ) - } - } - if (!info.touchpad.isLaunched) { - applicationScope.launch { - schedule( - touchpadRepository.isAnyTouchpadConnected, - info.touchpad, - DeviceType.TOUCHPAD - ) - } + backgroundScope.launch { + // Merging two flows to ensure that launch tutorial is launched consecutively in order + // to avoid race condition + merge(touchpadScheduleFlow, keyboardScheduleFlow).collect { + val tutorialType = resolveTutorialType(it) + launchTutorial(tutorialType) } } } - private suspend fun schedule( - isAnyDeviceConnected: Flow<Boolean>, - info: DeviceSchedulerInfo, - deviceType: DeviceType - ) { - if (!info.wasEverConnected) { - waitForDeviceConnection(isAnyDeviceConnected) - info.connectTime = Instant.now().toEpochMilli() - tutorialSchedulerRepository.updateConnectTime(deviceType, info.connectTime!!) + private val touchpadScheduleFlow = flow { + if (!repo.isLaunched(TOUCHPAD)) { + schedule(TOUCHPAD) + emit(TOUCHPAD) } - delay(remainingTimeMillis(info.connectTime!!)) - waitForDeviceConnection(isAnyDeviceConnected) - info.isLaunched = true - tutorialSchedulerRepository.updateLaunch(deviceType) - launchTutorial() } - private suspend fun waitForDeviceConnection(isAnyDeviceConnected: Flow<Boolean>): Boolean { - return isAnyDeviceConnected.filter { it }.first() + private val keyboardScheduleFlow = flow { + if (!repo.isLaunched(KEYBOARD)) { + schedule(KEYBOARD) + emit(KEYBOARD) + } } - private fun launchTutorial() { - val intent = Intent(TUTORIAL_ACTION) - intent.addCategory(Intent.CATEGORY_DEFAULT) - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - context.startActivity(intent) + private suspend fun schedule(deviceType: DeviceType) { + if (!repo.wasEverConnected(deviceType)) { + waitForDeviceConnection(deviceType) + repo.updateConnectTime(deviceType, Instant.now().toEpochMilli()) + } + delay(remainingTimeMillis(start = repo.connectTime(deviceType))) + waitForDeviceConnection(deviceType) + } + + private suspend fun waitForDeviceConnection(deviceType: DeviceType) = + isAnyDeviceConnected[deviceType]!!.filter { it }.first() + + private suspend fun launchTutorial(tutorialType: TutorialType) { + if (tutorialType == TutorialType.KEYBOARD || tutorialType == TutorialType.BOTH) + repo.updateLaunch(KEYBOARD) + if (tutorialType == TutorialType.TOUCHPAD || tutorialType == TutorialType.BOTH) + repo.updateLaunch(TOUCHPAD) + // TODO: launch tutorial + Log.d(TAG, "Launch tutorial for $tutorialType") + } + + private suspend fun resolveTutorialType(deviceType: DeviceType): TutorialType { + // Resolve the type of tutorial depending on which device are connected when the tutorial is + // launched. E.g. when the keyboard is connected for [LAUNCH_DELAY], both keyboard and + // touchpad are connected, we launch the tutorial for both. + if (repo.isLaunched(deviceType)) return TutorialType.NONE + val otherDevice = if (deviceType == KEYBOARD) TOUCHPAD else KEYBOARD + val isOtherDeviceConnected = isAnyDeviceConnected[otherDevice]!!.first() + if (!repo.isLaunched(otherDevice) && isOtherDeviceConnected) return TutorialType.BOTH + return if (deviceType == KEYBOARD) TutorialType.KEYBOARD else TutorialType.TOUCHPAD } private fun remainingTimeMillis(start: Long): Long { @@ -107,7 +119,20 @@ constructor( } companion object { - const val TUTORIAL_ACTION = "com.android.systemui.action.TOUCHPAD_TUTORIAL" - private val LAUNCH_DELAY = Duration.ofHours(72).toMillis() + const val TAG = "TutorialSchedulerInteractor" + private val DEFAULT_LAUNCH_DELAY = 72.hours.inWholeMilliseconds + private val LAUNCH_DELAY: Long + get() = + SystemProperties.getLong( + "persist.peripheral_tutorial_delay_ms", + DEFAULT_LAUNCH_DELAY + ) + } + + enum class TutorialType { + KEYBOARD, + TOUCHPAD, + BOTH, + NONE } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 3f9c98d6a5d4..17c5977fc80a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -42,6 +42,7 @@ import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STR import static com.android.systemui.DejankUtils.whitelistIpcs; import static com.android.systemui.Flags.notifyPowerManagerUserActivityBackground; import static com.android.systemui.Flags.refactorGetCurrentUser; +import static com.android.systemui.Flags.relockWithPowerButtonImmediately; import static com.android.systemui.Flags.translucentOccludingActivityFix; import static com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel.DREAMING_ANIMATION_DURATION_MS; @@ -477,6 +478,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, private boolean mUnlockingAndWakingFromDream = false; private boolean mHideAnimationRun = false; private boolean mHideAnimationRunning = false; + private boolean mIsKeyguardExitAnimationCanceled = false; private SoundPool mLockSounds; private int mLockSoundId; @@ -1588,10 +1590,11 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, setShowingLocked(!shouldWaitForProvisioning() && !mLockPatternUtils.isLockScreenDisabled( mSelectedUserInteractor.getSelectedUserId()), - true /* forceCallbacks */); + true /* forceCallbacks */, "setupLocked - keyguard service enabled"); } else { // The system's keyguard is disabled or missing. - setShowingLocked(false /* showing */, true /* forceCallbacks */); + setShowingLocked(false /* showing */, true /* forceCallbacks */, + "setupLocked - keyguard service disabled"); } mKeyguardTransitions.register( @@ -2334,6 +2337,12 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, Log.e(TAG, "doKeyguard: we're still showing, but going away. Re-show the " + "keyguard rather than short-circuiting and resetting."); } else { + // We're removing "reset" in the refactor - "resetting" the views will happen + // as a reaction to the root cause of the "reset" signal. + if (KeyguardWmStateRefactor.isEnabled()) { + return; + } + // It's already showing, and we're not trying to show it while the screen is // off. We can simply reset all of the views, but don't hide the bouncer in case // the user is currently interacting with it. @@ -2827,9 +2836,10 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, playSound(mTrustedSoundId); } - private void updateActivityLockScreenState(boolean showing, boolean aodShowing) { + private void updateActivityLockScreenState(boolean showing, boolean aodShowing, String reason) { mUiBgExecutor.execute(() -> { - Log.d(TAG, "updateActivityLockScreenState(" + showing + ", " + aodShowing + ")"); + Log.d(TAG, "updateActivityLockScreenState(" + showing + ", " + aodShowing + ", " + + reason + ")"); if (KeyguardWmStateRefactor.isEnabled()) { // Handled in WmLockscreenVisibilityManager if flag is enabled. @@ -2889,7 +2899,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, // Force if we're showing in the middle of unlocking, to ensure we end up in the // correct state. - setShowingLocked(true, hidingOrGoingAway /* force */); + setShowingLocked(true, hidingOrGoingAway /* force */, "handleShowInner"); mHiding = false; if (!KeyguardWmStateRefactor.isEnabled()) { @@ -3061,15 +3071,14 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, mHiding = true; mKeyguardGoingAwayRunnable.run(); } else { - Log.d(TAG, "Hiding keyguard while occluded. Just hide the keyguard view and exit."); - if (!KeyguardWmStateRefactor.isEnabled()) { mKeyguardViewControllerLazy.get().hide( mSystemClock.uptimeMillis() + mHideAnimation.getStartOffset(), mHideAnimation.getDuration()); } - onKeyguardExitFinished(); + onKeyguardExitFinished("Hiding keyguard while occluded. Just hide the keyguard " + + "view and exit."); } // It's possible that the device was unlocked (via BOUNCER or Fingerprint) while @@ -3100,6 +3109,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, Log.d(TAG, "handleStartKeyguardExitAnimation startTime=" + startTime + " fadeoutDuration=" + fadeoutDuration); synchronized (KeyguardViewMediator.this) { + mIsKeyguardExitAnimationCanceled = false; // Tell ActivityManager that we canceled the keyguard animation if // handleStartKeyguardExitAnimation was called, but we're not hiding the keyguard, // unless we're animating the surface behind the keyguard and will be hiding the @@ -3119,7 +3129,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, Slog.w(TAG, "Failed to call onAnimationFinished", e); } } - setShowingLocked(mShowing, true /* force */); + setShowingLocked(mShowing, true /* force */, + "handleStartKeyguardExitAnimation - canceled"); return; } mHiding = false; @@ -3143,9 +3154,11 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, Slog.w(TAG, "Failed to call onAnimationFinished", e); } } - onKeyguardExitFinished(); - mKeyguardViewControllerLazy.get().hide(0 /* startTime */, - 0 /* fadeoutDuration */); + if (!mIsKeyguardExitAnimationCanceled) { + onKeyguardExitFinished("onRemoteAnimationFinished"); + mKeyguardViewControllerLazy.get().hide(0 /* startTime */, + 0 /* fadeoutDuration */); + } mInteractionJankMonitor.end(CUJ_LOCKSCREEN_UNLOCK_ANIMATION); } @@ -3282,12 +3295,12 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, anim.start(); }); - onKeyguardExitFinished(); + onKeyguardExitFinished("remote animation disabled"); } } } - private void onKeyguardExitFinished() { + private void onKeyguardExitFinished(String reason) { if (DEBUG) Log.d(TAG, "onKeyguardExitFinished()"); // only play "unlock" noises if not on a call (since the incall UI // disables the keyguard) @@ -3295,7 +3308,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, playSounds(false); } - setShowingLocked(false); + setShowingLocked(false, "onKeyguardExitFinished: " + reason); mWakeAndUnlocking = false; mDismissCallbackRegistry.notifyDismissSucceeded(); resetKeyguardDonePendingLocked(); @@ -3343,6 +3356,9 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, // A lock is pending, meaning the keyguard exit animation was cancelled because we're // re-locking. We should just end the surface-behind animation without exiting the // keyguard. The pending lock will be handled by onFinishedGoingToSleep(). + if (relockWithPowerButtonImmediately()) { + mIsKeyguardExitAnimationCanceled = true; + } finishSurfaceBehindRemoteAnimation(true /* showKeyguard */); maybeHandlePendingLock(); } else { @@ -3391,12 +3407,13 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, doKeyguardLocked(null); finishSurfaceBehindRemoteAnimation(true /* showKeyguard */); // Ensure WM is notified that we made a decision to show - setShowingLocked(true /* showing */, true /* force */); + setShowingLocked(true /* showing */, true /* force */, + "exitKeyguardAndFinishSurfaceBehindRemoteAnimation - relocked"); return; } - onKeyguardExitFinished(); + onKeyguardExitFinished("exitKeyguardAndFinishSurfaceBehindRemoteAnimation"); if (mKeyguardStateController.isDismissingFromSwipe() || wasShowing) { Log.d(TAG, "onKeyguardExitRemoteAnimationFinished" @@ -3453,7 +3470,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, mSurfaceBehindRemoteAnimationRequested = false; mKeyguardStateController.notifyKeyguardGoingAway(false); if (mShowing) { - setShowingLocked(true, true); + setShowingLocked(true, true, "hideSurfaceBehindKeyguard"); } } @@ -3799,7 +3816,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, // update lock screen state in ATMS here, otherwise ATMS tries to resume activities when // enabling doze state. if (mShowing || !mPendingLock || !mDozeParameters.canControlUnlockedScreenOff()) { - setShowingLocked(mShowing); + setShowingLocked(mShowing, "setDozing"); } } @@ -3809,7 +3826,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, // is 1f), then show the activity lock screen. if (mAnimatingScreenOff && mDozing && linear == 1f) { mAnimatingScreenOff = false; - setShowingLocked(mShowing, true); + setShowingLocked(mShowing, true, "onDozeAmountChanged"); } } @@ -3847,11 +3864,11 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } } - void setShowingLocked(boolean showing) { - setShowingLocked(showing, false /* forceCallbacks */); + void setShowingLocked(boolean showing, String reason) { + setShowingLocked(showing, false /* forceCallbacks */, reason); } - private void setShowingLocked(boolean showing, boolean forceCallbacks) { + private void setShowingLocked(boolean showing, boolean forceCallbacks, String reason) { final boolean aodShowing = mDozing && !mWakeAndUnlocking; final boolean notifyDefaultDisplayCallbacks = showing != mShowing || forceCallbacks; final boolean updateActivityLockScreenState = showing != mShowing @@ -3862,9 +3879,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, notifyDefaultDisplayCallbacks(showing); } if (updateActivityLockScreenState) { - updateActivityLockScreenState(showing, aodShowing); + updateActivityLockScreenState(showing, aodShowing, reason); } - } private void notifyDefaultDisplayCallbacks(boolean showing) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/OWNERS b/packages/SystemUI/src/com/android/systemui/keyguard/OWNERS new file mode 100644 index 000000000000..443e98762c47 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/OWNERS @@ -0,0 +1,11 @@ +set noparent + +# Bug component: 78010 + +amiko@google.com +beverlyt@google.com +bhinegardner@google.com +chandruis@google.com +jglazier@google.com +mpietal@google.com +tsuji@google.com diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt index de60c1117c19..797a4ec419a9 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt @@ -285,7 +285,7 @@ constructor( state: TransitionState ) { if (updateTransitionId != transitionId) { - Log.wtf(TAG, "Attempting to update with old/invalid transitionId: $transitionId") + Log.e(TAG, "Attempting to update with old/invalid transitionId: $transitionId") return } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt index 51d92f054bbe..5dc020f41ad3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt @@ -300,7 +300,9 @@ constructor( swipeToDismissInteractor.dismissFling .filterNotNull() .filterRelevantKeyguardState() - .collect { _ -> startTransitionTo(KeyguardState.GONE) } + .collect { _ -> + startTransitionTo(KeyguardState.GONE, ownerReason = "dismissFling != null") + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt index aea57ce15794..905ca8eda87f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt @@ -135,7 +135,7 @@ constructor( } } - private suspend fun FromOccludedTransitionInteractor.startTransitionToLockscreenOrHub( + private suspend fun startTransitionToLockscreenOrHub( isIdleOnCommunal: Boolean, showCommunalFromOccluded: Boolean, dreamFromOccluded: Boolean, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt index 31236a479940..0682d8777107 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt @@ -433,10 +433,6 @@ constructor( value = featureFlags.isEnabled(Flags.WALLPAPER_PICKER_UI_FOR_AIWP) ), KeyguardPickerFlag( - name = Contract.FlagsTable.FLAG_NAME_TRANSIT_CLOCK, - value = featureFlags.isEnabled(Flags.TRANSIT_CLOCK) - ), - KeyguardPickerFlag( name = Contract.FlagsTable.FLAG_NAME_PAGE_TRANSITIONS, value = featureFlags.isEnabled(Flags.WALLPAPER_PICKER_PAGE_TRANSITIONS) ), diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt index efdae6202805..6ff369ec8711 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt @@ -162,6 +162,25 @@ constructor( } } } + + // Safety: When any transition is FINISHED, ensure all other transitionValue flows other + // than the FINISHED state are reset to a value of 0f. There have been rare but severe + // bugs that get the device stuck in a bad state when these are not properly reset. + scope.launch { + repository.transitions + .filter { it.transitionState == TransitionState.FINISHED } + .collect { + for (state in KeyguardState.entries) { + if (state != it.to) { + val flow = getTransitionValueFlow(state) + val replayCache = flow.replayCache + if (!replayCache.isEmpty() && replayCache.last() != 0f) { + flow.emit(0f) + } + } + } + } + } } fun transition(edge: Edge, edgeWithoutSceneContainer: Edge): Flow<TransitionStep> { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SwipeToDismissInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SwipeToDismissInteractor.kt index 86e41154205e..906d58664de9 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SwipeToDismissInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SwipeToDismissInteractor.kt @@ -19,14 +19,15 @@ package com.android.systemui.keyguard.domain.interactor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.shade.data.repository.ShadeRepository import com.android.systemui.util.kotlin.Utils.Companion.sample -import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn +import javax.inject.Inject /** * Handles logic around the swipe to dismiss gesture, where the user swipes up on the dismissable @@ -53,12 +54,14 @@ constructor( shadeRepository.currentFling .sample( transitionInteractor.startedKeyguardState, - keyguardInteractor.isKeyguardDismissible + keyguardInteractor.isKeyguardDismissible, + keyguardInteractor.statusBarState, ) - .filter { (flingInfo, startedState, keyguardDismissable) -> + .filter { (flingInfo, startedState, keyguardDismissable, statusBarState) -> flingInfo != null && - !flingInfo.expand && - startedState == KeyguardState.LOCKSCREEN && + !flingInfo.expand && + statusBarState != StatusBarState.SHADE_LOCKED && + startedState == KeyguardState.LOCKSCREEN && keyguardDismissable } .map { (flingInfo, _) -> flingInfo } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt index 9dc77d3dc9d3..fb9719142b54 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt @@ -26,6 +26,7 @@ import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor import com.android.systemui.keyguard.ui.view.DeviceEntryIconView import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerUdfpsIconViewModel import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.scene.shared.flag.SceneContainerFlag import kotlinx.coroutines.ExperimentalCoroutinesApi @ExperimentalCoroutinesApi @@ -52,7 +53,11 @@ object AlternateBouncerUdfpsViewBinder { } } - launch("$TAG#viewModel.alpha") { viewModel.alpha.collect { view.alpha = it } } + if (SceneContainerFlag.isEnabled) { + view.alpha = 1f + } else { + launch("$TAG#viewModel.alpha") { viewModel.alpha.collect { view.alpha = it } } + } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt index a250b22dde07..91a7f7fc66bd 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt @@ -37,13 +37,13 @@ import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor import com.android.systemui.deviceentry.ui.binder.UdfpsAccessibilityOverlayBinder import com.android.systemui.deviceentry.ui.view.UdfpsAccessibilityOverlay import com.android.systemui.deviceentry.ui.viewmodel.AlternateBouncerUdfpsAccessibilityOverlayViewModel -import com.android.systemui.keyguard.DismissCallbackRegistry import com.android.systemui.keyguard.ui.view.DeviceEntryIconView import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerDependencies import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerUdfpsIconViewModel import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerWindowViewModel import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.res.R +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scrim.ScrimView import dagger.Lazy import javax.inject.Inject @@ -67,7 +67,6 @@ constructor( private val alternateBouncerDependencies: Lazy<AlternateBouncerDependencies>, private val windowManager: Lazy<WindowManager>, private val layoutInflater: Lazy<LayoutInflater>, - private val dismissCallbackRegistry: DismissCallbackRegistry, ) : CoreStartable { private val layoutParams: WindowManager.LayoutParams get() = @@ -95,9 +94,10 @@ constructor( private var alternateBouncerView: ConstraintLayout? = null override fun start() { - if (!DeviceEntryUdfpsRefactor.isEnabled) { + if (!DeviceEntryUdfpsRefactor.isEnabled || SceneContainerFlag.isEnabled) { return } + applicationScope.launch("$TAG#alternateBouncerWindowViewModel") { alternateBouncerWindowViewModel.get().alternateBouncerWindowRequired.collect { addAlternateBouncerWindowView -> @@ -110,7 +110,7 @@ constructor( bind(alternateBouncerView!!, alternateBouncerDependencies.get()) } else { removeViewFromWindowManager() - alternateBouncerDependencies.get().viewModel.hideAlternateBouncer() + alternateBouncerDependencies.get().viewModel.onRemovedFromWindow() } } } @@ -144,7 +144,7 @@ constructor( private val onAttachAddBackGestureHandler = object : View.OnAttachStateChangeListener { private val onBackInvokedCallback: OnBackInvokedCallback = OnBackInvokedCallback { - onBackRequested() + alternateBouncerDependencies.get().viewModel.onBackRequested() } override fun onViewAttachedToWindow(view: View) { @@ -161,14 +161,12 @@ constructor( .findOnBackInvokedDispatcher() ?.unregisterOnBackInvokedCallback(onBackInvokedCallback) } - - fun onBackRequested() { - alternateBouncerDependencies.get().viewModel.hideAlternateBouncer() - dismissCallbackRegistry.notifyDismissCancelled() - } } private fun addViewToWindowManager() { + if (SceneContainerFlag.isEnabled) { + return + } if (alternateBouncerView != null) { return } @@ -190,6 +188,7 @@ constructor( if (DeviceEntryUdfpsRefactor.isUnexpectedlyInLegacyMode()) { return } + optionallyAddUdfpsViews( view = view, udfpsIconViewModel = alternateBouncerDependencies.udfpsIconViewModel, @@ -202,12 +201,13 @@ constructor( viewModel = alternateBouncerDependencies.messageAreaViewModel, ) - val scrim = view.requireViewById(R.id.alternate_bouncer_scrim) as ScrimView + val scrim: ScrimView = view.requireViewById(R.id.alternate_bouncer_scrim) val viewModel = alternateBouncerDependencies.viewModel val swipeUpAnywhereGestureHandler = alternateBouncerDependencies.swipeUpAnywhereGestureHandler val tapGestureDetector = alternateBouncerDependencies.tapGestureDetector - view.repeatWhenAttached { alternateBouncerViewContainer -> + + view.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.STARTED) { launch("$TAG#viewModel.registerForDismissGestures") { viewModel.registerForDismissGestures.collect { registerForDismissGestures -> @@ -216,11 +216,11 @@ constructor( swipeTag ) { _ -> alternateBouncerDependencies.powerInteractor.onUserTouch() - viewModel.showPrimaryBouncer() + viewModel.onTapped() } tapGestureDetector.addOnGestureDetectedCallback(tapTag) { _ -> alternateBouncerDependencies.powerInteractor.onUserTouch() - viewModel.showPrimaryBouncer() + viewModel.onTapped() } } else { swipeUpAnywhereGestureHandler.removeOnGestureDetectedCallback( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt index 830ef3b87faa..76d3389c25b4 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt @@ -26,8 +26,8 @@ import com.android.app.tracing.coroutines.launch import com.android.systemui.common.ui.view.LongPressHandlingView import com.android.systemui.keyguard.ui.viewmodel.KeyguardTouchHandlingViewModel import com.android.systemui.lifecycle.repeatWhenAttached -import com.android.systemui.res.R import com.android.systemui.plugins.FalsingManager +import com.android.systemui.res.R object KeyguardLongPressViewBinder { /** @@ -46,7 +46,6 @@ object KeyguardLongPressViewBinder { onSingleTap: () -> Unit, falsingManager: FalsingManager, ) { - view.contentDescription = view.resources.getString(R.string.accessibility_desc_lock_screen) view.accessibilityHintLongPressAction = AccessibilityNodeInfo.AccessibilityAction( AccessibilityNodeInfoCompat.ACTION_LONG_CLICK, @@ -54,8 +53,15 @@ object KeyguardLongPressViewBinder { ) view.listener = object : LongPressHandlingView.Listener { - override fun onLongPressDetected(view: View, x: Int, y: Int, isA11yAction: Boolean) { - if (!isA11yAction && falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) { + override fun onLongPressDetected( + view: View, + x: Int, + y: Int, + isA11yAction: Boolean + ) { + if ( + !isA11yAction && falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY) + ) { return } @@ -76,6 +82,12 @@ object KeyguardLongPressViewBinder { launch("$TAG#viewModel.isLongPressHandlingEnabled") { viewModel.isLongPressHandlingEnabled.collect { isEnabled -> view.setLongPressHandlingEnabled(isEnabled) + view.contentDescription = + if (isEnabled) { + view.resources.getString(R.string.accessibility_desc_lock_screen) + } else { + null + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt index df0b3dc3cbce..4908dbdec61e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt @@ -23,6 +23,7 @@ import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor import com.android.systemui.keyguard.ui.view.DeviceEntryIconView +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.shared.recents.utilities.Utilities.clamp import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -50,6 +51,7 @@ constructor( private val isSupported: Flow<Boolean> = deviceEntryUdfpsInteractor.isUdfpsSupported val alpha: Flow<Float> = alternateBouncerViewModel.transitionToAlternateBouncerProgress.map { + SceneContainerFlag.assertInLegacyMode() clamp(it * 2f, 0f, 1f) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt index 470f17b74032..7b0b23ffb2ff 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt @@ -18,15 +18,20 @@ package com.android.systemui.keyguard.ui.viewmodel import android.graphics.Color +import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor +import com.android.systemui.keyguard.DismissCallbackRegistry import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager +import dagger.Lazy import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach @ExperimentalCoroutinesApi class AlternateBouncerViewModel @@ -34,12 +39,18 @@ class AlternateBouncerViewModel constructor( private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager, keyguardTransitionInteractor: KeyguardTransitionInteractor, + private val dismissCallbackRegistry: DismissCallbackRegistry, + alternateBouncerInteractor: Lazy<AlternateBouncerInteractor>, ) { // When we're fully transitioned to the AlternateBouncer, the alpha of the scrim should be: private val alternateBouncerScrimAlpha = .66f + /** Reports the alternate bouncer visible state if the scene container flag is enabled. */ + val isVisible: Flow<Boolean> = + alternateBouncerInteractor.get().isVisible.onEach { SceneContainerFlag.assertInNewMode() } + /** Progress to a fully transitioned alternate bouncer. 1f represents fully transitioned. */ - val transitionToAlternateBouncerProgress = + val transitionToAlternateBouncerProgress: Flow<Float> = keyguardTransitionInteractor.transitionValue(ALTERNATE_BOUNCER) /** An observable for the scrim alpha. */ @@ -51,11 +62,16 @@ constructor( val registerForDismissGestures: Flow<Boolean> = transitionToAlternateBouncerProgress.map { it == 1f }.distinctUntilChanged() - fun showPrimaryBouncer() { + fun onTapped() { statusBarKeyguardViewManager.showPrimaryBouncer(/* scrimmed */ true) } - fun hideAlternateBouncer() { + fun onRemovedFromWindow() { + statusBarKeyguardViewManager.hideAlternateBouncer(false) + } + + fun onBackRequested() { statusBarKeyguardViewManager.hideAlternateBouncer(false) + dismissCallbackRegistry.notifyDismissCancelled() } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModel.kt index a460d515e0b2..9d8a7a81b9dc 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModel.kt @@ -51,7 +51,8 @@ constructor( onCancel = { 0f }, ) - val lockscreenAlpha: Flow<Float> = shortcutsAlpha + // Show immediately to avoid what can appear to be a flicker on device wakeup + val lockscreenAlpha: Flow<Float> = transitionAnimation.immediatelyTransitionTo(1f) val deviceEntryBackgroundViewAlpha: Flow<Float> = transitionAnimation.immediatelyTransitionTo(1f) diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt index 661da6d2af13..c2b5d98699b4 100644 --- a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt +++ b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt @@ -227,13 +227,33 @@ private fun inferTraceSectionName(): String { } /** + * Runs the given [block] in a new coroutine when `this` [View]'s Window's [WindowLifecycleState] is + * at least at [state] (or immediately after calling this function if the window is already at least + * at [state]), automatically canceling the work when the window is no longer at least at that + * state. + * + * [block] may be run multiple times, running once per every time this` [View]'s Window's + * [WindowLifecycleState] becomes at least at [state]. + */ +suspend fun View.repeatOnWindowLifecycle( + state: WindowLifecycleState, + block: suspend CoroutineScope.() -> Unit, +): Nothing { + when (state) { + WindowLifecycleState.ATTACHED -> repeatWhenAttachedToWindow(block) + WindowLifecycleState.VISIBLE -> repeatWhenWindowIsVisible(block) + WindowLifecycleState.FOCUSED -> repeatWhenWindowHasFocus(block) + } +} + +/** * Runs the given [block] every time the [View] becomes attached (or immediately after calling this * function, if the view was already attached), automatically canceling the work when the view * becomes detached. * * Only use from the main thread. * - * The [block] may be run multiple times, running once per every time the view is attached. + * [block] may be run multiple times, running once per every time the view is attached. */ @MainThread suspend fun View.repeatWhenAttachedToWindow(block: suspend CoroutineScope.() -> Unit): Nothing { @@ -249,7 +269,7 @@ suspend fun View.repeatWhenAttachedToWindow(block: suspend CoroutineScope.() -> * * Only use from the main thread. * - * The [block] may be run multiple times, running once per every time the window becomes visible. + * [block] may be run multiple times, running once per every time the window becomes visible. */ @MainThread suspend fun View.repeatWhenWindowIsVisible(block: suspend CoroutineScope.() -> Unit): Nothing { @@ -265,7 +285,7 @@ suspend fun View.repeatWhenWindowIsVisible(block: suspend CoroutineScope.() -> U * * Only use from the main thread. * - * The [block] may be run multiple times, running once per every time the window is focused. + * [block] may be run multiple times, running once per every time the window is focused. */ @MainThread suspend fun View.repeatWhenWindowHasFocus(block: suspend CoroutineScope.() -> Unit): Nothing { @@ -274,6 +294,21 @@ suspend fun View.repeatWhenWindowHasFocus(block: suspend CoroutineScope.() -> Un awaitCancellation() // satisfies return type of Nothing } +/** Lifecycle states for a [View]'s interaction with a [android.view.Window]. */ +enum class WindowLifecycleState { + /** Indicates that the [View] is attached to a [android.view.Window]. */ + ATTACHED, + /** + * Indicates that the [View] is attached to a [android.view.Window], and the window is visible. + */ + VISIBLE, + /** + * Indicates that the [View] is attached to a [android.view.Window], and the window is visible + * and focused. + */ + FOCUSED +} + private val View.isAttached get() = conflatedCallbackFlow { val onAttachListener = diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/SysUiViewModel.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/SysUiViewModel.kt index 0af5feaff3b2..77314813c34a 100644 --- a/packages/SystemUI/src/com/android/systemui/lifecycle/SysUiViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/lifecycle/SysUiViewModel.kt @@ -16,9 +16,10 @@ package com.android.systemui.lifecycle +import android.view.View import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.remember +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch /** Base class for all System UI view-models. */ abstract class SysUiViewModel : SafeActivatable() { @@ -37,8 +38,20 @@ abstract class SysUiViewModel : SafeActivatable() { fun <T : SysUiViewModel> rememberViewModel( key: Any = Unit, factory: () -> T, -): T { - val instance = remember(key) { factory() } - LaunchedEffect(instance) { instance.activate() } - return instance -} +): T = rememberActivated(key, factory) + +/** + * Invokes [block] in a new coroutine with a new [SysUiViewModel] that is automatically activated + * whenever `this` [View]'s Window's [WindowLifecycleState] is at least at + * [minWindowLifecycleState], and is automatically canceled once that is no longer the case. + */ +suspend fun <T : SysUiViewModel> View.viewModel( + minWindowLifecycleState: WindowLifecycleState, + factory: () -> T, + block: suspend CoroutineScope.(T) -> Unit, +): Nothing = + repeatOnWindowLifecycle(minWindowLifecycleState) { + val instance = factory() + launch { instance.activate() } + block(instance) + } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaController.kt index e17c0bb02ba8..9bea6e916e5a 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaController.kt @@ -18,6 +18,7 @@ package com.android.systemui.media.controls.ui.controller import android.content.Context import android.content.res.Configuration +import android.graphics.Rect import android.view.View import android.view.ViewGroup import androidx.annotation.VisibleForTesting @@ -125,6 +126,7 @@ constructor( /** single pane media container placed at the top of the notifications list */ var singlePaneContainer: MediaContainerView? = null private set + private var splitShadeContainer: ViewGroup? = null /** @@ -185,6 +187,13 @@ constructor( } } + fun isWithinMediaViewBounds(x: Int, y: Int): Boolean { + val bounds = Rect() + mediaHost.hostView.getBoundsOnScreen(bounds) + + return bounds.contains(x, y) + } + fun refreshMediaPosition(reason: String) { val currentState = statusBarStateController.state diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt index c5d7b25827ea..62a72184190d 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt @@ -888,8 +888,6 @@ constructor( heightInSceneContainerPx = height mediaCarouselScrollHandler.playerWidthPlusPadding = width + context.resources.getDimensionPixelSize(R.dimen.qs_media_padding) - mediaContent.minimumWidth = widthInSceneContainerPx - mediaContent.minimumHeight = heightInSceneContainerPx updatePlayers(recreateMedia = true) } @@ -1637,6 +1635,7 @@ constructor( "only active ${desiredHostState?.showsOnlyActiveMedia}" ) println("isSwipedAway: ${MediaPlayerData.isSwipedAway}") + println("allowMediaPlayerOnLockScreen: $allowMediaPlayerOnLockScreen") } } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt index 88a28bf998da..091b886c7ba4 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt @@ -1210,7 +1210,6 @@ constructor( (onCommunalNotDreaming && qsExpansion == 0.0f) || onCommunalDreamingAndShadeExpanding val location = when { - mediaFlags.isSceneContainerEnabled() -> desiredLocation dreamOverlayActive && dreamMediaComplicationActive -> LOCATION_DREAM_OVERLAY onCommunal -> LOCATION_COMMUNAL_HUB (qsExpansion > 0.0f || inSplitShade) && !onLockscreen -> LOCATION_QS diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java index b48b40986f73..875e505db1c6 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java @@ -94,6 +94,7 @@ import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; import com.android.systemui.statusbar.phone.SystemUIDialog; +import com.android.systemui.volume.panel.domain.interactor.VolumePanelGlobalStateInteractor; import dagger.assisted.Assisted; import dagger.assisted.AssistedFactory; @@ -173,6 +174,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, private float mActiveRadius; private FeatureFlags mFeatureFlags; private UserTracker mUserTracker; + private VolumePanelGlobalStateInteractor mVolumePanelGlobalStateInteractor; public enum BroadcastNotifyDialog { ACTION_FIRST_LAUNCH, @@ -195,6 +197,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, PowerExemptionManager powerExemptionManager, KeyguardManager keyGuardManager, FeatureFlags featureFlags, + VolumePanelGlobalStateInteractor volumePanelGlobalStateInteractor, UserTracker userTracker) { mContext = context; mPackageName = packageName; @@ -209,6 +212,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, mFeatureFlags = featureFlags; mUserTracker = userTracker; mToken = token; + mVolumePanelGlobalStateInteractor = volumePanelGlobalStateInteractor; InfoMediaManager imm = InfoMediaManager.createInstance(mContext, packageName, userHandle, lbm, token); mLocalMediaManager = new LocalMediaManager(mContext, lbm, imm, packageName); @@ -436,7 +440,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, launchIntent.putExtra(EXTRA_ROUTE_ID, routeId); launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); mCallback.dismissDialog(); - mActivityStarter.startActivity(launchIntent, true, controller); + startActivity(launchIntent, controller); } } @@ -447,7 +451,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, if (launchIntent != null) { launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); mCallback.dismissDialog(); - mActivityStarter.startActivity(launchIntent, true, controller); + startActivity(launchIntent, controller); } } @@ -951,10 +955,10 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, deepLinkIntent.putExtra( Settings.EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY, PAGE_CONNECTED_DEVICES_KEY); - mActivityStarter.startActivity(deepLinkIntent, true, controller); + startActivity(deepLinkIntent, controller); return; } - mActivityStarter.startActivity(launchIntent, true, controller); + startActivity(launchIntent, controller); } void launchLeBroadcastNotifyDialog(View mediaOutputDialog, BroadcastSender broadcastSender, @@ -998,6 +1002,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, mPowerExemptionManager, mKeyGuardManager, mFeatureFlags, + mVolumePanelGlobalStateInteractor, mUserTracker); MediaOutputBroadcastDialog dialog = new MediaOutputBroadcastDialog(mContext, true, broadcastSender, controller); @@ -1244,6 +1249,13 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, return !device.isVolumeFixed(); } + private void startActivity(Intent intent, ActivityTransitionAnimator.Controller controller) { + // Media Output dialog can be shown from the volume panel. This makes sure the panel is + // closed when navigating to another activity, so it doesn't stays on top of it + mVolumePanelGlobalStateInteractor.setVisible(false); + mActivityStarter.startActivity(intent, true, controller); + } + @Override public void onDevicesUpdated(List<NearbyDevice> nearbyDevices) throws RemoteException { mNearbyDeviceInfoMap.clear(); diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt index 4f062afc2af7..92db804d2730 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt @@ -17,8 +17,8 @@ package com.android.systemui.media.taptotransfer.receiver import android.animation.TimeInterpolator -import android.annotation.SuppressLint import android.animation.ValueAnimator +import android.annotation.SuppressLint import android.app.StatusBarManager import android.content.Context import android.graphics.Rect @@ -29,15 +29,15 @@ import android.os.Handler import android.os.PowerManager import android.view.Gravity import android.view.View +import android.view.View.ACCESSIBILITY_LIVE_REGION_ASSERTIVE +import android.view.View.ACCESSIBILITY_LIVE_REGION_NONE import android.view.ViewGroup import android.view.WindowManager import android.view.accessibility.AccessibilityManager -import android.view.View.ACCESSIBILITY_LIVE_REGION_ASSERTIVE -import android.view.View.ACCESSIBILITY_LIVE_REGION_NONE -import com.android.internal.widget.CachingIconView -import com.android.systemui.res.R import com.android.app.animation.Interpolators +import com.android.app.viewcapture.ViewCaptureAwareWindowManager import com.android.internal.logging.InstanceId +import com.android.internal.widget.CachingIconView import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.ui.binder.TintedIconViewBinder import com.android.systemui.dagger.SysUISingleton @@ -46,6 +46,7 @@ import com.android.systemui.dump.DumpManager import com.android.systemui.media.taptotransfer.MediaTttFlags import com.android.systemui.media.taptotransfer.common.MediaTttIcon import com.android.systemui.media.taptotransfer.common.MediaTttUtils +import com.android.systemui.res.R import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.temporarydisplay.TemporaryViewDisplayController @@ -71,7 +72,7 @@ open class MediaTttChipControllerReceiver @Inject constructor( private val commandQueue: CommandQueue, context: Context, logger: MediaTttReceiverLogger, - windowManager: WindowManager, + viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager, @Main mainExecutor: DelayableExecutor, accessibilityManager: AccessibilityManager, configurationController: ConfigurationController, @@ -88,7 +89,7 @@ open class MediaTttChipControllerReceiver @Inject constructor( ) : TemporaryViewDisplayController<ChipReceiverInfo, MediaTttReceiverLogger>( context, logger, - windowManager, + viewCaptureAwareWindowManager, mainExecutor, accessibilityManager, configurationController, diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt index d6affd2f0250..228b57603bed 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt @@ -32,6 +32,10 @@ import android.util.Log import android.view.View import android.view.ViewGroup import android.view.accessibility.AccessibilityEvent +import android.widget.ImageView +import androidx.annotation.ColorRes +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleRegistry @@ -52,6 +56,7 @@ import com.android.systemui.mediaprojection.appselector.view.MediaProjectionRece import com.android.systemui.res.R import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.AsyncActivityLauncher +import java.lang.IllegalArgumentException import javax.inject.Inject class MediaProjectionAppSelectorActivity( @@ -116,6 +121,7 @@ class MediaProjectionAppSelectorActivity( super.onCreate(savedInstanceState) controller.init() + setIcon() // we override AppList's AccessibilityDelegate set in ResolverActivity.onCreate because in // our case this delegate must extend RecyclerViewAccessibilityDelegate, otherwise // RecyclerView scrolling is broken @@ -298,6 +304,29 @@ class MediaProjectionAppSelectorActivity( override fun createContentPreviewView(parent: ViewGroup): ViewGroup = recentsViewController.createView(parent) + /** Set up intent for the [ChooserActivity] */ + private fun Intent.configureChooserIntent( + resources: Resources, + hostUserHandle: UserHandle, + personalProfileUserHandle: UserHandle, + ) { + // Specify the query intent to show icons for all apps on the chooser screen + val queryIntent = Intent(Intent.ACTION_MAIN).apply { addCategory(Intent.CATEGORY_LAUNCHER) } + putExtra(Intent.EXTRA_INTENT, queryIntent) + + // Update the title of the chooser + putExtra(Intent.EXTRA_TITLE, resources.getString(titleResId)) + + // Select host app's profile tab by default + val selectedProfile = + if (hostUserHandle == personalProfileUserHandle) { + PROFILE_PERSONAL + } else { + PROFILE_WORK + } + putExtra(EXTRA_SELECTED_PROFILE, selectedProfile) + } + private val hostUserHandle: UserHandle get() { val extras = @@ -321,6 +350,54 @@ class MediaProjectionAppSelectorActivity( return intent.getIntExtra(EXTRA_HOST_APP_UID, /* defaultValue= */ -1) } + /** + * The type of screen sharing being performed. Used to show the right text and icon in the + * activity. + */ + private val screenShareType: ScreenShareType? + get() { + if (!intent.hasExtra(EXTRA_SCREEN_SHARE_TYPE)) { + return null + } else { + val type = intent.getStringExtra(EXTRA_SCREEN_SHARE_TYPE) ?: return null + return try { + enumValueOf<ScreenShareType>(type) + } catch (e: IllegalArgumentException) { + null + } + } + } + + @get:StringRes + private val titleResId: Int + get() = + when (screenShareType) { + ScreenShareType.ShareToApp -> + R.string.media_projection_entry_share_app_selector_title + ScreenShareType.SystemCast -> + R.string.media_projection_entry_cast_app_selector_title + ScreenShareType.ScreenRecord -> R.string.screenrecord_app_selector_title + null -> R.string.screen_share_generic_app_selector_title + } + + @get:DrawableRes + private val iconResId: Int + get() = + when (screenShareType) { + ScreenShareType.ShareToApp -> R.drawable.ic_present_to_all + ScreenShareType.SystemCast -> R.drawable.ic_cast_connected + ScreenShareType.ScreenRecord -> R.drawable.ic_screenrecord + null -> R.drawable.ic_present_to_all + } + + @get:ColorRes + private val iconTintResId: Int? + get() = + when (screenShareType) { + ScreenShareType.ScreenRecord -> R.color.screenrecord_icon_color + else -> null + } + companion object { const val TAG = "MediaProjectionAppSelectorActivity" @@ -343,30 +420,18 @@ class MediaProjectionAppSelectorActivity( const val EXTRA_HOST_APP_UID = "launched_from_host_uid" const val KEY_CAPTURE_TARGET = "capture_region" - /** Set up intent for the [ChooserActivity] */ - private fun Intent.configureChooserIntent( - resources: Resources, - hostUserHandle: UserHandle, - personalProfileUserHandle: UserHandle - ) { - // Specify the query intent to show icons for all apps on the chooser screen - val queryIntent = - Intent(Intent.ACTION_MAIN).apply { addCategory(Intent.CATEGORY_LAUNCHER) } - putExtra(Intent.EXTRA_INTENT, queryIntent) - - // Update the title of the chooser - val title = resources.getString(R.string.screen_share_permission_app_selector_title) - putExtra(Intent.EXTRA_TITLE, title) - - // Select host app's profile tab by default - val selectedProfile = - if (hostUserHandle == personalProfileUserHandle) { - PROFILE_PERSONAL - } else { - PROFILE_WORK - } - putExtra(EXTRA_SELECTED_PROFILE, selectedProfile) - } + /** + * The type of screen sharing being performed. + * + * The value set for this extra should match the name of a [ScreenShareType]. + */ + const val EXTRA_SCREEN_SHARE_TYPE = "screen_share_type" + } + + private fun setIcon() { + val iconView = findViewById<ImageView>(R.id.media_projection_app_selector_icon) ?: return + iconView.setImageResource(iconResId) + iconTintResId?.let { iconView.setColorFilter(this.resources.getColor(it, this.theme)) } } private fun setAppListAccessibilityDelegate() { @@ -406,4 +471,14 @@ class MediaProjectionAppSelectorActivity( return delegate.onRequestSendAccessibilityEvent(host, child, event) } } + + /** Enum describing what type of app screen sharing is being performed. */ + enum class ScreenShareType { + /** The selected app will be cast to another device. */ + SystemCast, + /** The selected app will be shared to another app on the device. */ + ShareToApp, + /** The selected app will be recorded. */ + ScreenRecord, + } } diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt index 46aa0644035c..2ce7044897be 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt @@ -27,7 +27,6 @@ import android.view.ViewGroup import android.window.RemoteTransition import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView -import com.android.systemui.Flags.pssAppSelectorAbruptExitFix import com.android.systemui.Flags.pssAppSelectorRecentsSplitScreen import com.android.systemui.display.naturalBounds import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorResultHandler @@ -160,7 +159,7 @@ constructor( private fun createAnimation(task: RecentTask, view: View): ActivityOptions = - if (pssAppSelectorAbruptExitFix() && task.isForegroundTask) { + if (task.isForegroundTask) { // When the selected task is in the foreground, the scale up animation doesn't work. // We fallback to the default close animation. ActivityOptions.makeCustomTaskAnimation( 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 3c83db3f258d..18c6f53630a4 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java @@ -73,8 +73,7 @@ import javax.inject.Inject; import dagger.Lazy; -public class MediaProjectionPermissionActivity extends Activity - implements DialogInterface.OnClickListener { +public class MediaProjectionPermissionActivity extends Activity { private static final String TAG = "MediaProjectionPermissionActivity"; private static final float MAX_APP_NAME_SIZE_PX = 500f; private static final String ELLIPSIS = "\u2026"; @@ -269,7 +268,8 @@ public class MediaProjectionPermissionActivity extends Activity Consumer<BaseMediaProjectionPermissionDialogDelegate<AlertDialog>> onStartRecordingClicked = dialog -> { ScreenShareOption selectedOption = dialog.getSelectedScreenShareOption(); - grantMediaProjectionPermission(selectedOption.getMode()); + grantMediaProjectionPermission( + selectedOption.getMode(), hasCastingCapabilities); }; Runnable onCancelClicked = () -> finish(RECORD_CANCEL, /* projection= */ null); if (hasCastingCapabilities) { @@ -305,19 +305,6 @@ public class MediaProjectionPermissionActivity extends Activity } } - @Override - public void onClick(DialogInterface dialog, int which) { - if (which == AlertDialog.BUTTON_POSITIVE) { - grantMediaProjectionPermission(ENTIRE_SCREEN); - } else { - if (mDialog != null) { - mDialog.dismiss(); - } - setResult(RESULT_CANCELED); - finish(RECORD_CANCEL, /* projection= */ null); - } - } - private void setUpDialog(AlertDialog dialog) { SystemUIDialog.registerDismissListener(dialog); SystemUIDialog.applyFlags(dialog); @@ -345,25 +332,21 @@ public class MediaProjectionPermissionActivity extends Activity return false; } - private void grantMediaProjectionPermission(int screenShareMode) { + private void grantMediaProjectionPermission( + int screenShareMode, boolean hasCastingCapabilities) { try { + IMediaProjection projection = MediaProjectionServiceHelper.createOrReuseProjection( + mUid, mPackageName, mReviewGrantedConsentRequired); if (screenShareMode == ENTIRE_SCREEN) { - final IMediaProjection projection = - MediaProjectionServiceHelper.createOrReuseProjection(mUid, mPackageName, - mReviewGrantedConsentRequired); final Intent intent = new Intent(); - intent.putExtra(MediaProjectionManager.EXTRA_MEDIA_PROJECTION, - projection.asBinder()); + setCommonIntentExtras(intent, hasCastingCapabilities, projection); setResult(RESULT_OK, intent); finish(RECORD_CONTENT_DISPLAY, projection); } if (screenShareMode == SINGLE_APP) { - IMediaProjection projection = MediaProjectionServiceHelper.createOrReuseProjection( - mUid, mPackageName, mReviewGrantedConsentRequired); final Intent intent = new Intent(this, MediaProjectionAppSelectorActivity.class); - intent.putExtra(MediaProjectionManager.EXTRA_MEDIA_PROJECTION, - projection.asBinder()); + setCommonIntentExtras(intent, hasCastingCapabilities, projection); intent.putExtra(MediaProjectionAppSelectorActivity.EXTRA_HOST_APP_USER_HANDLE, getHostUserHandle()); intent.putExtra( @@ -391,6 +374,19 @@ public class MediaProjectionPermissionActivity extends Activity } } + private void setCommonIntentExtras( + Intent intent, + boolean hasCastingCapabilities, + IMediaProjection projection) throws RemoteException { + intent.putExtra(MediaProjectionManager.EXTRA_MEDIA_PROJECTION, + projection.asBinder()); + intent.putExtra( + MediaProjectionAppSelectorActivity.EXTRA_SCREEN_SHARE_TYPE, + hasCastingCapabilities + ? MediaProjectionAppSelectorActivity.ScreenShareType.SystemCast.name() + : MediaProjectionAppSelectorActivity.ScreenShareType.ShareToApp.name()); + } + private UserHandle getHostUserHandle() { return UserHandle.getUserHandleForUid(getLaunchedFromUid()); } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java index 13a786a623dd..ac878c2d698d 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java @@ -38,6 +38,7 @@ import android.content.Context; import android.content.res.Configuration; import android.database.ContentObserver; import android.inputmethodservice.InputMethodService; +import android.inputmethodservice.InputMethodService.ImeWindowVisibility; import android.net.Uri; import android.os.Bundle; import android.os.Handler; @@ -516,7 +517,7 @@ public final class NavBarHelper implements * @return Whether the IME is shown on top of the screen given the {@code vis} flag of * {@link InputMethodService} and the keyguard states. */ - public boolean isImeShown(int vis) { + public boolean isImeShown(@ImeWindowVisibility int vis) { View shadeWindowView = mNotificationShadeWindowController.getWindowRootView(); boolean isKeyguardShowing = mKeyguardStateController.isShowing(); boolean imeVisibleOnShade = shadeWindowView != null && shadeWindowView.isAttachedToWindow() diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java index e2ba76141845..a8b979e05276 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java @@ -16,11 +16,16 @@ package com.android.systemui.navigationbar; +import static com.android.systemui.Flags.enableViewCaptureTracing; +import static com.android.systemui.util.ConvenienceExtensionsKt.toKotlinLazy; + import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.WindowManager; +import com.android.app.viewcapture.ViewCapture; +import com.android.app.viewcapture.ViewCaptureAwareWindowManager; import com.android.systemui.dagger.qualifiers.DisplayId; import com.android.systemui.navigationbar.NavigationBarComponent.NavigationBarScope; import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler; @@ -28,6 +33,7 @@ import com.android.systemui.navigationbar.views.NavigationBarFrame; import com.android.systemui.navigationbar.views.NavigationBarView; import com.android.systemui.res.R; +import dagger.Lazy; import dagger.Module; import dagger.Provides; @@ -73,4 +79,15 @@ public interface NavigationBarModule { static WindowManager provideWindowManager(@DisplayId Context context) { return context.getSystemService(WindowManager.class); } + + /** A ViewCaptureAwareWindowManager specific to the display's context. */ + @Provides + @NavigationBarScope + @DisplayId + static ViewCaptureAwareWindowManager provideViewCaptureAwareWindowManager( + @DisplayId WindowManager windowManager, Lazy<ViewCapture> daggerLazyViewCapture) { + return new ViewCaptureAwareWindowManager(windowManager, + /* lazyViewCapture= */ toKotlinLazy(daggerLazyViewCapture), + /* isViewCaptureEnabled= */ enableViewCaptureTracing()); + } } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java index 15b1e4de878a..cb0bb4abb423 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java @@ -42,6 +42,8 @@ import android.content.Context; import android.graphics.Rect; import android.hardware.display.DisplayManager; import android.inputmethodservice.InputMethodService; +import android.inputmethodservice.InputMethodService.BackDispositionMode; +import android.inputmethodservice.InputMethodService.ImeWindowVisibility; import android.os.RemoteException; import android.os.Trace; import android.util.Log; @@ -424,8 +426,8 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, } @Override - public void setImeWindowStatus(int displayId, int vis, int backDisposition, - boolean showImeSwitcher) { + public void setImeWindowStatus(int displayId, @ImeWindowVisibility int vis, + @BackDispositionMode int backDisposition, boolean showImeSwitcher) { boolean imeShown = mNavBarHelper.isImeShown(vis); if (!imeShown) { // Count imperceptible changes as visible so we transition taskbar out quickly. diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java index 0fe4d3620227..388272f6e32a 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java @@ -1058,8 +1058,6 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack mEdgeBackPlugin.setIsLeftPanel(mIsOnLeftEdge); mEdgeBackPlugin.onMotionEvent(ev); dispatchToBackAnimation(ev); - mOverviewProxyService.updateContextualEduStats(mIsTrackpadThreeFingerSwipe, - GestureType.BACK); } if (mLogGesture || mIsTrackpadThreeFingerSwipe) { mDownPoint.set(ev.getX(), ev.getY()); @@ -1136,6 +1134,8 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack if (mAllowGesture) { if (mBackAnimation != null) { mBackAnimation.onThresholdCrossed(); + mOverviewProxyService.updateContextualEduStats( + mIsTrackpadThreeFingerSwipe, GestureType.BACK); } else { pilferPointers(); } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java index 7b248eb876a8..c706c3e97c96 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java @@ -68,6 +68,8 @@ import android.graphics.Point; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Region; +import android.inputmethodservice.InputMethodService.BackDispositionMode; +import android.inputmethodservice.InputMethodService.ImeWindowVisibility; import android.os.Binder; import android.os.Bundle; import android.os.Handler; @@ -102,6 +104,7 @@ import android.view.inputmethod.InputMethodManager; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; +import com.android.app.viewcapture.ViewCaptureAwareWindowManager; import com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEvent; @@ -196,6 +199,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements private final Context mContext; private final Bundle mSavedState; private final WindowManager mWindowManager; + private final ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager; private final AccessibilityManager mAccessibilityManager; private final DeviceProvisionedController mDeviceProvisionedController; private final StatusBarStateController mStatusBarStateController; @@ -556,6 +560,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements @Nullable Bundle savedState, @DisplayId Context context, @DisplayId WindowManager windowManager, + @DisplayId ViewCaptureAwareWindowManager viewCaptureAwareWindowManager, Lazy<AssistManager> assistManagerLazy, AccessibilityManager accessibilityManager, DeviceProvisionedController deviceProvisionedController, @@ -601,6 +606,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements mContext = context; mSavedState = savedState; mWindowManager = windowManager; + mViewCaptureAwareWindowManager = viewCaptureAwareWindowManager; mAccessibilityManager = accessibilityManager; mDeviceProvisionedController = deviceProvisionedController; mStatusBarStateController = statusBarStateController; @@ -721,7 +727,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements if (DEBUG) Log.v(TAG, "addNavigationBar: about to add " + mView); - mWindowManager.addView(mFrame, + mViewCaptureAwareWindowManager.addView(mFrame, getBarLayoutParams(mContext.getResources().getConfiguration().windowConfiguration .getRotation())); mDisplayId = mContext.getDisplayId(); @@ -764,7 +770,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements mCommandQueue.removeCallback(this); Trace.beginSection("NavigationBar#removeViewImmediate"); try { - mWindowManager.removeViewImmediate(mView.getRootView()); + mViewCaptureAwareWindowManager.removeViewImmediate(mView.getRootView()); } finally { Trace.endSection(); } @@ -866,7 +872,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements if (mOrientationHandle != null) { resetSecondaryHandle(); getBarTransitions().removeDarkIntensityListener(mOrientationHandleIntensityListener); - mWindowManager.removeView(mOrientationHandle); + mViewCaptureAwareWindowManager.removeView(mOrientationHandle); mOrientationHandle.getViewTreeObserver().removeOnGlobalLayoutListener( mOrientationHandleGlobalLayoutListener); } @@ -937,7 +943,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements mOrientationParams.setTitle("SecondaryHomeHandle" + mContext.getDisplayId()); mOrientationParams.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT; - mWindowManager.addView(mOrientationHandle, mOrientationParams); + mViewCaptureAwareWindowManager.addView(mOrientationHandle, mOrientationParams); mOrientationHandle.setVisibility(View.GONE); logNavbarOrientation("initSecondaryHomeHandleForRotation"); @@ -1094,8 +1100,8 @@ public class NavigationBar extends ViewController<NavigationBarView> implements // ----- CommandQueue Callbacks ----- @Override - public void setImeWindowStatus(int displayId, int vis, int backDisposition, - boolean showImeSwitcher) { + public void setImeWindowStatus(int displayId, @ImeWindowVisibility int vis, + @BackDispositionMode int backDisposition, boolean showImeSwitcher) { if (displayId != mDisplayId) { return; } diff --git a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneActionsViewModel.kt index 2d2b869a49ea..9fb09c03834c 100644 --- a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneActionsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneActionsViewModel.kt @@ -29,9 +29,6 @@ import dagger.assisted.AssistedInject /** * Models the UI state for the user actions that the user can perform to navigate to other scenes. - * - * Different from the [NotificationsShadeSceneContentViewModel] which models the _content_ of the - * scene. */ class NotificationsShadeSceneActionsViewModel @AssistedInject diff --git a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneContentViewModel.kt deleted file mode 100644 index c1c7320c42db..000000000000 --- a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneContentViewModel.kt +++ /dev/null @@ -1,49 +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. - */ - -@file:OptIn(ExperimentalCoroutinesApi::class) - -package com.android.systemui.notifications.ui.viewmodel - -import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor -import com.android.systemui.scene.domain.interactor.SceneInteractor -import com.android.systemui.shade.ui.viewmodel.BaseShadeSceneViewModel -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject -import kotlinx.coroutines.ExperimentalCoroutinesApi - -/** - * Models UI state used to render the content of the notifications shade scene. - * - * Different from [NotificationsShadeSceneActionsViewModel], which only models user actions that can - * be performed to navigate to other scenes. - */ -class NotificationsShadeSceneContentViewModel -@AssistedInject -constructor( - deviceEntryInteractor: DeviceEntryInteractor, - sceneInteractor: SceneInteractor, -) : - BaseShadeSceneViewModel( - deviceEntryInteractor, - sceneInteractor, - ) { - - @AssistedFactory - interface Factory { - fun create(): NotificationsShadeSceneContentViewModel - } -} diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java index 9939075b77d2..1511f31a3f92 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java @@ -259,6 +259,13 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { } /** + * @return height with the squishiness fraction applied. + */ + int getSquishedQqsHeight() { + return mHeader.getSquishedHeight(); + } + + /** * Returns the size of QS (or the QSCustomizer), regardless of the measured size of this view * @return size in pixels of QS (or QSCustomizer) */ @@ -267,6 +274,13 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { : mQSPanel.getMeasuredHeight(); } + /** + * @return height with the squishiness fraction applied. + */ + int getSquishedQsHeight() { + return mQSPanel.getSquishedHeight(); + } + public void setExpansion(float expansion) { mQsExpansion = expansion; if (mQSPanelContainer != null) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java index a6fd35a9ee37..0b37b5b7be3d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java @@ -992,11 +992,25 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl return mContainer.getQqsHeight(); } + /** + * @return height with the squishiness fraction applied. + */ + public int getSquishedQqsHeight() { + return mContainer.getSquishedQqsHeight(); + } + public int getQSHeight() { return mContainer.getQsHeight(); } /** + * @return height with the squishiness fraction applied. + */ + public int getSquishedQsHeight() { + return mContainer.getSquishedQsHeight(); + } + + /** * Pass the size of the navbar when it's at the bottom of the device so it can be used as * padding * @param padding size of the bottom nav bar in px diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index 032891fa715e..d3bed27ab2ab 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -733,6 +733,30 @@ public class QSPanel extends LinearLayout implements Tunable { mCanCollapse = canCollapse; } + /** + * @return height with the {@link QSPanel#setSquishinessFraction(float)} applied. + */ + public int getSquishedHeight() { + if (mFooter != null) { + final ViewGroup.LayoutParams footerLayoutParams = mFooter.getLayoutParams(); + final int footerBottomMargin; + if (footerLayoutParams instanceof MarginLayoutParams) { + footerBottomMargin = ((MarginLayoutParams) footerLayoutParams).bottomMargin; + } else { + footerBottomMargin = 0; + } + // This is the distance between the top of the QSPanel and the last view in the + // layout (which is the effective the bottom) + return mFooter.getBottom() + footerBottomMargin - getTop(); + } + if (mTileLayout != null) { + // Footer absence means that the panel is in the QQS. In this case it's just height + // of the tiles + paddings. + return mTileLayout.getTilesHeight() + getPaddingBottom() + getPaddingTop(); + } + return getHeight(); + } + @Nullable @VisibleForTesting View getMediaPlaceholder() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java index 5a3f1c0b7426..8fde52c910da 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java @@ -123,4 +123,11 @@ public class QuickStatusBarHeader extends FrameLayout { lp.setMarginEnd(marginEnd); view.setLayoutParams(lp); } + + /** + * @return height with the squishiness fraction applied. + */ + public int getSquishedHeight() { + return mHeaderQsPanel.getSquishedHeight(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt index 9b8dba166274..9fb1d46c4241 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt @@ -37,19 +37,22 @@ constructor( override fun map(config: QSTileConfig, data: AirplaneModeTileModel): QSTileState = QSTileState.build(resources, theme, config.uiConfig) { - val icon = + iconRes = + if (data.isEnabled) { + R.drawable.qs_airplane_icon_on + } else { + R.drawable.qs_airplane_icon_off + } + + icon = { Icon.Loaded( resources.getDrawable( - if (data.isEnabled) { - R.drawable.qs_airplane_icon_on - } else { - R.drawable.qs_airplane_icon_off - }, + iconRes!!, theme, ), contentDescription = null ) - this.icon = { icon } + } if (data.isEnabled) { activationState = QSTileState.ActivationState.ACTIVE secondaryLabel = resources.getStringArray(R.array.tile_states_airplane)[2] diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/CustomTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/CustomTileMapper.kt index 875079cae8eb..984228d80b7f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/CustomTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/CustomTileMapper.kt @@ -41,16 +41,25 @@ constructor( ) : QSTileDataToStateMapper<CustomTileDataModel> { override fun map(config: QSTileConfig, data: CustomTileDataModel): QSTileState { - val userContext = context.createContextAsUser(UserHandle(data.user.identifier), 0) + val userContext = + try { + context.createContextAsUser(UserHandle(data.user.identifier), 0) + } catch (exception: IllegalStateException) { + null + } val iconResult = - getIconProvider( - userContext = userContext, - icon = data.tile.icon, - callingAppUid = data.callingAppUid, - packageName = data.componentName.packageName, - defaultIcon = data.defaultTileIcon, - ) + if (userContext != null) { + getIconProvider( + userContext = userContext, + icon = data.tile.icon, + callingAppUid = data.callingAppUid, + packageName = data.componentName.packageName, + defaultIcon = data.defaultTileIcon, + ) + } else { + IconResult({ null }, true) + } return QSTileState.build(iconResult.iconProvider, data.tile.label) { var tileState: Int = data.tile.state @@ -61,7 +70,7 @@ constructor( icon = iconResult.iconProvider activationState = if (iconResult.failedToLoad) { - QSTileState.ActivationState.INACTIVE + QSTileState.ActivationState.UNAVAILABLE } else { QSTileState.ActivationState.valueOf(tileState) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractor.kt index eec5d3d915f8..204ead3fe29c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractor.kt @@ -79,6 +79,7 @@ constructor( flowOf( InternetTileModel.Active( secondaryTitle = secondary, + iconId = wifiIcon.icon.res, icon = Icon.Loaded(context.getDrawable(wifiIcon.icon.res)!!, null), stateDescription = wifiIcon.contentDescription, contentDescription = ContentDescription.Loaded("$internetLabel,$secondary"), diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt index ae2f32aae874..dfcf21628c3b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt @@ -34,7 +34,6 @@ import com.android.systemui.plugins.qs.QSContainerController import com.android.systemui.qs.QSContainerImpl import com.android.systemui.qs.QSImpl import com.android.systemui.qs.dagger.QSSceneComponent -import com.android.systemui.qs.tiles.viewmodel.StubQSTileViewModel.state import com.android.systemui.res.R import com.android.systemui.settings.brightness.MirrorController import com.android.systemui.shade.domain.interactor.ShadeInteractor @@ -126,12 +125,18 @@ interface QSSceneAdapter { /** The current height of QQS in the current [qsView], or 0 if there's no view. */ val qqsHeight: Int + /** @return height with the squishiness fraction applied. */ + val squishedQqsHeight: Int + /** * The current height of QS in the current [qsView], or 0 if there's no view. If customizing, it * will return the height allocated to the customizer. */ val qsHeight: Int + /** @return height with the squishiness fraction applied. */ + val squishedQsHeight: Int + /** Compatibility for use by LockscreenShadeTransitionController. Matches default from [QS] */ val isQsFullyCollapsed: Boolean get() = true @@ -273,9 +278,15 @@ constructor( override val qqsHeight: Int get() = qsImpl.value?.qqsHeight ?: 0 + override val squishedQqsHeight: Int + get() = qsImpl.value?.squishedQqsHeight ?: 0 + override val qsHeight: Int get() = qsImpl.value?.qsHeight ?: 0 + override val squishedQsHeight: Int + get() = qsImpl.value?.squishedQsHeight ?: 0 + // If value is null, there's no QS and therefore it's fully collapsed. override val isQsFullyCollapsed: Boolean get() = qsImpl.value?.isFullyCollapsed ?: true diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index 15366d592adf..ecf816b263ff 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -333,6 +333,13 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } @Override + public void updateContextualEduStats(boolean isTrackpadGesture, String gestureType) { + verifyCallerAndClearCallingIdentityPostMain("updateContextualEduStats", + () -> mHandler.post(() -> OverviewProxyService.this.updateContextualEduStats( + isTrackpadGesture, GestureType.valueOf(gestureType)))); + } + + @Override public void setHomeRotationEnabled(boolean enabled) { verifyCallerAndClearCallingIdentityPostMain("setHomeRotationEnabled", () -> mHandler.post(() -> notifyHomeRotationEnabled(enabled))); diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt index 98a61df4ca55..863a899b6f4c 100644 --- a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt +++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt @@ -24,6 +24,7 @@ import android.content.res.Resources import android.net.Uri import android.os.Handler import android.os.UserHandle +import android.util.Log import com.android.internal.logging.UiEventLogger import com.android.systemui.animation.DialogTransitionAnimator import com.android.systemui.dagger.qualifiers.LongRunning @@ -71,6 +72,7 @@ constructor( override fun provideRecordingServiceStrings(): RecordingServiceStrings = IrsStrings(resources) override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + Log.d(getTag(), "handling action: ${intent?.action}") when (intent?.action) { ACTION_START -> { bgExecutor.execute { @@ -95,7 +97,7 @@ constructor( bgExecutor.execute { mNotificationManager.cancelAsUser( null, - mNotificationId, + intent.getIntExtra(EXTRA_NOTIFICATION_ID, mNotificationId), UserHandle(mUserContextTracker.userContext.userId) ) diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt index 5b5013352c29..e73664d43952 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt @@ -25,6 +25,7 @@ import com.android.internal.logging.UiEventLogger import com.android.systemui.CoreStartable import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor import com.android.systemui.bouncer.shared.logging.BouncerUiEvent @@ -132,6 +133,7 @@ constructor( private val keyguardEnabledInteractor: KeyguardEnabledInteractor, private val dismissCallbackRegistry: DismissCallbackRegistry, private val statusBarStateController: SysuiStatusBarStateController, + private val alternateBouncerInteractor: AlternateBouncerInteractor, ) : CoreStartable { private val centralSurfaces: CentralSurfaces? get() = centralSurfacesOptLazy.get().getOrNull() @@ -228,13 +230,16 @@ constructor( }, headsUpInteractor.isHeadsUpOrAnimatingAway, occlusionInteractor.invisibleDueToOcclusion, + alternateBouncerInteractor.isVisible, ) { visibilityForTransitionState, isHeadsUpOrAnimatingAway, invisibleDueToOcclusion, + isAlternateBouncerVisible, -> when { isHeadsUpOrAnimatingAway -> true to "showing a HUN" + isAlternateBouncerVisible -> true to "showing alternate bouncer" invisibleDueToOcclusion -> false to "invisible due to occlusion" else -> visibilityForTransitionState } @@ -351,9 +356,10 @@ constructor( ) } val isOnLockscreen = renderedScenes.contains(Scenes.Lockscreen) - val isOnBouncer = renderedScenes.contains(Scenes.Bouncer) + val isAlternateBouncerVisible = alternateBouncerInteractor.isVisibleState() + val isOnPrimaryBouncer = renderedScenes.contains(Scenes.Bouncer) if (!deviceUnlockStatus.isUnlocked) { - return@mapNotNull if (isOnLockscreen || isOnBouncer) { + return@mapNotNull if (isOnLockscreen || isOnPrimaryBouncer) { // Already on lockscreen or bouncer, no need to change scenes. null } else { @@ -365,26 +371,44 @@ constructor( } if ( - isOnBouncer && + isOnPrimaryBouncer && deviceUnlockStatus.deviceUnlockSource == DeviceUnlockSource.TrustAgent ) { uiEventLogger.log(BouncerUiEvent.BOUNCER_DISMISS_EXTENDED_ACCESS) } when { - isOnBouncer -> - // When the device becomes unlocked in Bouncer, go to previous scene, - // or Gone. + isAlternateBouncerVisible -> { + // When the device becomes unlocked when the alternate bouncer is + // showing, always hide the alternate bouncer... + alternateBouncerInteractor.hide() + + // ... and go to Gone or stay on the current scene + if ( + isOnLockscreen || + !statusBarStateController.leaveOpenOnKeyguardHide() + ) { + Scenes.Gone to + "device was unlocked with alternate bouncer showing" + + " and shade didn't need to be left open" + } else { + null + } + } + isOnPrimaryBouncer -> + // When the device becomes unlocked in primary Bouncer, + // go to previous scene or Gone. if ( previousScene.value == Scenes.Lockscreen || !statusBarStateController.leaveOpenOnKeyguardHide() ) { Scenes.Gone to - "device was unlocked in Bouncer scene and shade" + + "device was unlocked with bouncer showing and shade" + " didn't need to be left open" } else { val prevScene = previousScene.value (prevScene ?: Scenes.Gone) to - "device was unlocked in Bouncer scene, from sceneKey=$prevScene" + "device was unlocked with primary bouncer showing," + + " from sceneKey=$prevScene" } isOnLockscreen -> // The lockscreen should be dismissed automatically in 2 scenarios: diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt index bccbb1130bcc..f6924f222e11 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt @@ -5,15 +5,18 @@ import android.util.AttributeSet import android.view.MotionEvent import android.view.View import android.view.WindowInsets +import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerDependencies import com.android.systemui.scene.shared.model.Scene import com.android.systemui.scene.shared.model.SceneContainerConfig import com.android.systemui.scene.shared.model.SceneDataSourceDelegator import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel import com.android.systemui.shade.TouchLogger import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow /** A root view of the main SysUI window that supports scenes. */ +@ExperimentalCoroutinesApi class SceneWindowRootView( context: Context, attrs: AttributeSet?, @@ -35,6 +38,7 @@ class SceneWindowRootView( scenes: Set<Scene>, layoutInsetController: LayoutInsetsController, sceneDataSourceDelegator: SceneDataSourceDelegator, + alternateBouncerDependencies: AlternateBouncerDependencies, ) { this.viewModel = viewModel setLayoutInsetsController(layoutInsetController) @@ -49,6 +53,7 @@ class SceneWindowRootView( super.setVisibility(if (isVisible) View.VISIBLE else View.INVISIBLE) }, dataSourceDelegator = sceneDataSourceDelegator, + alternateBouncerDependencies = alternateBouncerDependencies, ) } diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt index d31d6f4137c1..73a8e4c24578 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt @@ -37,6 +37,8 @@ import com.android.internal.policy.ScreenDecorationsUtils import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation import com.android.systemui.common.ui.compose.windowinsets.DisplayCutout import com.android.systemui.common.ui.compose.windowinsets.ScreenDecorProvider +import com.android.systemui.keyguard.ui.composable.AlternateBouncer +import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerDependencies import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.res.R import com.android.systemui.scene.shared.flag.SceneContainerFlag @@ -48,12 +50,14 @@ import com.android.systemui.scene.ui.composable.SceneContainer import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch +@ExperimentalCoroutinesApi object SceneWindowRootViewBinder { /** Binds between the view and view-model pertaining to a specific scene container. */ @@ -66,6 +70,7 @@ object SceneWindowRootViewBinder { scenes: Set<Scene>, onVisibilityChangedInternal: (isVisible: Boolean) -> Unit, dataSourceDelegator: SceneDataSourceDelegator, + alternateBouncerDependencies: AlternateBouncerDependencies, ) { val unsortedSceneByKey: Map<SceneKey, Scene> = scenes.associateBy { scene -> scene.key } val sortedSceneByKey: Map<SceneKey, Scene> = buildMap { @@ -120,6 +125,14 @@ object SceneWindowRootViewBinder { sharedNotificationContainer ) view.addView(sharedNotificationContainer) + + // TODO (b/358354906): use an overlay for the alternate bouncer + view.addView( + createAlternateBouncerView( + context = view.context, + alternateBouncerDependencies = alternateBouncerDependencies, + ) + ) } launch { @@ -164,6 +177,19 @@ object SceneWindowRootViewBinder { } } + private fun createAlternateBouncerView( + context: Context, + alternateBouncerDependencies: AlternateBouncerDependencies, + ): ComposeView { + return ComposeView(context).apply { + setContent { + AlternateBouncer( + alternateBouncerDependencies = alternateBouncerDependencies, + ) + } + } + } + // TODO(b/298525212): remove once Compose exposes window inset bounds. private fun displayCutoutFromWindowInsets( scope: CoroutineScope, diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java index 108564ca080e..700253babb82 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java @@ -80,6 +80,7 @@ public class RecordingService extends Service implements ScreenMediaRecorderList "com.android.systemui.screenrecord.STOP_FROM_NOTIF"; protected static final String ACTION_SHARE = "com.android.systemui.screenrecord.SHARE"; private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF"; + protected static final String EXTRA_NOTIFICATION_ID = "notification_id"; private final RecordingController mController; protected final KeyguardDismissUtil mKeyguardDismissUtil; @@ -542,7 +543,8 @@ public class RecordingService extends Service implements ScreenMediaRecorderList private Intent getShareIntent(Context context, Uri path) { return new Intent(context, this.getClass()).setAction(ACTION_SHARE) - .putExtra(EXTRA_PATH, path); + .putExtra(EXTRA_PATH, path) + .putExtra(EXTRA_NOTIFICATION_ID, mNotificationId); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt index b54bf6ca9ef8..f3357ee53b7f 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt @@ -146,6 +146,10 @@ class ScreenRecordPermissionDialogDelegate( hostUserHandle ) intent.putExtra(MediaProjectionAppSelectorActivity.EXTRA_HOST_APP_UID, hostUid) + intent.putExtra( + MediaProjectionAppSelectorActivity.EXTRA_SCREEN_SHARE_TYPE, + MediaProjectionAppSelectorActivity.ScreenShareType.ScreenRecord.name, + ) activityStarter.startActivity(intent, /* dismissShade= */ true) } dialog.dismiss() @@ -270,15 +274,18 @@ class ScreenRecordPermissionDialogDelegate( return listOf( ScreenShareOption( SINGLE_APP, - R.string.screen_share_permission_dialog_option_single_app, + R.string.screenrecord_permission_dialog_option_text_single_app, R.string.screenrecord_permission_dialog_warning_single_app, - startButtonText = R.string.screenrecord_permission_dialog_continue, + startButtonText = + R.string + .media_projection_entry_generic_permission_dialog_continue_single_app, ), ScreenShareOption( ENTIRE_SCREEN, - R.string.screen_share_permission_dialog_option_entire_screen, + R.string.screenrecord_permission_dialog_option_text_entire_screen, R.string.screenrecord_permission_dialog_warning_entire_screen, - startButtonText = R.string.screenrecord_permission_dialog_continue, + startButtonText = + R.string.screenrecord_permission_dialog_continue_entire_screen, ) ) } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/AssistContentRequester.java b/packages/SystemUI/src/com/android/systemui/screenshot/AssistContentRequester.java index 6ff153569ad7..74513f728601 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/AssistContentRequester.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/AssistContentRequester.java @@ -91,7 +91,7 @@ public class AssistContentRequester { try { boolean success = mActivityTaskManager.requestAssistDataForTask( new AssistDataReceiver(callback, this), taskId, mPackageName, - mAttributionTag); + mAttributionTag, false /* fetchStructure */); if (!success) { callback.onAssistContentAvailable(null); } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotWindow.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotWindow.kt index 644e12cba6fc..c4fe7a428084 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotWindow.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotWindow.kt @@ -31,6 +31,7 @@ import android.view.Window import android.view.WindowInsets import android.view.WindowManager import android.window.WindowContext +import com.android.app.viewcapture.ViewCaptureAwareWindowManager import com.android.internal.policy.PhoneWindow import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -41,6 +42,7 @@ class ScreenshotWindow @AssistedInject constructor( private val windowManager: WindowManager, + private val viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager, private val context: Context, @Assisted private val display: Display, ) { @@ -95,7 +97,7 @@ constructor( Log.d(TAG, "attachWindow") } attachRequested = true - windowManager.addView(decorView, params) + viewCaptureAwareWindowManager.addView(decorView, params) decorView.requestApplyInsets() decorView.requireViewById<ViewGroup>(R.id.content).apply { @@ -133,7 +135,7 @@ constructor( if (LogConfig.DEBUG_WINDOW) { Log.d(TAG, "Removing screenshot window") } - windowManager.removeViewImmediate(decorView) + viewCaptureAwareWindowManager.removeViewImmediate(decorView) detachRequested = false } if (attachRequested && !detachRequested) { diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt index 15bbef02196a..22f62fcfe172 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt @@ -55,10 +55,12 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInterac import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.media.controls.ui.controller.KeyguardMediaController import com.android.systemui.res.R import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.model.SceneDataSourceDelegator import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf import com.android.systemui.util.kotlin.collectFlow @@ -88,6 +90,8 @@ constructor( private val communalContent: CommunalContent, @Communal private val dataSourceDelegator: SceneDataSourceDelegator, private val notificationStackScrollLayoutController: NotificationStackScrollLayoutController, + private val keyguardMediaController: KeyguardMediaController, + private val lockscreenSmartspaceController: LockscreenSmartspaceController ) : LifecycleOwner { private class CommunalWrapper(context: Context) : FrameLayout(context) { @@ -445,7 +449,12 @@ constructor( // the touch. if ( !hubShowing && - !notificationStackScrollLayoutController.isBelowLastNotification(ev.x, ev.y) + (!notificationStackScrollLayoutController.isBelowLastNotification(ev.x, ev.y) || + keyguardMediaController.isWithinMediaViewBounds(ev.x.toInt(), ev.y.toInt()) || + lockscreenSmartspaceController.isWithinSmartspaceBounds( + ev.x.toInt(), + ev.y.toInt() + )) ) { return false } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 104d4b5427d4..65a59f50d1bf 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -1339,6 +1339,10 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump "NotificationPanelViewController.updateResources"); if (splitShadeChanged) { + if (isPanelVisibleBecauseOfHeadsUp()) { + // workaround for b/324642496, because HUNs set state to OPENING + onPanelStateChanged(STATE_CLOSED); + } onSplitShadeEnabledChanged(); } diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java index 0a092a088e69..16aef6586ee9 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java @@ -2252,8 +2252,11 @@ public class QuickSettingsControllerImpl implements QuickSettingsController, Dum // panel, mQs will not need to be null cause it will be tied to the same lifecycle. if (fragment == mQs) { // Clear it to remove bindings to mQs from the provider. - mNotificationStackScrollLayoutController.setQsHeaderBoundsProvider(null); - mNotificationStackScrollLayoutController.setQsHeader(null); + if (QSComposeFragment.isEnabled()) { + mNotificationStackScrollLayoutController.setQsHeaderBoundsProvider(null); + } else { + mNotificationStackScrollLayoutController.setQsHeader(null); + } mQs = null; } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt index bc2377895101..21bbaa5a41f2 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt @@ -31,6 +31,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.flags.FeatureFlags import com.android.systemui.keyguard.ui.view.KeyguardRootView +import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerDependencies import com.android.systemui.privacy.OngoingPrivacyChip import com.android.systemui.res.R import com.android.systemui.scene.shared.flag.SceneContainerFlag @@ -83,6 +84,7 @@ abstract class ShadeViewProviderModule { scenesProvider: Provider<Set<@JvmSuppressWildcards Scene>>, layoutInsetController: NotificationInsetsController, sceneDataSourceDelegator: Provider<SceneDataSourceDelegator>, + alternateBouncerDependencies: Provider<AlternateBouncerDependencies>, ): WindowRootView { return if (SceneContainerFlag.isEnabled) { checkNoSceneDuplicates(scenesProvider.get()) @@ -96,6 +98,7 @@ abstract class ShadeViewProviderModule { scenes = scenesProvider.get(), layoutInsetController = layoutInsetController, sceneDataSourceDelegator = sceneDataSourceDelegator.get(), + alternateBouncerDependencies = alternateBouncerDependencies.get(), ) sceneWindowRootView } else { diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/BaseShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/BaseShadeSceneViewModel.kt deleted file mode 100644 index 068d6a74c8a7..000000000000 --- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/BaseShadeSceneViewModel.kt +++ /dev/null @@ -1,56 +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. - */ - -@file:OptIn(ExperimentalCoroutinesApi::class) - -package com.android.systemui.shade.ui.viewmodel - -import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor -import com.android.systemui.lifecycle.SysUiViewModel -import com.android.systemui.scene.domain.interactor.SceneInteractor -import com.android.systemui.scene.shared.model.Scenes -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.collectLatest - -/** Base class for classes that model UI state of the content of shade scenes. */ -abstract class BaseShadeSceneViewModel( - private val deviceEntryInteractor: DeviceEntryInteractor, - private val sceneInteractor: SceneInteractor, -) : SysUiViewModel() { - - private val _isEmptySpaceClickable = - MutableStateFlow(!deviceEntryInteractor.isDeviceEntered.value) - /** Whether clicking on the empty area of the shade does something */ - val isEmptySpaceClickable: StateFlow<Boolean> = _isEmptySpaceClickable.asStateFlow() - - override suspend fun onActivated() { - deviceEntryInteractor.isDeviceEntered.collectLatest { isDeviceEntered -> - _isEmptySpaceClickable.value = !isDeviceEntered - } - } - - /** Notifies that the empty space in the shade has been clicked. */ - fun onEmptySpaceClicked() { - if (!isEmptySpaceClickable.value) { - return - } - - sceneInteractor.changeScene(Scenes.Lockscreen, "Shade empty space clicked.") - } -} diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModel.kt index 3cdff964e26a..a4d34163ba68 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModel.kt @@ -20,11 +20,13 @@ package com.android.systemui.shade.ui.viewmodel import androidx.lifecycle.LifecycleOwner import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor +import com.android.systemui.lifecycle.SysUiViewModel import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor import com.android.systemui.qs.FooterActionsController import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel import com.android.systemui.qs.ui.adapter.QSSceneAdapter import com.android.systemui.scene.domain.interactor.SceneInteractor +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.settings.brightness.ui.viewModel.BrightnessMirrorViewModel import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shade.shared.model.ShadeMode @@ -34,7 +36,10 @@ import dagger.assisted.AssistedInject import java.util.concurrent.atomic.AtomicBoolean import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.collectLatest /** * Models UI state used to render the content of the shade scene. @@ -53,20 +58,27 @@ constructor( private val footerActionsViewModelFactory: FooterActionsViewModel.Factory, private val footerActionsController: FooterActionsController, private val unfoldTransitionInteractor: UnfoldTransitionInteractor, - deviceEntryInteractor: DeviceEntryInteractor, - sceneInteractor: SceneInteractor, -) : - BaseShadeSceneViewModel( - deviceEntryInteractor, - sceneInteractor, - ) { + private val deviceEntryInteractor: DeviceEntryInteractor, + private val sceneInteractor: SceneInteractor, +) : SysUiViewModel() { val shadeMode: StateFlow<ShadeMode> = shadeInteractor.shadeMode + private val _isEmptySpaceClickable = + MutableStateFlow(!deviceEntryInteractor.isDeviceEntered.value) + /** Whether clicking on the empty area of the shade does something */ + val isEmptySpaceClickable: StateFlow<Boolean> = _isEmptySpaceClickable.asStateFlow() + val isMediaVisible: StateFlow<Boolean> = mediaCarouselInteractor.hasActiveMediaOrRecommendation private val footerActionsControllerInitialized = AtomicBoolean(false) + override suspend fun onActivated() { + deviceEntryInteractor.isDeviceEntered.collectLatest { isDeviceEntered -> + _isEmptySpaceClickable.value = !isDeviceEntered + } + } + /** * Amount of X-axis translation to apply to various elements as the unfolded foldable is folded * slightly, in pixels. @@ -82,6 +94,15 @@ constructor( return footerActionsViewModelFactory.create(lifecycleOwner) } + /** Notifies that the empty space in the shade has been clicked. */ + fun onEmptySpaceClicked() { + if (!isEmptySpaceClickable.value) { + return + } + + sceneInteractor.changeScene(Scenes.Lockscreen, "Shade empty space clicked.") + } + @AssistedFactory interface Factory { fun create(): ShadeSceneContentViewModel diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index 50be6dcaa678..a1477b57a22a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -37,6 +37,7 @@ import android.hardware.biometrics.IBiometricSysuiReceiver; import android.hardware.biometrics.PromptInfo; import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback; import android.inputmethodservice.InputMethodService.BackDispositionMode; +import android.inputmethodservice.InputMethodService.ImeWindowVisibility; import android.media.INearbyMediaDevicesProvider; import android.media.MediaRoute2Info; import android.os.Binder; @@ -257,10 +258,10 @@ public class CommandQueue extends IStatusBar.Stub implements * * @param displayId The id of the display to notify. * @param vis IME visibility. - * @param backDisposition Disposition mode of back button. It should be one of below flags: + * @param backDisposition Disposition mode of back button. * @param showImeSwitcher {@code true} to show IME switch button. */ - default void setImeWindowStatus(int displayId, int vis, + default void setImeWindowStatus(int displayId, @ImeWindowVisibility int vis, @BackDispositionMode int backDisposition, boolean showImeSwitcher) { } default void showRecentApps(boolean triggeredFromAltTab) { } default void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) { } @@ -743,8 +744,8 @@ public class CommandQueue extends IStatusBar.Stub implements } @Override - public void setImeWindowStatus(int displayId, int vis, int backDisposition, - boolean showImeSwitcher) { + public void setImeWindowStatus(int displayId, @ImeWindowVisibility int vis, + @BackDispositionMode int backDisposition, boolean showImeSwitcher) { synchronized (mLock) { mHandler.removeMessages(MSG_SHOW_IME_BUTTON); SomeArgs args = SomeArgs.obtain(); @@ -1205,8 +1206,8 @@ public class CommandQueue extends IStatusBar.Stub implements } } - private void handleShowImeButton(int displayId, int vis, int backDisposition, - boolean showImeSwitcher) { + private void handleShowImeButton(int displayId, @ImeWindowVisibility int vis, + @BackDispositionMode int backDisposition, boolean showImeSwitcher) { if (displayId == INVALID_DISPLAY) return; boolean isConcurrentMultiUserModeEnabled = UserManager.isVisibleBackgroundUsersEnabled() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS b/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS index 69ebb7674f72..c4f539a4acdf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS +++ b/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS @@ -5,3 +5,12 @@ set noparent caitlinshk@google.com evanlaird@google.com pixel@google.com + +per-file *Biometrics* = set noparent +per-file *Biometrics* = file:../keyguard/OWNERS +per-file *Doze* = set noparent +per-file *Doze* = file:../keyguard/OWNERS +per-file *Keyboard* = set noparent +per-file *Keyboard* = file:../keyguard/OWNERS +per-file *Keyguard* = set noparent +per-file *Keyguard* = file:../keyguard/OWNERS
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt index ef4dffad27a8..97add30b9a57 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt @@ -26,6 +26,7 @@ import android.content.ContentResolver import android.content.Context import android.content.Intent import android.database.ContentObserver +import android.graphics.Rect import android.net.Uri import android.os.Handler import android.os.UserHandle @@ -570,6 +571,20 @@ constructor( plugin?.unregisterListener(listener) } + fun isWithinSmartspaceBounds(x: Int, y: Int): Boolean { + smartspaceViews.forEach { + val bounds = Rect() + with(it as View) { + this.getBoundsOnScreen(bounds) + if (bounds.contains(x, y)) { + return true + } + } + } + + return false + } + private fun filterSmartspaceTarget(t: SmartspaceTarget): Boolean { if (isDateWeatherDecoupled && t.featureType == SmartspaceTarget.FEATURE_WEATHER) { return false @@ -587,7 +602,7 @@ constructor( // Only the primary user can have an associated managed profile, so only show // content for the managed profile if the primary user is active userTracker.userHandle.identifier == UserHandle.USER_SYSTEM && - (!t.isSensitive || showSensitiveContentForManagedUser) + (!t.isSensitive || showSensitiveContentForManagedUser) } else -> { false @@ -705,4 +720,3 @@ constructor( } } } - diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/OWNERS b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/OWNERS new file mode 100644 index 000000000000..4c349c4f86c3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/OWNERS @@ -0,0 +1,3 @@ +set noparent + +file:../../keyguard/OWNERS diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt index 1027bc98ef47..9b382e61b2e3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt @@ -89,6 +89,7 @@ constructor( .filter { it.callType == CallType.Ongoing } .minByOrNull { it.whenTime } } + .distinctUntilChanged() .flowOn(backgroundDispatcher) /** Are any notifications being actively presented in the notification stack? */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 9d13a17d8e02..cb3e26b9f8ea 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -1804,6 +1804,20 @@ public class ExpandableNotificationRow extends ActivatableNotificationView NotificationEntry childEntry, NotificationEntry containerEntry ); + + /** + * Called when resetting the alpha value for content views + */ + void logResetAllContentAlphas( + NotificationEntry entry + ); + + /** + * Called when resetting the alpha value for content views is skipped + */ + void logSkipResetAllContentAlphas( + NotificationEntry entry + ); } /** @@ -3001,6 +3015,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mChildrenContainer.animate().cancel(); } resetAllContentAlphas(); + } else { + mLogger.logSkipResetAllContentAlphas(getEntry()); } mPublicLayout.setVisibility(mShowingPublic ? View.VISIBLE : View.INVISIBLE); updateChildrenVisibility(); @@ -3186,6 +3202,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView @Override protected void resetAllContentAlphas() { + mLogger.logResetAllContentAlphas(getEntry()); mPrivateLayout.setAlpha(1f); mPrivateLayout.setLayerType(LAYER_TYPE_NONE, null); mPublicLayout.setAlpha(1f); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java index 4c76e3284dbe..c31a2cb8908b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java @@ -195,6 +195,20 @@ public class ExpandableNotificationRowController implements NotifViewController ) { mLogBufferLogger.logRemoveTransientRow(childEntry, containerEntry); } + + @Override + public void logResetAllContentAlphas( + NotificationEntry entry + ) { + mLogBufferLogger.logResetAllContentAlphas(entry); + } + + @Override + public void logSkipResetAllContentAlphas( + NotificationEntry entry + ) { + mLogBufferLogger.logSkipResetAllContentAlphas(entry); + } }; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt index 4f5a04f2bdc9..b1e90329e01a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt @@ -128,6 +128,24 @@ constructor( { "removeTransientRow from row: childKey: $str1 -- containerKey: $str2" } ) } + + fun logResetAllContentAlphas(entry: NotificationEntry) { + notificationRenderBuffer.log( + TAG, + LogLevel.INFO, + { str1 = entry.logKey }, + { "resetAllContentAlphas: $str1" } + ) + } + + fun logSkipResetAllContentAlphas(entry: NotificationEntry) { + notificationRenderBuffer.log( + TAG, + LogLevel.INFO, + { str1 = entry.logKey }, + { "Skip resetAllContentAlphas: $str1" } + ) + } } private const val TAG = "NotifRow" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt index fe86375d628e..bf5b3a34afb6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt @@ -17,7 +17,6 @@ package com.android.systemui.statusbar.notification.row import android.app.Notification -import android.app.Notification.RichOngoingStyle import android.app.PendingIntent import android.content.Context import android.util.Log @@ -69,14 +68,12 @@ class RichOngoingNotificationContentExtractorImpl @Inject constructor() : builder: Notification.Builder, systemUIContext: Context, packageContext: Context - ): RichOngoingContentModel? { - if (builder.style !is RichOngoingStyle) return null - + ): RichOngoingContentModel? = try { val sbn = entry.sbn val notification = sbn.notification val icon = IconModel(notification.smallIcon) - return if (sbn.packageName == "com.google.android.deskclock") { + if (sbn.packageName == "com.google.android.deskclock") { when (notification.channelId) { "Timers v2" -> { parseTimerNotification(notification, icon) @@ -93,9 +90,8 @@ class RichOngoingNotificationContentExtractorImpl @Inject constructor() : } else null } catch (e: Exception) { Log.e("RONs", "Error parsing RON", e) - return null + null } - } /** * FOR PROTOTYPING ONLY: create a RON TimerContentModel using the time information available diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt index fd08e898fce3..a30b8772c3d1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt @@ -17,14 +17,14 @@ package com.android.systemui.statusbar.notification.stack.ui.viewbinder import android.util.Log -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.repeatOnLifecycle import com.android.systemui.common.ui.ConfigurationState import com.android.systemui.common.ui.view.onLayoutChanged import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager +import com.android.systemui.lifecycle.WindowLifecycleState import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.lifecycle.viewModel import com.android.systemui.res.R import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationScrollViewModel @@ -33,7 +33,6 @@ import com.android.systemui.util.kotlin.launchAndDispose import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.DisposableHandle -import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch @@ -46,7 +45,7 @@ constructor( dumpManager: DumpManager, @Main private val mainImmediateDispatcher: CoroutineDispatcher, private val view: NotificationScrollView, - private val viewModel: NotificationScrollViewModel, + private val viewModelFactory: NotificationScrollViewModel.Factory, private val configuration: ConfigurationState, ) : FlowDumperImpl(dumpManager) { @@ -61,38 +60,42 @@ constructor( } fun bindWhileAttached(): DisposableHandle { - return view.asView().repeatWhenAttached(mainImmediateDispatcher) { - repeatOnLifecycle(Lifecycle.State.CREATED) { bind() } - } + return view.asView().repeatWhenAttached(mainImmediateDispatcher) { bind() } } - suspend fun bind() = coroutineScope { - launchAndDispose { - updateViewPosition() - view.asView().onLayoutChanged { updateViewPosition() } - } + suspend fun bind(): Nothing = + view.asView().viewModel( + minWindowLifecycleState = WindowLifecycleState.ATTACHED, + factory = viewModelFactory::create, + ) { viewModel -> + launchAndDispose { + updateViewPosition() + view.asView().onLayoutChanged { updateViewPosition() } + } - launch { - viewModel - .shadeScrimShape(cornerRadius = scrimRadius, viewLeftOffset = viewLeftOffset) - .collect { view.setScrimClippingShape(it) } - } + launch { + viewModel + .shadeScrimShape(cornerRadius = scrimRadius, viewLeftOffset = viewLeftOffset) + .collect { view.setScrimClippingShape(it) } + } - launch { viewModel.maxAlpha.collect { view.setMaxAlpha(it) } } - launch { viewModel.scrolledToTop.collect { view.setScrolledToTop(it) } } - launch { viewModel.expandFraction.collect { view.setExpandFraction(it.coerceIn(0f, 1f)) } } - launch { viewModel.isScrollable.collect { view.setScrollingEnabled(it) } } - launch { viewModel.isDozing.collect { isDozing -> view.setDozing(isDozing) } } + launch { viewModel.maxAlpha.collect { view.setMaxAlpha(it) } } + launch { viewModel.scrolledToTop.collect { view.setScrolledToTop(it) } } + launch { + viewModel.expandFraction.collect { view.setExpandFraction(it.coerceIn(0f, 1f)) } + } + launch { viewModel.isScrollable.collect { view.setScrollingEnabled(it) } } + launch { viewModel.isDozing.collect { isDozing -> view.setDozing(isDozing) } } - launchAndDispose { - view.setSyntheticScrollConsumer(viewModel.syntheticScrollConsumer) - view.setCurrentGestureOverscrollConsumer(viewModel.currentGestureOverscrollConsumer) - DisposableHandle { - view.setSyntheticScrollConsumer(null) - view.setCurrentGestureOverscrollConsumer(null) + launchAndDispose { + view.setSyntheticScrollConsumer(viewModel.syntheticScrollConsumer) + view.setCurrentGestureOverscrollConsumer(viewModel.currentGestureOverscrollConsumer) + DisposableHandle { + view.setSyntheticScrollConsumer(null) + view.setCurrentGestureOverscrollConsumer(null) + } } } - } /** flow of the scrim clipping radius */ private val scrimRadius: Flow<Int> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt index 2ba79a8612bb..bfb624a9287b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt @@ -19,9 +19,9 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.SceneKey -import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dump.DumpManager import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.lifecycle.SysUiViewModel import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.model.SceneFamilies @@ -33,9 +33,11 @@ import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrim import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationTransitionThresholds.EXPANSION_FOR_DELAYED_STACK_FADE_IN import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationTransitionThresholds.EXPANSION_FOR_MAX_SCRIM_ALPHA -import com.android.systemui.util.kotlin.FlowDumperImpl +import com.android.systemui.util.kotlin.ActivatableFlowDumper +import com.android.systemui.util.kotlin.ActivatableFlowDumperImpl import dagger.Lazy -import javax.inject.Inject +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged @@ -43,9 +45,8 @@ import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map /** ViewModel which represents the state of the NSSL/Controller in the world of flexiglass */ -@SysUISingleton class NotificationScrollViewModel -@Inject +@AssistedInject constructor( dumpManager: DumpManager, stackAppearanceInteractor: NotificationStackAppearanceInteractor, @@ -54,7 +55,14 @@ constructor( // TODO(b/336364825) Remove Lazy when SceneContainerFlag is released - // while the flag is off, creating this object too early results in a crash keyguardInteractor: Lazy<KeyguardInteractor>, -) : FlowDumperImpl(dumpManager) { +) : + ActivatableFlowDumper by ActivatableFlowDumperImpl(dumpManager, "NotificationScrollViewModel"), + SysUiViewModel() { + + override suspend fun onActivated() { + activateFlowDumper() + } + /** * The expansion fraction of the notification stack. It should go from 0 to 1 when transitioning * from Gone to Shade scenes, and remain at 1 when in Lockscreen or Shade scenes and while @@ -186,4 +194,9 @@ constructor( keyguardInteractor.get().isDozing.dumpWhileCollecting("isDozing") } } + + @AssistedFactory + interface Factory { + fun create(): NotificationScrollViewModel + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt index d179888b569c..53fab621173a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt @@ -16,34 +16,32 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.flags.Flags +import com.android.systemui.lifecycle.SysUiViewModel import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimRounding -import com.android.systemui.util.kotlin.FlowDumperImpl -import javax.inject.Inject +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import kotlinx.coroutines.flow.Flow /** * ViewModel used by the Notification placeholders inside the scene container to update the * [NotificationStackAppearanceInteractor], and by extension control the NSSL. */ -@SysUISingleton class NotificationsPlaceholderViewModel -@Inject +@AssistedInject constructor( - dumpManager: DumpManager, private val interactor: NotificationStackAppearanceInteractor, shadeInteractor: ShadeInteractor, private val headsUpNotificationInteractor: HeadsUpNotificationInteractor, featureFlags: FeatureFlagsClassic, -) : FlowDumperImpl(dumpManager) { +) : SysUiViewModel() { + /** DEBUG: whether the placeholder should be made slightly visible for positional debugging. */ val isVisualDebuggingEnabled: Boolean = featureFlags.isEnabled(Flags.NSSL_DEBUG_LINES) @@ -70,35 +68,37 @@ constructor( headsUpNotificationInteractor.isHeadsUpOrAnimatingAway /** Corner rounding of the stack */ - val shadeScrimRounding: Flow<ShadeScrimRounding> = - interactor.shadeScrimRounding.dumpWhileCollecting("shadeScrimRounding") + // TODO(b/359244921): add .dumpWhileCollecting("shadeScrimRounding") + val shadeScrimRounding: Flow<ShadeScrimRounding> = interactor.shadeScrimRounding /** * The amount [0-1] that the shade or quick settings has been opened. At 0, the shade is closed; * at 1, either the shade or quick settings is open. */ - val expandFraction: Flow<Float> = shadeInteractor.anyExpansion.dumpValue("expandFraction") + // TODO(b/359244921): add .dumpValue("expandFraction") + val expandFraction: Flow<Float> = shadeInteractor.anyExpansion /** * The amount [0-1] that quick settings has been opened. At 0, the shade may be open or closed; * at 1, the quick settings are open. */ - val shadeToQsFraction: Flow<Float> = shadeInteractor.qsExpansion.dumpValue("shadeToQsFraction") + // TODO(b/359244921): add .dumpValue("shadeToQsFraction") + val shadeToQsFraction: Flow<Float> = shadeInteractor.qsExpansion /** * The amount in px that the notification stack should scroll due to internal expansion. This * should only happen when a notification expansion hits the bottom of the screen, so it is * necessary to scroll up to keep expanding the notification. */ - val syntheticScroll: Flow<Float> = - interactor.syntheticScroll.dumpWhileCollecting("syntheticScroll") + // TODO(b/359244921): add .dumpWhileCollecting("syntheticScroll") + val syntheticScroll: Flow<Float> = interactor.syntheticScroll /** * Whether the current touch gesture is overscroll. If true, it means the NSSL has already * consumed part of the gesture. */ - val isCurrentGestureOverscroll: Flow<Boolean> = - interactor.isCurrentGestureOverscroll.dumpWhileCollecting("isCurrentGestureOverScroll") + // TODO(b/359244921): add .dumpWhileCollecting("isCurrentGestureOverScroll") + val isCurrentGestureOverscroll: Flow<Boolean> = interactor.isCurrentGestureOverscroll /** Sets whether the notification stack is scrolled to the top. */ fun setScrolledToTop(scrolledToTop: Boolean) { @@ -114,6 +114,11 @@ constructor( fun snoozeHun() { headsUpNotificationInteractor.snooze() } + + @AssistedFactory + interface Factory { + fun create(): NotificationsPlaceholderViewModel + } } // Expansion fraction thresholds (between 0-1f) at which the corresponding value should be 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 b6d58d6a23d9..c4fbc37b2dd5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -30,6 +30,7 @@ import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME; import static com.android.systemui.Flags.keyboardShortcutHelperRewrite; import static com.android.systemui.Flags.lightRevealMigration; import static com.android.systemui.Flags.newAodTransition; +import static com.android.systemui.Flags.relockWithPowerButtonImmediately; import static com.android.systemui.charging.WirelessChargingAnimation.UNKNOWN_BATTERY_LEVEL; import static com.android.systemui.flags.Flags.SHORTCUT_LIST_SEARCH_LAYOUT; import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF; @@ -2352,8 +2353,14 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } else if (mState == StatusBarState.KEYGUARD && !mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing() && mStatusBarKeyguardViewManager.isSecure()) { - Log.d(TAG, "showBouncerOrLockScreenIfKeyguard, showingBouncer"); - mStatusBarKeyguardViewManager.showBouncer(true /* scrimmed */); + if (!relockWithPowerButtonImmediately()) { + Log.d(TAG, "showBouncerOrLockScreenIfKeyguard, showingBouncer"); + if (SceneContainerFlag.isEnabled()) { + mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */); + } else { + mStatusBarKeyguardViewManager.showBouncer(true /* scrimmed */); + } + } } } } @@ -2985,7 +2992,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { @Override public void onFalse() { // Hides quick settings, bouncer, and quick-quick settings. - mStatusBarKeyguardViewManager.reset(true); + mStatusBarKeyguardViewManager.reset(true, /* isFalsingReset= */true); } }; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java index f13a593d08f2..ac1015521502 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java @@ -424,14 +424,12 @@ public final class DozeServiceHost implements DozeHost { @Override public void setDozeScreenBrightness(int brightness) { - mDozeLog.traceDozeScreenBrightness(brightness); mNotificationShadeWindowController.setDozeScreenBrightness(brightness); } @Override public void setDozeScreenBrightnessFloat(float brightness) { - mDozeLog.traceDozeScreenBrightnessFloat(brightness); mNotificationShadeWindowController.setDozeScreenBrightnessFloat(brightness); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java index 8115c36c31e2..f1787088ab98 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java @@ -36,6 +36,7 @@ import android.widget.LinearLayout; import com.android.internal.policy.SystemBarUtils; import com.android.systemui.Dependency; +import com.android.systemui.Flags; import com.android.systemui.Gefingerpoken; import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver; @@ -217,8 +218,12 @@ public class PhoneStatusBarView extends FrameLayout { @Override public boolean onInterceptTouchEvent(MotionEvent event) { - mTouchEventHandler.onInterceptTouchEvent(event); - return super.onInterceptTouchEvent(event); + if (Flags.statusBarSwipeOverChip()) { + return mTouchEventHandler.onInterceptTouchEvent(event); + } else { + mTouchEventHandler.onInterceptTouchEvent(event); + return super.onInterceptTouchEvent(event); + } } public void updateResources() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt index 5206e46a4580..a818c05b1666 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt @@ -23,9 +23,10 @@ import android.view.MotionEvent import android.view.View import android.view.ViewGroup import android.view.ViewTreeObserver +import com.android.systemui.Flags import com.android.systemui.Gefingerpoken import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags +import com.android.systemui.flags.Flags.ENABLE_UNFOLD_STATUS_BAR_ANIMATIONS import com.android.systemui.res.R import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.ui.view.WindowRootView @@ -83,22 +84,25 @@ private constructor( statusContainer.setOnHoverListener( statusOverlayHoverListenerFactory.createDarkAwareListener(statusContainer) ) - statusContainer.setOnTouchListener(object : View.OnTouchListener { - override fun onTouch(v: View, event: MotionEvent): Boolean { - // We want to handle only mouse events here to avoid stealing finger touches from - // status bar which expands shade when swiped down on. We're using onTouchListener - // instead of onClickListener as the later will lead to isClickable being set to - // true and hence ALL touches always being intercepted. See [View.OnTouchEvent] - if (event.source == InputDevice.SOURCE_MOUSE) { - if (event.action == MotionEvent.ACTION_UP) { - v.performClick() - shadeController.animateExpandShade() + statusContainer.setOnTouchListener( + object : View.OnTouchListener { + override fun onTouch(v: View, event: MotionEvent): Boolean { + // We want to handle only mouse events here to avoid stealing finger touches + // from status bar which expands shade when swiped down on. See b/326097469. + // We're using onTouchListener instead of onClickListener as the later will lead + // to isClickable being set to true and hence ALL touches always being + // intercepted. See [View.OnTouchEvent] + if (event.source == InputDevice.SOURCE_MOUSE) { + if (event.action == MotionEvent.ACTION_UP) { + v.performClick() + shadeController.animateExpandShade() + } + return true } - return true + return false } - return false } - }) + ) progressProvider?.setReadyToHandleTransition(true) configurationController.addCallback(configurationListener) @@ -180,8 +184,12 @@ private constructor( inner class PhoneStatusBarViewTouchHandler : Gefingerpoken { override fun onInterceptTouchEvent(event: MotionEvent): Boolean { - onTouch(event) - return false + return if (Flags.statusBarSwipeOverChip()) { + shadeViewController.handleExternalInterceptTouch(event) + } else { + onTouch(event) + false + } } override fun onTouchEvent(event: MotionEvent): Boolean { @@ -280,7 +288,7 @@ private constructor( ) { fun create(view: PhoneStatusBarView): PhoneStatusBarViewController { val statusBarMoveFromCenterAnimationController = - if (featureFlags.isEnabled(Flags.ENABLE_UNFOLD_STATUS_BAR_ANIMATIONS)) { + if (featureFlags.isEnabled(ENABLE_UNFOLD_STATUS_BAR_ANIMATIONS)) { unfoldComponent.getOrNull()?.getStatusBarMoveFromCenterAnimationController() } else { null diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 2d775b74eb32..5486abba9987 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -708,7 +708,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb * Shows the notification keyguard or the bouncer depending on * {@link #needsFullscreenBouncer()}. */ - protected void showBouncerOrKeyguard(boolean hideBouncerWhenShowing) { + protected void showBouncerOrKeyguard(boolean hideBouncerWhenShowing, boolean isFalsingReset) { boolean isDozing = mDozing; if (Flags.simPinRaceConditionOnRestart()) { KeyguardState toState = mKeyguardTransitionInteractor.getTransitionState().getValue() @@ -734,8 +734,11 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mPrimaryBouncerInteractor.show(/* isScrimmed= */ true); } } - } else { - Log.e(TAG, "Attempted to show the sim bouncer when it is already showing."); + } else if (!isFalsingReset) { + // Falsing resets can cause this to flicker, so don't reset in this case + Log.i(TAG, "Sim bouncer is already showing, issuing a refresh"); + mPrimaryBouncerInteractor.show(/* isScrimmed= */ true); + } } else { mCentralSurfaces.showKeyguard(); @@ -957,6 +960,10 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb @Override public void reset(boolean hideBouncerWhenShowing) { + reset(hideBouncerWhenShowing, /* isFalsingReset= */false); + } + + public void reset(boolean hideBouncerWhenShowing, boolean isFalsingReset) { if (mKeyguardStateController.isShowing() && !bouncerIsAnimatingAway()) { final boolean isOccluded = mKeyguardStateController.isOccluded(); // Hide quick settings. @@ -965,10 +972,14 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb if (isOccluded && !mDozing) { mCentralSurfaces.hideKeyguard(); if (hideBouncerWhenShowing || needsFullscreenBouncer()) { - hideBouncer(false /* destroyView */); + // We're removing "reset" in the refactor - bouncer will be hidden by the root + // cause of the "reset" calls. + if (!KeyguardWmStateRefactor.isEnabled()) { + hideBouncer(false /* destroyView */); + } } } else { - showBouncerOrKeyguard(hideBouncerWhenShowing); + showBouncerOrKeyguard(hideBouncerWhenShowing, isFalsingReset); } if (hideBouncerWhenShowing) { hideAlternateBouncer(true); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt index 4368239c31f0..bd6a1c05ddc9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt @@ -227,6 +227,15 @@ constructor( callNotificationInfo // This shouldn't happen, but protect against it in case ?: return OngoingCallModel.NoCall + logger.log( + TAG, + LogLevel.DEBUG, + { + bool1 = Flags.statusBarCallChipNotificationIcon() + bool2 = currentInfo.notificationIconView != null + }, + { "Creating OngoingCallModel.InCall. notifIconFlag=$bool1 hasIcon=$bool2" } + ) val icon = if (Flags.statusBarCallChipNotificationIcon()) { currentInfo.notificationIconView @@ -257,6 +266,7 @@ constructor( private fun updateInfoFromNotifModel(notifModel: ActiveNotificationModel?) { if (notifModel == null) { + logger.log(TAG, LogLevel.DEBUG, {}, { "NotifInteractorCallModel: null" }) removeChip() } else if (notifModel.callType != CallType.Ongoing) { logger.log( @@ -267,6 +277,18 @@ constructor( ) removeChip() } else { + logger.log( + TAG, + LogLevel.DEBUG, + { + str1 = notifModel.key + long1 = notifModel.whenTime + str1 = notifModel.callType.name + bool1 = notifModel.statusBarChipIconView != null + }, + { "NotifInteractorCallModel: key=$str1 when=$long1 callType=$str2 hasIcon=$bool1" } + ) + val newOngoingCallInfo = CallNotificationInfo( notifModel.key, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt index 2e54972c4950..9cbfc440ab16 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt @@ -198,7 +198,8 @@ constructor( fun logServiceProvidersUpdatedBroadcast(intent: Intent) { val showSpn = intent.getBooleanExtra(TelephonyManager.EXTRA_SHOW_SPN, false) - val spn = intent.getStringExtra(TelephonyManager.EXTRA_DATA_SPN) + val spn = intent.getStringExtra(TelephonyManager.EXTRA_SPN) + val dataSpn = intent.getStringExtra(TelephonyManager.EXTRA_DATA_SPN) val showPlmn = intent.getBooleanExtra(TelephonyManager.EXTRA_SHOW_PLMN, false) val plmn = intent.getStringExtra(TelephonyManager.EXTRA_PLMN) @@ -208,12 +209,13 @@ constructor( { bool1 = showSpn str1 = spn + str2 = dataSpn bool2 = showPlmn - str2 = plmn + str3 = plmn }, { "Intent: ACTION_SERVICE_PROVIDERS_UPDATED." + - " showSpn=$bool1 spn=$str1 showPlmn=$bool2 plmn=$str2" + " showSpn=$bool1 spn=$str1 dataSpn=$str2 showPlmn=$bool2 plmn=$str3" } ) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt index 99ed2d99c749..85bbe7e53493 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt @@ -21,6 +21,8 @@ import android.telephony.TelephonyManager.EXTRA_DATA_SPN import android.telephony.TelephonyManager.EXTRA_PLMN import android.telephony.TelephonyManager.EXTRA_SHOW_PLMN import android.telephony.TelephonyManager.EXTRA_SHOW_SPN +import android.telephony.TelephonyManager.EXTRA_SPN +import com.android.systemui.Flags.statusBarSwitchToSpnFromDataSpn import com.android.systemui.log.table.Diffable import com.android.systemui.log.table.TableRowLogger @@ -96,7 +98,13 @@ sealed interface NetworkNameModel : Diffable<NetworkNameModel> { fun Intent.toNetworkNameModel(separator: String): NetworkNameModel? { val showSpn = getBooleanExtra(EXTRA_SHOW_SPN, false) - val spn = getStringExtra(EXTRA_DATA_SPN) + val spn = + if (statusBarSwitchToSpnFromDataSpn()) { + getStringExtra(EXTRA_SPN) + } else { + getStringExtra(EXTRA_DATA_SPN) + } + val showPlmn = getBooleanExtra(EXTRA_SHOW_PLMN, false) val plmn = getStringExtra(EXTRA_PLMN) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java index 21f1a3dfc23a..c30a6b7d0f17 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java @@ -46,6 +46,7 @@ import android.view.ViewGroup; import android.view.WindowInsets; import android.view.WindowManager; +import com.android.app.viewcapture.ViewCaptureAwareWindowManager; import com.android.internal.policy.SystemBarUtils; import com.android.systemui.animation.ActivityTransitionAnimator; import com.android.systemui.animation.DelegateTransitionAnimatorController; @@ -71,7 +72,7 @@ public class StatusBarWindowController { private static final boolean DEBUG = false; private final Context mContext; - private final WindowManager mWindowManager; + private final ViewCaptureAwareWindowManager mWindowManager; private final IWindowManager mIWindowManager; private final StatusBarContentInsetsProvider mContentInsetsProvider; private int mBarHeight = -1; @@ -91,14 +92,14 @@ public class StatusBarWindowController { public StatusBarWindowController( Context context, @StatusBarWindowModule.InternalWindowView StatusBarWindowView statusBarWindowView, - WindowManager windowManager, + ViewCaptureAwareWindowManager viewCaptureAwareWindowManager, IWindowManager iWindowManager, StatusBarContentInsetsProvider contentInsetsProvider, FragmentService fragmentService, @Main Resources resources, Optional<UnfoldTransitionProgressProvider> unfoldTransitionProgressProvider) { mContext = context; - mWindowManager = windowManager; + mWindowManager = viewCaptureAwareWindowManager; mIWindowManager = iWindowManager; mContentInsetsProvider = contentInsetsProvider; mStatusBarWindowView = statusBarWindowView; diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt index 8f0489631ae0..3c53d2d05fb8 100644 --- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt +++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt @@ -32,6 +32,7 @@ import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_ICONS import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_TEXT import androidx.annotation.CallSuper import androidx.annotation.VisibleForTesting +import com.android.app.viewcapture.ViewCaptureAwareWindowManager import com.android.systemui.CoreStartable import com.android.systemui.Dumpable import com.android.systemui.dagger.qualifiers.Main @@ -70,7 +71,7 @@ import java.io.PrintWriter abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : TemporaryViewLogger<T>>( internal val context: Context, internal val logger: U, - internal val windowManager: WindowManager, + internal val windowManager: ViewCaptureAwareWindowManager, @Main private val mainExecutor: DelayableExecutor, private val accessibilityManager: AccessibilityManager, private val configurationController: ConfigurationController, diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt index b6f54331b29f..9b9cba9186f4 100644 --- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt @@ -29,7 +29,6 @@ import android.view.View import android.view.View.ACCESSIBILITY_LIVE_REGION_ASSERTIVE import android.view.View.ACCESSIBILITY_LIVE_REGION_NONE import android.view.ViewGroup -import android.view.WindowManager import android.view.accessibility.AccessibilityManager import android.view.accessibility.AccessibilityNodeInfo import android.widget.ImageView @@ -38,6 +37,7 @@ import androidx.annotation.DimenRes import androidx.annotation.IdRes import androidx.annotation.VisibleForTesting import com.android.app.animation.Interpolators +import com.android.app.viewcapture.ViewCaptureAwareWindowManager import com.android.internal.widget.CachingIconView import com.android.systemui.Gefingerpoken import com.android.systemui.classifier.FalsingCollector @@ -81,7 +81,7 @@ open class ChipbarCoordinator constructor( context: Context, logger: ChipbarLogger, - windowManager: WindowManager, + viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager, @Main mainExecutor: DelayableExecutor, accessibilityManager: AccessibilityManager, configurationController: ConfigurationController, @@ -100,7 +100,7 @@ constructor( TemporaryViewDisplayController<ChipbarInfo, ChipbarLogger>( context, logger, - windowManager, + viewCaptureAwareWindowManager, mainExecutor, accessibilityManager, configurationController, diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/TouchpadTutorialModule.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/TouchpadTutorialModule.kt new file mode 100644 index 000000000000..238e8a1c01ad --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/TouchpadTutorialModule.kt @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.touchpad.tutorial + +import android.app.Activity +import androidx.compose.runtime.Composable +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.inputdevice.tutorial.TouchpadTutorialScreensProvider +import com.android.systemui.model.SysUiState +import com.android.systemui.settings.DisplayTracker +import com.android.systemui.touchpad.tutorial.domain.interactor.TouchpadGesturesInteractor +import com.android.systemui.touchpad.tutorial.ui.composable.BackGestureTutorialScreen +import com.android.systemui.touchpad.tutorial.ui.composable.HomeGestureTutorialScreen +import com.android.systemui.touchpad.tutorial.ui.view.TouchpadTutorialActivity +import dagger.Binds +import dagger.Module +import dagger.Provides +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap +import kotlinx.coroutines.CoroutineScope + +@Module +interface TouchpadTutorialModule { + + @Binds + @IntoMap + @ClassKey(TouchpadTutorialActivity::class) + fun activity(impl: TouchpadTutorialActivity): Activity + + companion object { + @Provides + fun touchpadScreensProvider(): TouchpadTutorialScreensProvider { + return ScreensProvider + } + + @SysUISingleton + @Provides + fun touchpadGesturesInteractor( + sysUiState: SysUiState, + displayTracker: DisplayTracker, + @Background backgroundScope: CoroutineScope + ): TouchpadGesturesInteractor { + return TouchpadGesturesInteractor(sysUiState, displayTracker, backgroundScope) + } + } +} + +private object ScreensProvider : TouchpadTutorialScreensProvider { + @Composable + override fun BackGesture(onDoneButtonClicked: () -> Unit, onBack: () -> Unit) { + BackGestureTutorialScreen(onDoneButtonClicked, onBack) + } + + @Composable + override fun HomeGesture(onDoneButtonClicked: () -> Unit, onBack: () -> Unit) { + HomeGestureTutorialScreen(onDoneButtonClicked, onBack) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/domain/interactor/TouchpadGesturesInteractor.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/domain/interactor/TouchpadGesturesInteractor.kt index b6c2ae794da9..df95232758a4 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/domain/interactor/TouchpadGesturesInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/domain/interactor/TouchpadGesturesInteractor.kt @@ -16,22 +16,16 @@ package com.android.systemui.touchpad.tutorial.domain.interactor -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.model.SysUiState import com.android.systemui.settings.DisplayTracker import com.android.systemui.shared.system.QuickStepContract -import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch -@SysUISingleton -class TouchpadGesturesInteractor -@Inject -constructor( +class TouchpadGesturesInteractor( private val sysUiState: SysUiState, private val displayTracker: DisplayTracker, - @Background private val backgroundScope: CoroutineScope + private val backgroundScope: CoroutineScope ) { fun disableGestures() { setGesturesState(disabled = true) diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt index 5980e1de85b4..1c8041ff5b31 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt @@ -21,6 +21,8 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import com.airbnb.lottie.compose.rememberLottieDynamicProperties import com.android.compose.theme.LocalAndroidColorScheme +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialScreenConfig +import com.android.systemui.inputdevice.tutorial.ui.composable.rememberColorFilterProperty import com.android.systemui.res.R import com.android.systemui.touchpad.tutorial.ui.gesture.BackGestureMonitor import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt index 9ac2cba2b8d8..57d7c84af4ba 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt @@ -16,57 +16,21 @@ package com.android.systemui.touchpad.tutorial.ui.composable -import android.graphics.ColorFilter -import android.graphics.PorterDuff -import android.graphics.PorterDuffColorFilter import androidx.activity.compose.BackHandler -import androidx.annotation.RawRes -import androidx.annotation.StringRes -import androidx.compose.animation.AnimatedContent -import androidx.compose.animation.EnterTransition -import androidx.compose.animation.ExitTransition -import androidx.compose.animation.animateColorAsState -import androidx.compose.animation.core.LinearEasing -import androidx.compose.animation.core.snap -import androidx.compose.animation.core.tween -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.togetherWith -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxScope -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.drawBehind -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.input.pointer.pointerInteropFilter import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import com.airbnb.lottie.LottieProperty -import com.airbnb.lottie.compose.LottieAnimation -import com.airbnb.lottie.compose.LottieCompositionSpec -import com.airbnb.lottie.compose.LottieConstants -import com.airbnb.lottie.compose.LottieDynamicProperties -import com.airbnb.lottie.compose.LottieDynamicProperty -import com.airbnb.lottie.compose.animateLottieCompositionAsState -import com.airbnb.lottie.compose.rememberLottieComposition -import com.airbnb.lottie.compose.rememberLottieDynamicProperty +import com.android.systemui.inputdevice.tutorial.ui.composable.ActionTutorialContent +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialScreenConfig import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.FINISHED import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.IN_PROGRESS @@ -81,6 +45,14 @@ interface GestureMonitorProvider { ): TouchpadGestureMonitor } +fun GestureState.toTutorialActionState(): TutorialActionState { + return when (this) { + NOT_STARTED -> TutorialActionState.NOT_STARTED + IN_PROGRESS -> TutorialActionState.IN_PROGRESS + FINISHED -> TutorialActionState.FINISHED + } +} + @Composable fun GestureTutorialScreen( screenConfig: TutorialScreenConfig, @@ -104,7 +76,11 @@ fun GestureTutorialScreen( ) } TouchpadGesturesHandlingBox(gestureHandler, gestureState) { - GestureTutorialContent(gestureState, onDoneButtonClicked, screenConfig) + ActionTutorialContent( + gestureState.toTutorialActionState(), + onDoneButtonClicked, + screenConfig + ) } } @@ -135,169 +111,3 @@ private fun TouchpadGesturesHandlingBox( content() } } - -@Composable -private fun GestureTutorialContent( - gestureState: GestureState, - onDoneButtonClicked: () -> Unit, - config: TutorialScreenConfig -) { - val animatedColor by - animateColorAsState( - targetValue = - if (gestureState == FINISHED) config.colors.successBackground - else config.colors.background, - animationSpec = tween(durationMillis = 150, easing = LinearEasing), - label = "backgroundColor" - ) - Column( - verticalArrangement = Arrangement.Center, - modifier = - Modifier.fillMaxSize() - .drawBehind { drawRect(animatedColor) } - .padding(start = 48.dp, top = 124.dp, end = 48.dp, bottom = 48.dp) - ) { - Row(modifier = Modifier.fillMaxWidth().weight(1f)) { - TutorialDescription( - titleTextId = - if (gestureState == FINISHED) config.strings.titleSuccessResId - else config.strings.titleResId, - titleColor = config.colors.title, - bodyTextId = - if (gestureState == FINISHED) config.strings.bodySuccessResId - else config.strings.bodyResId, - modifier = Modifier.weight(1f) - ) - Spacer(modifier = Modifier.width(76.dp)) - TutorialAnimation( - gestureState, - config, - modifier = Modifier.weight(1f).padding(top = 8.dp) - ) - } - DoneButton(onDoneButtonClicked = onDoneButtonClicked) - } -} - -@Composable -fun TutorialDescription( - @StringRes titleTextId: Int, - titleColor: Color, - @StringRes bodyTextId: Int, - modifier: Modifier = Modifier -) { - Column(verticalArrangement = Arrangement.Top, modifier = modifier) { - Text( - text = stringResource(id = titleTextId), - style = MaterialTheme.typography.displayLarge, - color = titleColor - ) - Spacer(modifier = Modifier.height(16.dp)) - Text( - text = stringResource(id = bodyTextId), - style = MaterialTheme.typography.bodyLarge, - color = Color.White - ) - } -} - -@Composable -fun TutorialAnimation( - gestureState: GestureState, - config: TutorialScreenConfig, - modifier: Modifier = Modifier -) { - Box(modifier = modifier.fillMaxWidth()) { - AnimatedContent( - targetState = gestureState, - transitionSpec = { - if (initialState == NOT_STARTED && targetState == IN_PROGRESS) { - val transitionDurationMillis = 150 - fadeIn(animationSpec = tween(transitionDurationMillis, easing = LinearEasing)) - .togetherWith( - fadeOut(animationSpec = snap(delayMillis = transitionDurationMillis)) - ) - // we explicitly don't want size transform because when targetState - // animation is loaded for the first time, AnimatedContent thinks target - // size is smaller and tries to shrink initial state animation - .using(sizeTransform = null) - } else { - // empty transition works because all remaining transitions are from IN_PROGRESS - // state which shares initial animation frame with both FINISHED and NOT_STARTED - EnterTransition.None togetherWith ExitTransition.None - } - } - ) { gestureState -> - when (gestureState) { - NOT_STARTED -> - EducationAnimation( - config.animations.educationResId, - config.colors.animationColors - ) - IN_PROGRESS -> - FrozenSuccessAnimation( - config.animations.successResId, - config.colors.animationColors - ) - FINISHED -> - SuccessAnimation(config.animations.successResId, config.colors.animationColors) - } - } - } -} - -@Composable -private fun FrozenSuccessAnimation( - @RawRes successAnimationId: Int, - animationProperties: LottieDynamicProperties -) { - val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(successAnimationId)) - LottieAnimation( - composition = composition, - progress = { 0f }, // animation should freeze on 1st frame - dynamicProperties = animationProperties, - ) -} - -@Composable -private fun EducationAnimation( - @RawRes educationAnimationId: Int, - animationProperties: LottieDynamicProperties -) { - val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(educationAnimationId)) - val progress by - animateLottieCompositionAsState(composition, iterations = LottieConstants.IterateForever) - LottieAnimation( - composition = composition, - progress = { progress }, - dynamicProperties = animationProperties, - ) -} - -@Composable -private fun SuccessAnimation( - @RawRes successAnimationId: Int, - animationProperties: LottieDynamicProperties -) { - val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(successAnimationId)) - val progress by animateLottieCompositionAsState(composition, iterations = 1) - LottieAnimation( - composition = composition, - progress = { progress }, - dynamicProperties = animationProperties, - ) -} - -@Composable -fun rememberColorFilterProperty( - layerName: String, - color: Color -): LottieDynamicProperty<ColorFilter> { - return rememberLottieDynamicProperty( - LottieProperty.COLOR_FILTER, - value = PorterDuffColorFilter(color.toArgb(), PorterDuff.Mode.SRC_ATOP), - // "**" below means match zero or more layers, so ** layerName ** means find layer with that - // name at any depth - keyPath = arrayOf("**", layerName, "**") - ) -} diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt index ed3110c04131..0a6283aa7417 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt @@ -21,6 +21,8 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import com.airbnb.lottie.compose.rememberLottieDynamicProperties import com.android.compose.theme.LocalAndroidColorScheme +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialScreenConfig +import com.android.systemui.inputdevice.tutorial.ui.composable.rememberColorFilterProperty import com.android.systemui.res.R import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState import com.android.systemui.touchpad.tutorial.ui.gesture.HomeGestureMonitor diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt index 14355fa18335..65b452a81da8 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt @@ -34,6 +34,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import com.android.systemui.inputdevice.tutorial.ui.composable.DoneButton import com.android.systemui.res.R @Composable diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt index ad8ab30daa33..256c5b590b14 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt @@ -27,9 +27,11 @@ import androidx.compose.runtime.getValue import androidx.lifecycle.Lifecycle.State.STARTED import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.theme.PlatformTheme +import com.android.systemui.inputdevice.tutorial.ui.composable.ActionKeyTutorialScreen import com.android.systemui.touchpad.tutorial.ui.composable.BackGestureTutorialScreen import com.android.systemui.touchpad.tutorial.ui.composable.HomeGestureTutorialScreen import com.android.systemui.touchpad.tutorial.ui.composable.TutorialSelectionScreen +import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen.ACTION_KEY import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen.BACK_GESTURE import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen.HOME_GESTURE import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen.TUTORIAL_SELECTION @@ -71,7 +73,7 @@ fun TouchpadTutorialScreen(vm: TouchpadTutorialViewModel, closeTutorial: () -> U TutorialSelectionScreen( onBackTutorialClicked = { vm.goTo(BACK_GESTURE) }, onHomeTutorialClicked = { vm.goTo(HOME_GESTURE) }, - onActionKeyTutorialClicked = {}, + onActionKeyTutorialClicked = { vm.goTo(ACTION_KEY) }, onDoneButtonClicked = closeTutorial ) BACK_GESTURE -> @@ -84,5 +86,10 @@ fun TouchpadTutorialScreen(vm: TouchpadTutorialViewModel, closeTutorial: () -> U onDoneButtonClicked = { vm.goTo(TUTORIAL_SELECTION) }, onBack = { vm.goTo(TUTORIAL_SELECTION) }, ) + ACTION_KEY -> // TODO(b/358105049) move action key tutorial to OOBE flow + ActionKeyTutorialScreen( + onDoneButtonClicked = { vm.goTo(TUTORIAL_SELECTION) }, + onBack = { vm.goTo(TUTORIAL_SELECTION) }, + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialViewModel.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialViewModel.kt index 11984af0281a..d3aeaa7e3dca 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialViewModel.kt @@ -55,4 +55,5 @@ enum class Screen { TUTORIAL_SELECTION, BACK_GESTURE, HOME_GESTURE, + ACTION_KEY, } diff --git a/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java b/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java index 56b466249e93..32f2ca6fb696 100644 --- a/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java +++ b/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java @@ -116,7 +116,7 @@ public class CreateUserActivity extends Activity { return mCreateUserDialogController.createDialog( this, this::startActivity, - (mUserCreator.isMultipleAdminEnabled() && mUserCreator.isUserAdmin() + (mUserCreator.canCreateAdminUser() && mUserCreator.isUserAdmin() && !isKeyguardShowing), this::addUserNow, this::finish diff --git a/packages/SystemUI/src/com/android/systemui/user/UserCreator.kt b/packages/SystemUI/src/com/android/systemui/user/UserCreator.kt index 9304a462b6a8..a426da929d6b 100644 --- a/packages/SystemUI/src/com/android/systemui/user/UserCreator.kt +++ b/packages/SystemUI/src/com/android/systemui/user/UserCreator.kt @@ -19,6 +19,7 @@ import android.app.Dialog import android.content.Context import android.content.pm.UserInfo import android.graphics.drawable.Drawable +import android.multiuser.Flags import android.os.UserManager import com.android.internal.util.UserIcons import com.android.settingslib.users.UserCreatingDialog @@ -91,7 +92,17 @@ constructor( return userManager.isAdminUser } - fun isMultipleAdminEnabled(): Boolean { - return UserManager.isMultipleAdminEnabled() + /** + * Checks if the creation of a new admin user is allowed. + * + * @return `true` if creating a new admin is allowed, `false` otherwise. + */ + fun canCreateAdminUser(): Boolean { + return if (Flags.unicornModeRefactoringForHsumReadOnly()) { + UserManager.isMultipleAdminEnabled() && + !userManager.hasUserRestriction(UserManager.DISALLOW_GRANT_ADMIN) + } else { + UserManager.isMultipleAdminEnabled() + } } } diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/FlowDumper.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/FlowDumper.kt index e17274c435aa..ade6c3df2e0f 100644 --- a/packages/SystemUI/src/com/android/systemui/util/kotlin/FlowDumper.kt +++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/FlowDumper.kt @@ -19,11 +19,14 @@ package com.android.systemui.util.kotlin import android.util.IndentingPrintWriter import com.android.systemui.Dumpable import com.android.systemui.dump.DumpManager +import com.android.systemui.lifecycle.SafeActivatable +import com.android.systemui.lifecycle.SysUiViewModel import com.android.systemui.util.asIndenting import com.android.systemui.util.printCollection import java.io.PrintWriter import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.atomic.AtomicBoolean +import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow @@ -64,19 +67,20 @@ interface FlowDumper : Dumpable { } /** - * An implementation of [FlowDumper]. This be extended directly, or can be used to implement - * [FlowDumper] by delegation. - * - * @param dumpManager if provided, this will be used by the [FlowDumperImpl] to register and - * unregister itself when there is something to dump. - * @param tag a static name by which this [FlowDumperImpl] is registered. If not provided, this - * class's name will be used. If you're implementing by delegation, you probably want to provide - * this tag to get a meaningful dumpable name. + * The minimal implementation of FlowDumper. The owner must either register this with the + * DumpManager, or else call [dumpFlows] from its own [Dumpable.dump] method. */ -open class FlowDumperImpl(private val dumpManager: DumpManager?, tag: String? = null) : FlowDumper { +open class SimpleFlowDumper : FlowDumper { + private val stateFlowMap = ConcurrentHashMap<String, StateFlow<*>>() private val sharedFlowMap = ConcurrentHashMap<String, SharedFlow<*>>() private val flowCollectionMap = ConcurrentHashMap<Pair<String, String>, Any>() + + protected fun isNotEmpty(): Boolean = + stateFlowMap.isNotEmpty() || sharedFlowMap.isNotEmpty() || flowCollectionMap.isNotEmpty() + + protected open fun onMapKeysChanged(added: Boolean) {} + override fun dumpFlows(pw: IndentingPrintWriter) { pw.printCollection("StateFlow (value)", stateFlowMap.toSortedMap().entries) { (key, flow) -> append(key).append('=').println(flow.value) @@ -92,43 +96,62 @@ open class FlowDumperImpl(private val dumpManager: DumpManager?, tag: String? = } } - private val Any.idString: String - get() = Integer.toHexString(System.identityHashCode(this)) - override fun <T> Flow<T>.dumpWhileCollecting(dumpName: String): Flow<T> = flow { val mapKey = dumpName to idString try { collect { flowCollectionMap[mapKey] = it ?: "null" - updateRegistration(required = true) + onMapKeysChanged(added = true) emit(it) } } finally { flowCollectionMap.remove(mapKey) - updateRegistration(required = false) + onMapKeysChanged(added = false) } } override fun <T, F : StateFlow<T>> F.dumpValue(dumpName: String): F { stateFlowMap[dumpName] = this + onMapKeysChanged(added = true) return this } override fun <T, F : SharedFlow<T>> F.dumpReplayCache(dumpName: String): F { sharedFlowMap[dumpName] = this + onMapKeysChanged(added = true) return this } - private val dumpManagerName = tag ?: "[$idString] ${javaClass.simpleName}" + protected val Any.idString: String + get() = Integer.toHexString(System.identityHashCode(this)) +} + +/** + * An implementation of [FlowDumper] that registers itself whenever there is something to dump. This + * class is meant to be extended. + * + * @param dumpManager this will be used by the [FlowDumperImpl] to register and unregister itself + * when there is something to dump. + * @param tag a static name by which this [FlowDumperImpl] is registered. If not provided, this + * class's name will be used. + */ +abstract class FlowDumperImpl( + private val dumpManager: DumpManager, + private val tag: String? = null, +) : SimpleFlowDumper() { + + override fun onMapKeysChanged(added: Boolean) { + updateRegistration(required = added) + } + + private val dumpManagerName = "[$idString] ${tag ?: javaClass.simpleName}" + private var registered = AtomicBoolean(false) + private fun updateRegistration(required: Boolean) { - if (dumpManager == null) return if (required && registered.get()) return synchronized(registered) { - val shouldRegister = - stateFlowMap.isNotEmpty() || - sharedFlowMap.isNotEmpty() || - flowCollectionMap.isNotEmpty() + val shouldRegister = isNotEmpty() val wasRegistered = registered.getAndSet(shouldRegister) if (wasRegistered != shouldRegister) { if (shouldRegister) { @@ -140,3 +163,49 @@ open class FlowDumperImpl(private val dumpManager: DumpManager?, tag: String? = } } } + +/** + * A [FlowDumper] that also has an [activateFlowDumper] suspend function that allows the dumper to + * be registered with the [DumpManager] only when activated, just like + * [Activatable.activate()][com.android.systemui.lifecycle.Activatable.activate]. + */ +interface ActivatableFlowDumper : FlowDumper { + suspend fun activateFlowDumper() +} + +/** + * Implementation of [ActivatableFlowDumper] that only registers when activated. + * + * This is generally used to implement [ActivatableFlowDumper] by delegation, especially for + * [SysUiViewModel] implementations. + * + * @param dumpManager used to automatically register and unregister this instance when activated and + * there is something to dump. + * @param tag the name with which this is dumper registered. + */ +class ActivatableFlowDumperImpl( + private val dumpManager: DumpManager, + tag: String, +) : SimpleFlowDumper(), ActivatableFlowDumper { + + private val registration = + object : SafeActivatable() { + override suspend fun onActivated() { + try { + dumpManager.registerCriticalDumpable( + dumpManagerName, + this@ActivatableFlowDumperImpl + ) + awaitCancellation() + } finally { + dumpManager.unregisterDumpable(dumpManagerName) + } + } + } + + private val dumpManagerName = "[$idString] $tag" + + override suspend fun activateFlowDumper() { + registration.activate() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt index 5d8b6f144d97..d39daafd2311 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt @@ -79,13 +79,15 @@ interface AudioModule { localBluetoothManager: LocalBluetoothManager?, @Application coroutineScope: CoroutineScope, @Background coroutineContext: CoroutineContext, + volumeLogger: VolumeLogger ): AudioSharingRepository = if (Flags.enableLeAudioSharing() && localBluetoothManager != null) { AudioSharingRepositoryImpl( contentResolver, localBluetoothManager, coroutineScope, - coroutineContext + coroutineContext, + volumeLogger ) } else { AudioSharingRepositoryEmptyImpl() diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/data/repository/VolumePanelGlobalStateRepository.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/data/repository/VolumePanelGlobalStateRepository.kt index e46ce2699beb..24fb001a1b6d 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/data/repository/VolumePanelGlobalStateRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/data/repository/VolumePanelGlobalStateRepository.kt @@ -19,6 +19,7 @@ package com.android.systemui.volume.panel.data.repository import com.android.systemui.Dumpable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dump.DumpManager +import com.android.systemui.volume.panel.shared.VolumePanelLogger import com.android.systemui.volume.panel.shared.model.VolumePanelGlobalState import java.io.PrintWriter import javax.inject.Inject @@ -27,10 +28,15 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update -private const val TAG = "VolumePanelGlobalState" +private const val TAG = "VolumePanelGlobalStateRepository" @SysUISingleton -class VolumePanelGlobalStateRepository @Inject constructor(dumpManager: DumpManager) : Dumpable { +class VolumePanelGlobalStateRepository +@Inject +constructor( + dumpManager: DumpManager, + private val logger: VolumePanelLogger, +) : Dumpable { private val mutableGlobalState = MutableStateFlow( @@ -48,6 +54,7 @@ class VolumePanelGlobalStateRepository @Inject constructor(dumpManager: DumpMana update: (currentState: VolumePanelGlobalState) -> VolumePanelGlobalState ) { mutableGlobalState.update(update) + logger.onVolumePanelGlobalStateChanged(mutableGlobalState.value) } override fun dump(pw: PrintWriter, args: Array<out String>) { diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractor.kt index 5301b008bab7..9de862a814d6 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractor.kt @@ -19,6 +19,7 @@ package com.android.systemui.volume.panel.domain.interactor import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria import com.android.systemui.volume.panel.domain.model.ComponentModel +import com.android.systemui.volume.panel.shared.VolumePanelLogger import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey import javax.inject.Inject import javax.inject.Provider @@ -26,8 +27,12 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.conflate +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.shareIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.stateIn interface ComponentsInteractor { @@ -45,6 +50,7 @@ constructor( enabledComponents: Collection<VolumePanelComponentKey>, defaultCriteria: Provider<ComponentAvailabilityCriteria>, @VolumePanelScope coroutineScope: CoroutineScope, + private val logger: VolumePanelLogger, private val criteriaByKey: Map< VolumePanelComponentKey, @@ -57,12 +63,18 @@ constructor( combine( enabledComponents.map { componentKey -> val componentCriteria = (criteriaByKey[componentKey] ?: defaultCriteria).get() - componentCriteria.isAvailable().map { isAvailable -> - ComponentModel(componentKey, isAvailable = isAvailable) - } + componentCriteria + .isAvailable() + .distinctUntilChanged() + .conflate() + .onEach { logger.onComponentAvailabilityChanged(componentKey, it) } + .map { isAvailable -> + ComponentModel(componentKey, isAvailable = isAvailable) + } } ) { it.asList() } - .shareIn(coroutineScope, SharingStarted.Eagerly, replay = 1) + .stateIn(coroutineScope, SharingStarted.Eagerly, null) + .filterNotNull() } diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/shared/VolumePanelLogger.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/VolumePanelLogger.kt index cc513b5d820c..276326cbf430 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/shared/VolumePanelLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/VolumePanelLogger.kt @@ -20,15 +20,41 @@ import com.android.settingslib.volume.shared.model.AudioStream import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.LogLevel import com.android.systemui.log.dagger.VolumeLog -import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope +import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey +import com.android.systemui.volume.panel.shared.model.VolumePanelGlobalState +import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelState import javax.inject.Inject private const val TAG = "SysUI_VolumePanel" /** Logs events related to the Volume Panel. */ -@VolumePanelScope class VolumePanelLogger @Inject constructor(@VolumeLog private val logBuffer: LogBuffer) { + fun onVolumePanelStateChanged(state: VolumePanelState) { + logBuffer.log(TAG, LogLevel.DEBUG, { str1 = state.toString() }, { "State changed: $str1" }) + } + + fun onComponentAvailabilityChanged(key: VolumePanelComponentKey, isAvailable: Boolean) { + logBuffer.log( + TAG, + LogLevel.DEBUG, + { + str1 = key + bool1 = isAvailable + }, + { "$str1 isAvailable=$bool1" } + ) + } + + fun onVolumePanelGlobalStateChanged(globalState: VolumePanelGlobalState) { + logBuffer.log( + TAG, + LogLevel.DEBUG, + { bool1 = globalState.isVisible }, + { "Global state changed: isVisible=$bool1" } + ) + } + fun onSetVolumeRequested(audioStream: AudioStream, volume: Int) { logBuffer.log( TAG, diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/ComponentsLayout.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/ComponentsLayout.kt index 1c51236689d8..a06d3e3c6785 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/ComponentsLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/ComponentsLayout.kt @@ -17,6 +17,7 @@ package com.android.systemui.volume.panel.ui.layout import com.android.systemui.volume.panel.ui.viewmodel.ComponentState +import com.android.systemui.volume.panel.ui.viewmodel.toLogString /** Represents components grouping into the layout. */ data class ComponentsLayout( @@ -29,3 +30,12 @@ data class ComponentsLayout( /** This is a separated entity that is always visible on the bottom of the Volume Panel. */ val bottomBarComponent: ComponentState, ) + +fun ComponentsLayout.toLogString(): String { + return "(" + + " headerComponents=${headerComponents.joinToString { it.toLogString() }}" + + " contentComponents=${contentComponents.joinToString { it.toLogString() }}" + + " footerComponents=${footerComponents.joinToString { it.toLogString() }}" + + " bottomBarComponent=${bottomBarComponent.toLogString()}" + + " )" +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/ComponentState.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/ComponentState.kt index 5f4dbfb4235e..41c80fa58527 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/ComponentState.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/ComponentState.kt @@ -32,3 +32,5 @@ data class ComponentState( val component: VolumePanelUiComponent, val isVisible: Boolean, ) + +fun ComponentState.toLogString(): String = "$key:visible=$isVisible" diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt index f495a02f6cc7..2f60c4b29a81 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt @@ -19,23 +19,30 @@ package com.android.systemui.volume.panel.ui.viewmodel import android.content.Context import android.content.IntentFilter import android.content.res.Resources +import com.android.systemui.Dumpable import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dump.DumpManager import com.android.systemui.res.R import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.onConfigChanged +import com.android.systemui.util.kotlin.launchAndDispose import com.android.systemui.volume.VolumePanelDialogReceiver import com.android.systemui.volume.panel.dagger.VolumePanelComponent import com.android.systemui.volume.panel.dagger.factory.VolumePanelComponentFactory import com.android.systemui.volume.panel.domain.VolumePanelStartable import com.android.systemui.volume.panel.domain.interactor.ComponentsInteractor import com.android.systemui.volume.panel.domain.interactor.VolumePanelGlobalStateInteractor +import com.android.systemui.volume.panel.shared.VolumePanelLogger import com.android.systemui.volume.panel.ui.composable.ComponentsFactory import com.android.systemui.volume.panel.ui.layout.ComponentsLayout import com.android.systemui.volume.panel.ui.layout.ComponentsLayoutManager +import com.android.systemui.volume.panel.ui.layout.toLogString +import java.io.PrintWriter import javax.inject.Inject import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.DisposableHandle +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine @@ -43,19 +50,23 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart -import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.stateIn +private const val TAG = "VolumePanelViewModel" + // Can't inject a constructor here because VolumePanelComponent provides this view model for its // components. +@OptIn(ExperimentalCoroutinesApi::class) class VolumePanelViewModel( resources: Resources, coroutineScope: CoroutineScope, daggerComponentFactory: VolumePanelComponentFactory, configurationController: ConfigurationController, broadcastDispatcher: BroadcastDispatcher, + private val dumpManager: DumpManager, + private val logger: VolumePanelLogger, private val volumePanelGlobalStateInteractor: VolumePanelGlobalStateInteractor, -) { +) : Dumpable { private val volumePanelComponent: VolumePanelComponent = daggerComponentFactory.create(this, coroutineScope) @@ -77,9 +88,10 @@ class VolumePanelViewModel( .onStart { emit(resources.configuration) } .map { configuration -> VolumePanelState( - orientation = configuration.orientation, - isLargeScreen = resources.getBoolean(R.bool.volume_panel_is_large_screen), - ) + orientation = configuration.orientation, + isLargeScreen = resources.getBoolean(R.bool.volume_panel_is_large_screen), + ) + .also { logger.onVolumePanelStateChanged(it) } } .stateIn( scope, @@ -89,7 +101,7 @@ class VolumePanelViewModel( isLargeScreen = resources.getBoolean(R.bool.volume_panel_is_large_screen) ), ) - val componentsLayout: Flow<ComponentsLayout> = + val componentsLayout: StateFlow<ComponentsLayout?> = combine( componentsInteractor.components, volumePanelState, @@ -104,13 +116,18 @@ class VolumePanelViewModel( } componentsLayoutManager.layout(scope, componentStates) } - .shareIn( + .stateIn( scope, SharingStarted.Eagerly, - replay = 1, + null, ) init { + scope.launchAndDispose { + dumpManager.registerNormalDumpable(TAG, this) + DisposableHandle { dumpManager.unregisterDumpable(TAG) } + } + volumePanelComponent.volumePanelStartables().onEach(VolumePanelStartable::start) broadcastDispatcher .broadcastFlow(IntentFilter(VolumePanelDialogReceiver.DISMISS_ACTION)) @@ -122,6 +139,13 @@ class VolumePanelViewModel( volumePanelGlobalStateInteractor.setVisible(false) } + override fun dump(pw: PrintWriter, args: Array<out String>) { + with(pw) { + println("volumePanelState=${volumePanelState.value}") + println("componentsLayout=${componentsLayout.value?.toLogString()}") + } + } + class Factory @Inject constructor( @@ -129,6 +153,8 @@ class VolumePanelViewModel( private val daggerComponentFactory: VolumePanelComponentFactory, private val configurationController: ConfigurationController, private val broadcastDispatcher: BroadcastDispatcher, + private val dumpManager: DumpManager, + private val logger: VolumePanelLogger, private val volumePanelGlobalStateInteractor: VolumePanelGlobalStateInteractor, ) { @@ -139,6 +165,8 @@ class VolumePanelViewModel( daggerComponentFactory, configurationController, broadcastDispatcher, + dumpManager, + logger, volumePanelGlobalStateInteractor, ) } diff --git a/packages/SystemUI/src/com/android/systemui/volume/shared/VolumeLogger.kt b/packages/SystemUI/src/com/android/systemui/volume/shared/VolumeLogger.kt index 869a82a78848..d6b159e9b13a 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/shared/VolumeLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/shared/VolumeLogger.kt @@ -16,7 +16,8 @@ package com.android.systemui.volume.shared -import com.android.settingslib.volume.data.repository.AudioRepositoryImpl +import com.android.settingslib.volume.shared.AudioLogger +import com.android.settingslib.volume.shared.AudioSharingLogger import com.android.settingslib.volume.shared.model.AudioStream import com.android.settingslib.volume.shared.model.AudioStreamModel import com.android.systemui.dagger.SysUISingleton @@ -30,7 +31,7 @@ private const val TAG = "SysUI_Volume" /** Logs general System UI volume events. */ @SysUISingleton class VolumeLogger @Inject constructor(@VolumeLog private val logBuffer: LogBuffer) : - AudioRepositoryImpl.Logger { + AudioLogger, AudioSharingLogger { override fun onSetVolumeRequested(audioStream: AudioStream, volume: Int) { logBuffer.log( @@ -55,4 +56,35 @@ class VolumeLogger @Inject constructor(@VolumeLog private val logBuffer: LogBuff { "Volume update received: stream=$str1 volume=$int1" } ) } + + override fun onAudioSharingStateChanged(state: Boolean) { + logBuffer.log( + TAG, + LogLevel.DEBUG, + { bool1 = state }, + { "Audio sharing state update: state=$bool1" } + ) + } + + override fun onSecondaryGroupIdChanged(groupId: Int) { + logBuffer.log( + TAG, + LogLevel.DEBUG, + { int1 = groupId }, + { "Secondary group id in audio sharing update: groupId=$int1" } + ) + } + + override fun onVolumeMapChanged(map: Map<Int, Int>) { + logBuffer.log( + TAG, + LogLevel.DEBUG, + { str1 = map.toString() }, + { "Volume map update: map=$str1" } + ) + } + + override fun onSetDeviceVolumeRequested(volume: Int) { + logBuffer.log(TAG, LogLevel.DEBUG, { int1 = volume }, { "Set device volume: volume=$int1" }) + } } diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java index 9ca0591f9c5b..50efc21f50fc 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java @@ -34,6 +34,8 @@ import android.content.pm.UserInfo; import android.content.res.Configuration; import android.graphics.Rect; import android.inputmethodservice.InputMethodService; +import android.inputmethodservice.InputMethodService.BackDispositionMode; +import android.inputmethodservice.InputMethodService.ImeWindowVisibility; import android.util.Log; import android.view.Display; import android.view.KeyEvent; @@ -378,8 +380,8 @@ public final class WMShell implements } @Override - public void setImeWindowStatus(int displayId, int vis, int backDisposition, - boolean showImeSwitcher) { + public void setImeWindowStatus(int displayId, @ImeWindowVisibility int vis, + @BackDispositionMode int backDisposition, boolean showImeSwitcher) { if (displayId == mDisplayTracker.getDefaultDisplayId() && (vis & InputMethodService.IME_VISIBLE) != 0) { oneHanded.stopOneHanded( diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java index 997f8a844d69..344d065979f9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java @@ -80,6 +80,8 @@ import androidx.annotation.Nullable; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; +import com.android.app.viewcapture.ViewCapture; +import com.android.app.viewcapture.ViewCaptureAwareWindowManager; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.biometrics.AuthController; import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository; @@ -109,6 +111,8 @@ import com.android.systemui.util.settings.FakeSettings; import com.android.systemui.util.settings.SecureSettings; import com.android.systemui.util.time.FakeSystemClock; +import kotlin.Lazy; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -127,6 +131,7 @@ public class ScreenDecorationsTest extends SysuiTestCase { private ScreenDecorations mScreenDecorations; private WindowManager mWindowManager; + private ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager; private DisplayManager mDisplayManager; private SecureSettings mSecureSettings; private FakeExecutor mExecutor; @@ -173,6 +178,8 @@ public class ScreenDecorationsTest extends SysuiTestCase { private CutoutDecorProviderFactory mCutoutFactory; @Mock private JavaAdapter mJavaAdapter; + @Mock + private Lazy<ViewCapture> mLazyViewCapture; private FakeFacePropertyRepository mFakeFacePropertyRepository = new FakeFacePropertyRepository(); @@ -245,12 +252,15 @@ public class ScreenDecorationsTest extends SysuiTestCase { new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")), mFakeFacePropertyRepository)); + mViewCaptureAwareWindowManager = new ViewCaptureAwareWindowManager(mWindowManager, + mLazyViewCapture, false); mScreenDecorations = spy(new ScreenDecorations(mContext, mSecureSettings, mCommandRegistry, mUserTracker, mDisplayTracker, mDotViewController, mThreadFactory, mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory, new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")), - mFakeFacePropertyRepository, mJavaAdapter, mCameraProtectionLoader) { + mFakeFacePropertyRepository, mJavaAdapter, mCameraProtectionLoader, + mViewCaptureAwareWindowManager) { @Override public void start() { super.start(); @@ -1264,7 +1274,8 @@ public class ScreenDecorationsTest extends SysuiTestCase { mDotViewController, mThreadFactory, mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory, new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")), - mFakeFacePropertyRepository, mJavaAdapter, mCameraProtectionLoader); + mFakeFacePropertyRepository, mJavaAdapter, mCameraProtectionLoader, + mViewCaptureAwareWindowManager); screenDecorations.start(); when(mContext.getDisplay()).thenReturn(mDisplay); when(mDisplay.getDisplayInfo(any())).thenAnswer(new Answer<Boolean>() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java index b71739ac6434..5ff3915c76f9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java @@ -37,6 +37,8 @@ import android.view.accessibility.AccessibilityManager; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; +import com.android.app.viewcapture.ViewCapture; +import com.android.app.viewcapture.ViewCaptureAwareWindowManager; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.systemui.Dependency; @@ -46,6 +48,8 @@ import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver; import com.android.systemui.settings.FakeDisplayTracker; import com.android.systemui.util.settings.SecureSettings; +import kotlin.Lazy; + import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -71,6 +75,7 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase { private Context mContextWrapper; private WindowManager mWindowManager; + private ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager; private AccessibilityManager mAccessibilityManager; private KeyguardUpdateMonitor mKeyguardUpdateMonitor; private AccessibilityFloatingMenuController mController; @@ -83,6 +88,8 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase { private KeyguardUpdateMonitorCallback mKeyguardCallback; @Mock private SecureSettings mSecureSettings; + @Mock + private Lazy<ViewCapture> mLazyViewCapture; @Before public void setUp() throws Exception { @@ -95,6 +102,8 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase { }; mWindowManager = mContext.getSystemService(WindowManager.class); + mViewCaptureAwareWindowManager = new ViewCaptureAwareWindowManager(mWindowManager, + mLazyViewCapture, /* isViewCaptureEnabled= */ false); mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class); when(mTargetsObserver.getCurrentAccessibilityButtonTargets()) @@ -154,7 +163,7 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase { enableAccessibilityFloatingMenuConfig(); mController = setUpController(); mController.mFloatingMenu = new MenuViewLayerController(mContextWrapper, mWindowManager, - mAccessibilityManager, mSecureSettings); + mViewCaptureAwareWindowManager, mAccessibilityManager, mSecureSettings); captureKeyguardUpdateMonitorCallback(); mKeyguardCallback.onUserUnlocked(); @@ -181,7 +190,7 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase { enableAccessibilityFloatingMenuConfig(); mController = setUpController(); mController.mFloatingMenu = new MenuViewLayerController(mContextWrapper, mWindowManager, - mAccessibilityManager, mSecureSettings); + mViewCaptureAwareWindowManager, mAccessibilityManager, mSecureSettings); captureKeyguardUpdateMonitorCallback(); mKeyguardCallback.onUserSwitching(fakeUserId); @@ -195,7 +204,7 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase { enableAccessibilityFloatingMenuConfig(); mController = setUpController(); mController.mFloatingMenu = new MenuViewLayerController(mContextWrapper, mWindowManager, - mAccessibilityManager, mSecureSettings); + mViewCaptureAwareWindowManager, mAccessibilityManager, mSecureSettings); captureKeyguardUpdateMonitorCallback(); mKeyguardCallback.onUserUnlocked(); mKeyguardCallback.onKeyguardVisibilityChanged(true); @@ -321,13 +330,17 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase { private AccessibilityFloatingMenuController setUpController() { final WindowManager windowManager = mContext.getSystemService(WindowManager.class); + final ViewCaptureAwareWindowManager viewCaptureAwareWindowManager = + new ViewCaptureAwareWindowManager(windowManager, mLazyViewCapture, + /* isViewCaptureEnabled= */ false); final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class); final FakeDisplayTracker displayTracker = new FakeDisplayTracker(mContext); mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class); final AccessibilityFloatingMenuController controller = new AccessibilityFloatingMenuController(mContextWrapper, windowManager, - displayManager, mAccessibilityManager, mTargetsObserver, mModeObserver, - mKeyguardUpdateMonitor, mSecureSettings, displayTracker); + viewCaptureAwareWindowManager, displayManager, mAccessibilityManager, + mTargetsObserver, mModeObserver, mKeyguardUpdateMonitor, mSecureSettings, + displayTracker); controller.init(); return controller; diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java index bd1a7f072832..07ce7b9352c1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java @@ -38,9 +38,13 @@ import android.view.accessibility.AccessibilityManager; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; +import com.android.app.viewcapture.ViewCapture; +import com.android.app.viewcapture.ViewCaptureAwareWindowManager; import com.android.systemui.SysuiTestCase; import com.android.systemui.util.settings.SecureSettings; +import kotlin.Lazy; + import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -69,11 +73,16 @@ public class MenuViewLayerControllerTest extends SysuiTestCase { @Mock private WindowMetrics mWindowMetrics; + @Mock + private Lazy<ViewCapture> mLazyViewCapture; + private MenuViewLayerController mMenuViewLayerController; @Before public void setUp() throws Exception { final WindowManager wm = mContext.getSystemService(WindowManager.class); + final ViewCaptureAwareWindowManager viewCaptureAwareWm = new ViewCaptureAwareWindowManager( + mWindowManager, mLazyViewCapture, /* isViewCaptureEnabled= */ false); doAnswer(invocation -> wm.getMaximumWindowMetrics()).when( mWindowManager).getMaximumWindowMetrics(); mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager); @@ -81,7 +90,7 @@ public class MenuViewLayerControllerTest extends SysuiTestCase { when(mWindowMetrics.getBounds()).thenReturn(new Rect(0, 0, 1080, 2340)); when(mWindowMetrics.getWindowInsets()).thenReturn(stubDisplayInsets()); mMenuViewLayerController = new MenuViewLayerController(mContext, mWindowManager, - mAccessibilityManager, mSecureSettings); + viewCaptureAwareWm, mAccessibilityManager, mSecureSettings); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsControllerTest.java new file mode 100644 index 000000000000..2ac5d105ba99 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsControllerTest.java @@ -0,0 +1,285 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.accessibility.hearingaid; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import static java.util.Collections.emptyList; + +import android.bluetooth.BluetoothCsipSetCoordinator; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothHapClient; +import android.bluetooth.BluetoothHapPresetInfo; +import android.testing.TestableLooper; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.HapClientProfile; +import com.android.settingslib.bluetooth.LocalBluetoothProfile; +import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; +import com.android.systemui.SysuiTestCase; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.List; +import java.util.concurrent.Executor; + +/** Tests for {@link HearingDevicesPresetsController}. */ +@RunWith(AndroidJUnit4.class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +@SmallTest +public class HearingDevicesPresetsControllerTest extends SysuiTestCase { + + private static final int TEST_PRESET_INDEX = 1; + private static final String TEST_PRESET_NAME = "test_preset"; + private static final int TEST_HAP_GROUP_ID = 1; + private static final int TEST_REASON = 1024; + + @Rule + public MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private LocalBluetoothProfileManager mProfileManager; + @Mock + private HapClientProfile mHapClientProfile; + @Mock + private CachedBluetoothDevice mCachedBluetoothDevice; + @Mock + private CachedBluetoothDevice mSubCachedBluetoothDevice; + @Mock + private BluetoothDevice mBluetoothDevice; + @Mock + private BluetoothDevice mSubBluetoothDevice; + + @Mock + private HearingDevicesPresetsController.PresetCallback mCallback; + + private HearingDevicesPresetsController mController; + + @Before + public void setUp() { + when(mProfileManager.getHapClientProfile()).thenReturn(mHapClientProfile); + when(mHapClientProfile.isProfileReady()).thenReturn(true); + when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice); + when(mCachedBluetoothDevice.getSubDevice()).thenReturn(mSubCachedBluetoothDevice); + when(mSubCachedBluetoothDevice.getDevice()).thenReturn(mSubBluetoothDevice); + + mController = new HearingDevicesPresetsController(mProfileManager, mCallback); + } + + @Test + public void onServiceConnected_callExpectedCallback() { + mController.onServiceConnected(); + + verify(mHapClientProfile).registerCallback(any(Executor.class), + any(BluetoothHapClient.Callback.class)); + verify(mCallback).onPresetInfoUpdated(anyList(), anyInt()); + } + + @Test + public void getAllPresetInfo_setInvalidHearingDevice_getEmpty() { + when(mCachedBluetoothDevice.getProfiles()).thenReturn(emptyList()); + mController.setHearingDeviceIfSupportHap(mCachedBluetoothDevice); + BluetoothHapPresetInfo hapPresetInfo = getHapPresetInfo(true); + when(mHapClientProfile.getAllPresetInfo(mBluetoothDevice)).thenReturn( + List.of(hapPresetInfo)); + + assertThat(mController.getAllPresetInfo()).isEmpty(); + } + + @Test + public void getAllPresetInfo_containsNotAvailablePresetInfo_getEmpty() { + setValidHearingDeviceSupportHap(); + BluetoothHapPresetInfo hapPresetInfo = getHapPresetInfo(false); + when(mHapClientProfile.getAllPresetInfo(mBluetoothDevice)).thenReturn( + List.of(hapPresetInfo)); + + assertThat(mController.getAllPresetInfo()).isEmpty(); + } + + @Test + public void getAllPresetInfo_containsOnePresetInfo_getOnePresetInfo() { + setValidHearingDeviceSupportHap(); + BluetoothHapPresetInfo hapPresetInfo = getHapPresetInfo(true); + when(mHapClientProfile.getAllPresetInfo(mBluetoothDevice)).thenReturn( + List.of(hapPresetInfo)); + + assertThat(mController.getAllPresetInfo()).contains(hapPresetInfo); + } + + @Test + public void getActivePresetIndex_getExpectedIndex() { + setValidHearingDeviceSupportHap(); + when(mHapClientProfile.getActivePresetIndex(mBluetoothDevice)).thenReturn( + TEST_PRESET_INDEX); + + assertThat(mController.getActivePresetIndex()).isEqualTo(TEST_PRESET_INDEX); + } + + @Test + public void onPresetSelected_presetIndex_callOnPresetInfoUpdatedWithExpectedPresetIndex() { + setValidHearingDeviceSupportHap(); + BluetoothHapPresetInfo hapPresetInfo = getHapPresetInfo(true); + when(mHapClientProfile.getAllPresetInfo(mBluetoothDevice)).thenReturn( + List.of(hapPresetInfo)); + when(mHapClientProfile.getActivePresetIndex(mBluetoothDevice)).thenReturn( + TEST_PRESET_INDEX); + + mController.onPresetSelected(mBluetoothDevice, TEST_PRESET_INDEX, TEST_REASON); + + verify(mCallback).onPresetInfoUpdated(eq(List.of(hapPresetInfo)), eq(TEST_PRESET_INDEX)); + } + + @Test + public void onPresetInfoChanged_presetIndex_callOnPresetInfoUpdatedWithExpectedPresetIndex() { + setValidHearingDeviceSupportHap(); + BluetoothHapPresetInfo hapPresetInfo = getHapPresetInfo(true); + when(mHapClientProfile.getAllPresetInfo(mBluetoothDevice)).thenReturn( + List.of(hapPresetInfo)); + when(mHapClientProfile.getActivePresetIndex(mBluetoothDevice)).thenReturn( + TEST_PRESET_INDEX); + + mController.onPresetInfoChanged(mBluetoothDevice, List.of(hapPresetInfo), TEST_REASON); + + verify(mCallback).onPresetInfoUpdated(List.of(hapPresetInfo), TEST_PRESET_INDEX); + } + + @Test + public void onPresetSelectionFailed_callOnPresetCommandFailed() { + setValidHearingDeviceSupportHap(); + + mController.onPresetSelectionFailed(mBluetoothDevice, TEST_REASON); + + verify(mCallback).onPresetCommandFailed(TEST_REASON); + } + + @Test + public void onSetPresetNameFailed_callOnPresetCommandFailed() { + setValidHearingDeviceSupportHap(); + + mController.onSetPresetNameFailed(mBluetoothDevice, TEST_REASON); + + verify(mCallback).onPresetCommandFailed(TEST_REASON); + } + + @Test + public void onPresetSelectionForGroupFailed_callSelectPresetIndividual() { + setValidHearingDeviceSupportHap(); + mController.selectPreset(TEST_PRESET_INDEX); + Mockito.reset(mHapClientProfile); + when(mHapClientProfile.getHapGroup(mBluetoothDevice)).thenReturn(TEST_HAP_GROUP_ID); + + mController.onPresetSelectionForGroupFailed(TEST_HAP_GROUP_ID, TEST_REASON); + + + verify(mHapClientProfile).selectPreset(mBluetoothDevice, TEST_PRESET_INDEX); + verify(mHapClientProfile).selectPreset(mSubBluetoothDevice, TEST_PRESET_INDEX); + } + + @Test + public void onSetPresetNameForGroupFailed_callOnPresetCommandFailed() { + setValidHearingDeviceSupportHap(); + + mController.onSetPresetNameForGroupFailed(TEST_HAP_GROUP_ID, TEST_REASON); + + verify(mCallback).onPresetCommandFailed(TEST_REASON); + } + + @Test + public void registerHapCallback_callHapRegisterCallback() { + mController.registerHapCallback(); + + verify(mHapClientProfile).registerCallback(any(Executor.class), + any(BluetoothHapClient.Callback.class)); + } + + @Test + public void unregisterHapCallback_callHapUnregisterCallback() { + mController.unregisterHapCallback(); + + verify(mHapClientProfile).unregisterCallback(any(BluetoothHapClient.Callback.class)); + } + + @Test + public void selectPreset_supportSynchronized_validGroupId_callSelectPresetForGroup() { + setValidHearingDeviceSupportHap(); + when(mHapClientProfile.supportsSynchronizedPresets(mBluetoothDevice)).thenReturn(true); + when(mHapClientProfile.getHapGroup(mBluetoothDevice)).thenReturn(TEST_HAP_GROUP_ID); + + mController.selectPreset(TEST_PRESET_INDEX); + + verify(mHapClientProfile).selectPresetForGroup(TEST_HAP_GROUP_ID, TEST_PRESET_INDEX); + } + + @Test + public void selectPreset_supportSynchronized_invalidGroupId_callSelectPresetIndividual() { + setValidHearingDeviceSupportHap(); + when(mHapClientProfile.supportsSynchronizedPresets(mBluetoothDevice)).thenReturn(true); + when(mHapClientProfile.getHapGroup(mBluetoothDevice)).thenReturn( + BluetoothCsipSetCoordinator.GROUP_ID_INVALID); + + mController.selectPreset(TEST_PRESET_INDEX); + + verify(mHapClientProfile).selectPreset(mBluetoothDevice, TEST_PRESET_INDEX); + verify(mHapClientProfile).selectPreset(mSubBluetoothDevice, TEST_PRESET_INDEX); + } + + @Test + public void selectPreset_notSupportSynchronized_validGroupId_callSelectPresetIndividual() { + setValidHearingDeviceSupportHap(); + when(mHapClientProfile.supportsSynchronizedPresets(mBluetoothDevice)).thenReturn(false); + when(mHapClientProfile.getHapGroup(mBluetoothDevice)).thenReturn(TEST_HAP_GROUP_ID); + + mController.selectPreset(TEST_PRESET_INDEX); + + verify(mHapClientProfile).selectPreset(mBluetoothDevice, TEST_PRESET_INDEX); + verify(mHapClientProfile).selectPreset(mSubBluetoothDevice, TEST_PRESET_INDEX); + } + + private BluetoothHapPresetInfo getHapPresetInfo(boolean available) { + BluetoothHapPresetInfo info = mock(BluetoothHapPresetInfo.class); + when(info.getName()).thenReturn(TEST_PRESET_NAME); + when(info.getIndex()).thenReturn(TEST_PRESET_INDEX); + when(info.isAvailable()).thenReturn(available); + return info; + } + + private void setValidHearingDeviceSupportHap() { + LocalBluetoothProfile hapClientProfile = mock(HapClientProfile.class); + List<LocalBluetoothProfile> profiles = List.of(hapClientProfile); + when(mCachedBluetoothDevice.getProfiles()).thenReturn(profiles); + + mController.setHearingDeviceIfSupportHap(mCachedBluetoothDevice); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt index dc69cdadc320..f94a6f24a106 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt @@ -29,7 +29,6 @@ import android.hardware.biometrics.PromptVerticalListContentView import android.hardware.face.FaceSensorPropertiesInternal import android.hardware.fingerprint.FingerprintManager import android.hardware.fingerprint.FingerprintSensorPropertiesInternal -import android.os.Handler import android.os.IBinder import android.os.UserManager import android.testing.TestableLooper @@ -45,7 +44,6 @@ import androidx.test.filters.SmallTest import com.android.internal.jank.InteractionJankMonitor import com.android.internal.widget.LockPatternUtils import com.android.launcher3.icons.IconProvider -import com.android.systemui.Flags.FLAG_CONSTRAINT_BP import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.data.repository.FakeBiometricStatusRepository import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository @@ -105,7 +103,6 @@ open class AuthContainerViewTest : SysuiTestCase() { @Mock lateinit var fingerprintManager: FingerprintManager @Mock lateinit var lockPatternUtils: LockPatternUtils @Mock lateinit var wakefulnessLifecycle: WakefulnessLifecycle - @Mock lateinit var panelInteractionDetector: AuthDialogPanelInteractionDetector @Mock lateinit var windowToken: IBinder @Mock lateinit var interactionJankMonitor: InteractionJankMonitor @Mock lateinit var vibrator: VibratorHelper @@ -265,14 +262,6 @@ open class AuthContainerViewTest : SysuiTestCase() { } @Test - fun testActionCancel_panelInteractionDetectorDisable() { - val container = initializeFingerprintContainer() - container.mBiometricCallback.onUserCanceled() - waitForIdleSync() - verify(panelInteractionDetector).disable() - } - - @Test fun testActionAuthenticated_sendsDismissedAuthenticated() { val container = initializeFingerprintContainer() container.mBiometricCallback.onAuthenticated() @@ -416,19 +405,7 @@ open class AuthContainerViewTest : SysuiTestCase() { } @Test - fun testShowBiometricUI() { - mSetFlagsRule.disableFlags(FLAG_CONSTRAINT_BP) - val container = initializeFingerprintContainer() - - waitForIdleSync() - - assertThat(container.hasCredentialView()).isFalse() - assertThat(container.hasBiometricPrompt()).isTrue() - } - - @Test fun testShowBiometricUI_ContentViewWithMoreOptionsButton() { - mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP) mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) var isButtonClicked = false val contentView = @@ -466,7 +443,6 @@ open class AuthContainerViewTest : SysuiTestCase() { @Test fun testShowCredentialUI_withVerticalListContentView() { - mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP) mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) val container = initializeFingerprintContainer( @@ -488,7 +464,6 @@ open class AuthContainerViewTest : SysuiTestCase() { @Test fun testShowCredentialUI_withContentViewWithMoreOptionsButton() { - mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP) mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) val contentView = PromptContentViewWithMoreOptionsButton.Builder() @@ -674,7 +649,6 @@ open class AuthContainerViewTest : SysuiTestCase() { fingerprintProps, faceProps, wakefulnessLifecycle, - panelInteractionDetector, userManager, lockPatternUtils, interactionJankMonitor, @@ -690,7 +664,6 @@ open class AuthContainerViewTest : SysuiTestCase() { activityTaskManager ), { credentialViewModel }, - Handler(TestableLooper.get(this).looper), fakeExecutor, vibrator ) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt deleted file mode 100644 index 023148603b50..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.biometrics - -import android.platform.test.flag.junit.FlagsParameterization -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import com.android.systemui.flags.andSceneContainer -import com.android.systemui.kosmos.applicationCoroutineScope -import com.android.systemui.kosmos.testScope -import com.android.systemui.shade.domain.interactor.shadeInteractor -import com.android.systemui.shade.shadeTestUtil -import com.android.systemui.testKosmos -import kotlinx.coroutines.test.runCurrent -import kotlinx.coroutines.test.runTest -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.Mock -import org.mockito.Mockito.verify -import org.mockito.MockitoAnnotations -import org.mockito.kotlin.verifyZeroInteractions -import platform.test.runner.parameterized.ParameterizedAndroidJunit4 -import platform.test.runner.parameterized.Parameters - -@SmallTest -@RunWith(ParameterizedAndroidJunit4::class) -class AuthDialogPanelInteractionDetectorTest(flags: FlagsParameterization?) : SysuiTestCase() { - - companion object { - @JvmStatic - @Parameters(name = "{0}") - fun getParams(): List<FlagsParameterization> { - return FlagsParameterization.allCombinationsOf().andSceneContainer() - } - } - - init { - mSetFlagsRule.setFlagsParameterization(flags!!) - } - - private val kosmos = testKosmos() - private val testScope = kosmos.testScope - - private val shadeTestUtil by lazy { kosmos.shadeTestUtil } - - @Mock private lateinit var action: Runnable - - lateinit var detector: AuthDialogPanelInteractionDetector - - @Before - fun setUp() { - MockitoAnnotations.initMocks(this) - detector = - AuthDialogPanelInteractionDetector( - kosmos.applicationCoroutineScope, - { kosmos.shadeInteractor }, - ) - } - - @Test - fun enableDetector_expand_shouldRunAction() = - testScope.runTest { - // GIVEN shade is closed and detector is enabled - shadeTestUtil.setShadeExpansion(0f) - detector.enable(action) - runCurrent() - - // WHEN shade expands - shadeTestUtil.setTracking(true) - shadeTestUtil.setShadeExpansion(.5f) - runCurrent() - - // THEN action was run - verify(action).run() - } - - @Test - fun enableDetector_isUserInteractingTrue_shouldNotPostRunnable() = - testScope.runTest { - // GIVEN isInteracting starts true - shadeTestUtil.setTracking(true) - runCurrent() - detector.enable(action) - - // THEN action was not run - verifyZeroInteractions(action) - } - - @Test - fun enableDetector_shadeExpandImmediate_shouldNotPostRunnable() = - testScope.runTest { - // GIVEN shade is closed and detector is enabled - shadeTestUtil.setShadeExpansion(0f) - detector.enable(action) - runCurrent() - - // WHEN shade expands fully instantly - shadeTestUtil.setShadeExpansion(1f) - runCurrent() - - // THEN action not run - verifyZeroInteractions(action) - detector.disable() - } - - @Test - fun disableDetector_shouldNotPostRunnable() = - testScope.runTest { - // GIVEN shade is closed and detector is enabled - shadeTestUtil.setShadeExpansion(0f) - detector.enable(action) - runCurrent() - - // WHEN detector is disabled and shade opens - detector.disable() - runCurrent() - shadeTestUtil.setTracking(true) - shadeTestUtil.setShadeExpansion(.5f) - runCurrent() - - // THEN action not run - verifyZeroInteractions(action) - } - - @Test - fun enableDetector_beginCollapse_shouldNotPostRunnable() = - testScope.runTest { - // GIVEN shade is open and detector is enabled - shadeTestUtil.setShadeExpansion(1f) - detector.enable(action) - runCurrent() - - // WHEN shade begins to collapse - shadeTestUtil.programmaticCollapseShade() - runCurrent() - - // THEN action not run - verifyZeroInteractions(action) - detector.disable() - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt index 720f2071ac73..dc499cd2fe8d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt @@ -155,7 +155,7 @@ class PromptSelectorInteractorImplTest : SysuiTestCase() { Authenticators.BIOMETRIC_STRONG } isDeviceCredentialAllowed = allowCredentialFallback - componentNameForConfirmDeviceCredentialActivity = + realCallerForConfirmDeviceCredentialActivity = if (setComponentNameForConfirmDeviceCredentialActivity) componentNameOverriddenForConfirmDeviceCredentialActivity else null diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt index 534f25c33900..6047e7d1bf79 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt @@ -43,7 +43,6 @@ import android.view.MotionEvent import android.view.Surface import androidx.test.filters.SmallTest import com.android.app.activityTaskManager -import com.android.systemui.Flags.FLAG_CONSTRAINT_BP import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.AuthController import com.android.systemui.biometrics.Utils.toBitmap @@ -1385,7 +1384,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } @Test - @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP) + @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) fun descriptionOverriddenByVerticalListContentView() = runGenericTest(description = "test description", contentView = promptContentView) { val contentView by collectLastValue(kosmos.promptViewModel.contentView) @@ -1396,7 +1395,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } @Test - @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP) + @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) fun descriptionOverriddenByContentViewWithMoreOptionsButton() = runGenericTest( description = "test description", @@ -1410,7 +1409,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } @Test - @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP) + @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) fun descriptionWithoutContentView() = runGenericTest(description = "test description") { val contentView by collectLastValue(kosmos.promptViewModel.contentView) @@ -1421,7 +1420,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } @Test - @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP) + @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) fun logo_nullIfPkgNameNotFound() = runGenericTest(packageName = OP_PACKAGE_NAME_CAN_NOT_BE_FOUND) { val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo) @@ -1430,7 +1429,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } @Test - @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP) + @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) fun logo_defaultFromActivityInfo() = runGenericTest(packageName = OP_PACKAGE_NAME_WITH_ACTIVITY_LOGO) { val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo) @@ -1445,7 +1444,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } @Test - @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP) + @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) fun logo_defaultIsNull() = runGenericTest(packageName = OP_PACKAGE_NAME_NO_ICON) { val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo) @@ -1454,7 +1453,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } @Test - @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP) + @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) fun logo_default() = runGenericTest { val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo) assertThat(logoInfo).isNotNull() @@ -1462,7 +1461,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } @Test - @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP) + @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) fun logo_resSetByApp() = runGenericTest(logoRes = logoResFromApp) { val expectedBitmap = context.getDrawable(logoResFromApp).toBitmap() @@ -1472,7 +1471,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } @Test - @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP) + @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) fun logo_bitmapSetByApp() = runGenericTest(logoBitmap = logoBitmapFromApp) { val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo) @@ -1480,7 +1479,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } @Test - @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP) + @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) fun logoDescription_emptyIfPkgNameNotFound() = runGenericTest(packageName = OP_PACKAGE_NAME_CAN_NOT_BE_FOUND) { val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo) @@ -1488,7 +1487,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } @Test - @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP) + @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) fun logoDescription_defaultFromActivityInfo() = runGenericTest(packageName = OP_PACKAGE_NAME_WITH_ACTIVITY_LOGO) { val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo) @@ -1500,7 +1499,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } @Test - @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP) + @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) fun logoDescription_defaultIsEmpty() = runGenericTest(packageName = OP_PACKAGE_NAME_NO_ICON) { val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo) @@ -1508,14 +1507,14 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } @Test - @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP) + @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) fun logoDescription_default() = runGenericTest { val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo) assertThat(logoInfo!!.second).isEqualTo(defaultLogoDescriptionFromAppInfo) } @Test - @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP) + @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) fun logoDescription_setByApp() = runGenericTest(logoDescription = logoDescriptionFromApp) { val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo) @@ -1523,7 +1522,6 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } @Test - @EnableFlags(FLAG_CONSTRAINT_BP) fun position_bottom_rotation0() = runGenericTest { kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_0) val position by collectLastValue(kosmos.promptViewModel.position) @@ -1531,7 +1529,6 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } // TODO(b/335278136): Add test for no sensor landscape @Test - @EnableFlags(FLAG_CONSTRAINT_BP) fun position_bottom_forceLarge() = runGenericTest { kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_270) kosmos.promptViewModel.onSwitchToCredential() @@ -1540,7 +1537,6 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } @Test - @EnableFlags(FLAG_CONSTRAINT_BP) fun position_bottom_largeScreen() = runGenericTest { kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_270) kosmos.displayStateRepository.setIsLargeScreen(true) @@ -1549,7 +1545,6 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } @Test - @EnableFlags(FLAG_CONSTRAINT_BP) fun position_right_rotation90() = runGenericTest { kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_90) val position by collectLastValue(kosmos.promptViewModel.position) @@ -1557,7 +1552,6 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } @Test - @EnableFlags(FLAG_CONSTRAINT_BP) fun position_left_rotation270() = runGenericTest { kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_270) val position by collectLastValue(kosmos.promptViewModel.position) @@ -1565,7 +1559,6 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } @Test - @EnableFlags(FLAG_CONSTRAINT_BP) fun position_top_rotation180() = runGenericTest { kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_180) val position by collectLastValue(kosmos.promptViewModel.position) @@ -1577,7 +1570,6 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } @Test - @EnableFlags(FLAG_CONSTRAINT_BP) fun guideline_bottom() = runGenericTest { kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_0) val guidelineBounds by collectLastValue(kosmos.promptViewModel.guidelineBounds) @@ -1585,7 +1577,6 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } // TODO(b/335278136): Add test for no sensor landscape @Test - @EnableFlags(FLAG_CONSTRAINT_BP) fun guideline_right() = runGenericTest { kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_90) @@ -1602,7 +1593,6 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } @Test - @EnableFlags(FLAG_CONSTRAINT_BP) fun guideline_right_onlyShortTitle() = runGenericTest(subtitle = "") { kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_90) @@ -1617,7 +1607,6 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } @Test - @EnableFlags(FLAG_CONSTRAINT_BP) fun guideline_left() = runGenericTest { kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_270) @@ -1634,7 +1623,6 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } @Test - @EnableFlags(FLAG_CONSTRAINT_BP) fun guideline_left_onlyShortTitle() = runGenericTest(subtitle = "") { kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_270) @@ -1649,7 +1637,6 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } @Test - @EnableFlags(FLAG_CONSTRAINT_BP) fun guideline_top() = runGenericTest { kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_180) val guidelineBounds by collectLastValue(kosmos.promptViewModel.guidelineBounds) diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt index 3b2cf61dde68..0db7b62b8ef1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt @@ -32,7 +32,6 @@ import androidx.test.filters.SmallTest import com.airbnb.lottie.model.KeyPath import com.android.keyguard.keyguardUpdateMonitor import com.android.settingslib.Utils -import com.android.systemui.Flags.FLAG_CONSTRAINT_BP import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider import com.android.systemui.biometrics.data.repository.biometricStatusRepository @@ -199,7 +198,6 @@ class SideFpsOverlayViewModelTest : SysuiTestCase() { @Test fun updatesOverlayViewParams_onDisplayRotationChange_xAlignedSensor() { kosmos.testScope.runTest { - mSetFlagsRule.disableFlags(FLAG_CONSTRAINT_BP) setupTestConfiguration( DeviceConfig.X_ALIGNED, rotation = DisplayRotation.ROTATION_0, @@ -230,11 +228,11 @@ class SideFpsOverlayViewModelTest : SysuiTestCase() { .isEqualTo( displayWidth - sensorLocation.sensorLocationX - sensorLocation.sensorRadius * 2 ) - assertThat(overlayViewParams!!.y).isEqualTo(displayHeight - boundsHeight) + assertThat(overlayViewParams!!.y).isEqualTo(displayHeight) kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_270) assertThat(overlayViewParams).isNotNull() - assertThat(overlayViewParams!!.x).isEqualTo(displayWidth - boundsWidth) + assertThat(overlayViewParams!!.x).isEqualTo(displayWidth) assertThat(overlayViewParams!!.y).isEqualTo(sensorLocation.sensorLocationX) } } @@ -242,7 +240,6 @@ class SideFpsOverlayViewModelTest : SysuiTestCase() { @Test fun updatesOverlayViewParams_onDisplayRotationChange_yAlignedSensor() { kosmos.testScope.runTest { - mSetFlagsRule.disableFlags(FLAG_CONSTRAINT_BP) setupTestConfiguration( DeviceConfig.Y_ALIGNED, rotation = DisplayRotation.ROTATION_0, @@ -256,7 +253,7 @@ class SideFpsOverlayViewModelTest : SysuiTestCase() { runCurrent() assertThat(overlayViewParams).isNotNull() - assertThat(overlayViewParams!!.x).isEqualTo(displayWidth - boundsWidth) + assertThat(overlayViewParams!!.x).isEqualTo(displayWidth) assertThat(overlayViewParams!!.y).isEqualTo(sensorLocation.sensorLocationY) kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_90) @@ -278,7 +275,7 @@ class SideFpsOverlayViewModelTest : SysuiTestCase() { .isEqualTo( displayWidth - sensorLocation.sensorLocationY - sensorLocation.sensorRadius * 2 ) - assertThat(overlayViewParams!!.y).isEqualTo(displayHeight - boundsHeight) + assertThat(overlayViewParams!!.y).isEqualTo(displayHeight) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt index 4d7c49973605..2c53fd67c89d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt @@ -113,7 +113,8 @@ class AudioSharingInteractorTest : SysuiTestCase() { assertThat(actual) .isEqualTo( AudioSharingButtonState.Visible( - R.string.quick_settings_bluetooth_audio_sharing_button_sharing + R.string.quick_settings_bluetooth_audio_sharing_button_sharing, + isActive = true ) ) } @@ -163,7 +164,8 @@ class AudioSharingInteractorTest : SysuiTestCase() { assertThat(actual) .isEqualTo( AudioSharingButtonState.Visible( - R.string.quick_settings_bluetooth_audio_sharing_button + R.string.quick_settings_bluetooth_audio_sharing_button, + isActive = false ) ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt index d01fac36230e..fb0fd23fab24 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt @@ -328,4 +328,70 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() { dialog.dismiss() } } + + @Test + fun testOnAudioSharingButtonUpdated_visibleActive_activateButton() { + testScope.runTest { + val dialog = mBluetoothTileDialogDelegate.createDialog() + dialog.show() + fakeSystemClock.setElapsedRealtime(Long.MAX_VALUE) + mBluetoothTileDialogDelegate.onAudioSharingButtonUpdated( + dialog, + visibility = VISIBLE, + label = null, + isActive = true + ) + + val audioSharingButton = dialog.requireViewById<View>(R.id.audio_sharing_button) + + assertThat(audioSharingButton).isNotNull() + assertThat(audioSharingButton.visibility).isEqualTo(VISIBLE) + assertThat(audioSharingButton.isActivated).isTrue() + dialog.dismiss() + } + } + + @Test + fun testOnAudioSharingButtonUpdated_visibleNotActive_inactivateButton() { + testScope.runTest { + val dialog = mBluetoothTileDialogDelegate.createDialog() + dialog.show() + fakeSystemClock.setElapsedRealtime(Long.MAX_VALUE) + mBluetoothTileDialogDelegate.onAudioSharingButtonUpdated( + dialog, + visibility = VISIBLE, + label = null, + isActive = false + ) + + val audioSharingButton = dialog.requireViewById<View>(R.id.audio_sharing_button) + + assertThat(audioSharingButton).isNotNull() + assertThat(audioSharingButton.visibility).isEqualTo(VISIBLE) + assertThat(audioSharingButton.isActivated).isFalse() + dialog.dismiss() + } + } + + @Test + fun testOnAudioSharingButtonUpdated_gone_inactivateButton() { + testScope.runTest { + val dialog = mBluetoothTileDialogDelegate.createDialog() + dialog.show() + fakeSystemClock.setElapsedRealtime(Long.MAX_VALUE) + mBluetoothTileDialogDelegate.onAudioSharingButtonUpdated( + dialog, + visibility = GONE, + label = null, + isActive = false + ) + + val audioSharingButton = dialog.requireViewById<View>(R.id.audio_sharing_button) + + assertThat(audioSharingButton).isNotNull() + assertThat(audioSharingButton.visibility).isEqualTo(GONE) + assertThat(audioSharingButton.isActivated).isFalse() + dialog.dismiss() + } + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt index 034bab855faf..57b397cfca7e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt @@ -23,11 +23,13 @@ import android.view.WindowManager import android.view.WindowMetrics import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.app.viewcapture.ViewCapture +import com.android.app.viewcapture.ViewCaptureAwareWindowManager import com.android.internal.logging.UiEventLogger -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags +import com.android.systemui.res.R import com.android.systemui.statusbar.commandline.CommandRegistry import com.android.systemui.statusbar.policy.BatteryController import com.android.systemui.statusbar.policy.ConfigurationController @@ -40,12 +42,12 @@ import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers import org.mockito.Mock -import org.mockito.Mockito.`when` import org.mockito.Mockito.any import org.mockito.Mockito.eq import org.mockito.Mockito.never import org.mockito.Mockito.reset import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @SmallTest @@ -60,6 +62,7 @@ class WiredChargingRippleControllerTest : SysuiTestCase() { @Mock private lateinit var windowManager: WindowManager @Mock private lateinit var uiEventLogger: UiEventLogger @Mock private lateinit var windowMetrics: WindowMetrics + @Mock private lateinit var lazyViewCapture: Lazy<ViewCapture> private val systemClock = FakeSystemClock() @Before @@ -68,7 +71,9 @@ class WiredChargingRippleControllerTest : SysuiTestCase() { `when`(featureFlags.isEnabled(Flags.CHARGING_RIPPLE)).thenReturn(true) controller = WiredChargingRippleController( commandRegistry, batteryController, configurationController, - featureFlags, context, windowManager, systemClock, uiEventLogger) + featureFlags, context, windowManager, + ViewCaptureAwareWindowManager(windowManager, + lazyViewCapture, isViewCaptureEnabled = false), systemClock, uiEventLogger) rippleView.setupShader() controller.rippleView = rippleView // Replace the real ripple view with a mock instance controller.registerCallbacks() diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStatePreventingAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStatePreventingAdapterTest.java index 419f7eda9d1f..299c38481b03 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStatePreventingAdapterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStatePreventingAdapterTest.java @@ -29,31 +29,22 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.phone.DozeParameters; -import com.android.systemui.util.concurrency.FakeExecutor; -import com.android.systemui.util.time.FakeSystemClock; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import java.util.concurrent.Executor; - @SmallTest @RunWith(AndroidJUnit4.class) public class DozeScreenStatePreventingAdapterTest extends SysuiTestCase { - private Executor mExecutor; private DozeMachine.Service mInner; private DozeScreenStatePreventingAdapter mWrapper; @Before public void setup() throws Exception { - mExecutor = new FakeExecutor(new FakeSystemClock()); mInner = mock(DozeMachine.Service.class); - mWrapper = new DozeScreenStatePreventingAdapter( - mInner, - mExecutor - ); + mWrapper = new DozeScreenStatePreventingAdapter(mInner); } @Test @@ -98,7 +89,7 @@ public class DozeScreenStatePreventingAdapterTest extends SysuiTestCase { when(params.getDisplayStateSupported()).thenReturn(false); assertEquals(DozeScreenStatePreventingAdapter.class, - DozeScreenStatePreventingAdapter.wrapIfNeeded(mInner, params, mExecutor) + DozeScreenStatePreventingAdapter.wrapIfNeeded(mInner, params) .getClass()); } @@ -107,7 +98,6 @@ public class DozeScreenStatePreventingAdapterTest extends SysuiTestCase { DozeParameters params = mock(DozeParameters.class); when(params.getDisplayStateSupported()).thenReturn(true); - assertSame(mInner, DozeScreenStatePreventingAdapter.wrapIfNeeded(mInner, params, - mExecutor)); + assertSame(mInner, DozeScreenStatePreventingAdapter.wrapIfNeeded(mInner, params)); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapterTest.java index 5a89710f6cf6..0d6a9ceece5c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapterTest.java @@ -29,28 +29,22 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.phone.DozeParameters; -import com.android.systemui.util.concurrency.FakeExecutor; -import com.android.systemui.util.time.FakeSystemClock; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import java.util.concurrent.Executor; - @SmallTest @RunWith(AndroidJUnit4.class) public class DozeSuspendScreenStatePreventingAdapterTest extends SysuiTestCase { - private Executor mExecutor; private DozeMachine.Service mInner; private DozeSuspendScreenStatePreventingAdapter mWrapper; @Before public void setup() throws Exception { - mExecutor = new FakeExecutor(new FakeSystemClock()); mInner = mock(DozeMachine.Service.class); - mWrapper = new DozeSuspendScreenStatePreventingAdapter(mInner, mExecutor); + mWrapper = new DozeSuspendScreenStatePreventingAdapter(mInner); } @Test @@ -101,7 +95,7 @@ public class DozeSuspendScreenStatePreventingAdapterTest extends SysuiTestCase { when(params.getDozeSuspendDisplayStateSupported()).thenReturn(false); assertEquals(DozeSuspendScreenStatePreventingAdapter.class, - DozeSuspendScreenStatePreventingAdapter.wrapIfNeeded(mInner, params, mExecutor) + DozeSuspendScreenStatePreventingAdapter.wrapIfNeeded(mInner, params) .getClass()); } @@ -110,7 +104,6 @@ public class DozeSuspendScreenStatePreventingAdapterTest extends SysuiTestCase { DozeParameters params = mock(DozeParameters.class); when(params.getDozeSuspendDisplayStateSupported()).thenReturn(true); - assertSame(mInner, DozeSuspendScreenStatePreventingAdapter.wrapIfNeeded(mInner, params, - mExecutor)); + assertSame(mInner, DozeSuspendScreenStatePreventingAdapter.wrapIfNeeded(mInner, params)); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java index e3a38a8d6763..37f1a3d73b0c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -443,7 +443,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { mViewMediator.onSystemReady(); TestableLooper.get(this).processAllMessages(); - mViewMediator.setShowingLocked(false); + mViewMediator.setShowingLocked(false, ""); TestableLooper.get(this).processAllMessages(); mViewMediator.onStartedGoingToSleep(OFF_BECAUSE_OF_USER); @@ -463,7 +463,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { mViewMediator.onSystemReady(); TestableLooper.get(this).processAllMessages(); - mViewMediator.setShowingLocked(false); + mViewMediator.setShowingLocked(false, ""); TestableLooper.get(this).processAllMessages(); mViewMediator.onStartedGoingToSleep(OFF_BECAUSE_OF_USER); @@ -570,7 +570,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { // When showing and provisioned mViewMediator.onSystemReady(); when(mUpdateMonitor.isDeviceProvisioned()).thenReturn(true); - mViewMediator.setShowingLocked(true); + mViewMediator.setShowingLocked(true, ""); // and a SIM becomes locked and requires a PIN mViewMediator.mUpdateCallback.onSimStateChanged( @@ -579,7 +579,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { TelephonyManager.SIM_STATE_PIN_REQUIRED); // and the keyguard goes away - mViewMediator.setShowingLocked(false); + mViewMediator.setShowingLocked(false, ""); when(mKeyguardStateController.isShowing()).thenReturn(false); mViewMediator.mUpdateCallback.onKeyguardVisibilityChanged(false); @@ -595,7 +595,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { // When showing and provisioned mViewMediator.onSystemReady(); when(mUpdateMonitor.isDeviceProvisioned()).thenReturn(true); - mViewMediator.setShowingLocked(false); + mViewMediator.setShowingLocked(false, ""); // and a SIM becomes locked and requires a PIN mViewMediator.mUpdateCallback.onSimStateChanged( @@ -604,7 +604,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { TelephonyManager.SIM_STATE_PIN_REQUIRED); // and the keyguard goes away - mViewMediator.setShowingLocked(false); + mViewMediator.setShowingLocked(false, ""); when(mKeyguardStateController.isShowing()).thenReturn(false); mViewMediator.mUpdateCallback.onKeyguardVisibilityChanged(false); @@ -843,7 +843,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { mViewMediator.onSystemReady(); TestableLooper.get(this).processAllMessages(); - mViewMediator.setShowingLocked(false); + mViewMediator.setShowingLocked(false, ""); TestableLooper.get(this).processAllMessages(); mViewMediator.onStartedGoingToSleep(OFF_BECAUSE_OF_USER); @@ -863,7 +863,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { mViewMediator.onSystemReady(); TestableLooper.get(this).processAllMessages(); - mViewMediator.setShowingLocked(false); + mViewMediator.setShowingLocked(false, ""); TestableLooper.get(this).processAllMessages(); mViewMediator.onStartedGoingToSleep(OFF_BECAUSE_OF_USER); @@ -978,7 +978,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { @Test @TestableLooper.RunWithLooper(setAsMainLooper = true) public void testDoKeyguardWhileInteractive_resets() { - mViewMediator.setShowingLocked(true); + mViewMediator.setShowingLocked(true, ""); when(mKeyguardStateController.isShowing()).thenReturn(true); TestableLooper.get(this).processAllMessages(); @@ -992,7 +992,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { @Test @TestableLooper.RunWithLooper(setAsMainLooper = true) public void testDoKeyguardWhileNotInteractive_showsInsteadOfResetting() { - mViewMediator.setShowingLocked(true); + mViewMediator.setShowingLocked(true, ""); when(mKeyguardStateController.isShowing()).thenReturn(true); TestableLooper.get(this).processAllMessages(); @@ -1051,7 +1051,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { mViewMediator.onSystemReady(); processAllMessagesAndBgExecutorMessages(); - mViewMediator.setShowingLocked(true); + mViewMediator.setShowingLocked(true, ""); RemoteAnimationTarget[] apps = new RemoteAnimationTarget[]{ mock(RemoteAnimationTarget.class) @@ -1123,7 +1123,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { @Test @TestableLooper.RunWithLooper(setAsMainLooper = true) public void testNotStartingKeyguardWhenFlagIsDisabled() { - mViewMediator.setShowingLocked(false); + mViewMediator.setShowingLocked(false, ""); when(mKeyguardStateController.isShowing()).thenReturn(false); mViewMediator.onDreamingStarted(); @@ -1133,7 +1133,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { @Test @TestableLooper.RunWithLooper(setAsMainLooper = true) public void testStartingKeyguardWhenFlagIsEnabled() { - mViewMediator.setShowingLocked(true); + mViewMediator.setShowingLocked(true, ""); when(mKeyguardStateController.isShowing()).thenReturn(true); mViewMediator.onDreamingStarted(); @@ -1174,7 +1174,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { TestableLooper.get(this).processAllMessages(); // WHEN keyguard visibility becomes FALSE - mViewMediator.setShowingLocked(false); + mViewMediator.setShowingLocked(false, ""); keyguardUpdateMonitorCallback.onKeyguardVisibilityChanged(false); TestableLooper.get(this).processAllMessages(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt index 2af4d872f6a0..bfe89de6229d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt @@ -17,9 +17,6 @@ package com.android.systemui.keyguard.data.repository import android.animation.ValueAnimator -import android.util.Log -import android.util.Log.TerribleFailure -import android.util.Log.TerribleFailureHandler import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.FlakyTest import androidx.test.filters.SmallTest @@ -53,7 +50,6 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.test.advanceTimeBy import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest -import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -67,23 +63,14 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() { private val testScope = kosmos.testScope private lateinit var underTest: KeyguardTransitionRepository - private lateinit var oldWtfHandler: TerribleFailureHandler - private lateinit var wtfHandler: WtfHandler private lateinit var runner: KeyguardTransitionRunner @Before fun setUp() { underTest = KeyguardTransitionRepositoryImpl(Dispatchers.Main) - wtfHandler = WtfHandler() - oldWtfHandler = Log.setWtfHandler(wtfHandler) runner = KeyguardTransitionRunner(underTest) } - @After - fun tearDown() { - oldWtfHandler?.let { Log.setWtfHandler(it) } - } - @Test fun startTransitionRunsAnimatorToCompletion() = testScope.runTest { @@ -333,15 +320,17 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() { } @Test - fun attemptTomanuallyUpdateTransitionWithInvalidUUIDthrowsException() = + fun attemptTomanuallyUpdateTransitionWithInvalidUUIDEmitsNothing() = testScope.runTest { + val steps by collectValues(underTest.transitions.dropWhile { step -> step.from == OFF }) underTest.updateTransition(UUID.randomUUID(), 0f, TransitionState.RUNNING) - assertThat(wtfHandler.failed).isTrue() + assertThat(steps.size).isEqualTo(0) } @Test - fun attemptToManuallyUpdateTransitionAfterFINISHEDstateThrowsException() = + fun attemptToManuallyUpdateTransitionAfterFINISHEDstateEmitsNothing() = testScope.runTest { + val steps by collectValues(underTest.transitions.dropWhile { step -> step.from == OFF }) val uuid = underTest.startTransition( TransitionInfo( @@ -356,12 +345,19 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() { underTest.updateTransition(it, 1f, TransitionState.FINISHED) underTest.updateTransition(it, 0.5f, TransitionState.RUNNING) } - assertThat(wtfHandler.failed).isTrue() + assertThat(steps.size).isEqualTo(2) + assertThat(steps[0]) + .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED, OWNER_NAME)) + assertThat(steps[1]) + .isEqualTo( + TransitionStep(AOD, LOCKSCREEN, 1f, TransitionState.FINISHED, OWNER_NAME) + ) } @Test - fun attemptToManuallyUpdateTransitionAfterCANCELEDstateThrowsException() = + fun attemptToManuallyUpdateTransitionAfterCANCELEDstateEmitsNothing() = testScope.runTest { + val steps by collectValues(underTest.transitions.dropWhile { step -> step.from == OFF }) val uuid = underTest.startTransition( TransitionInfo( @@ -376,7 +372,13 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() { underTest.updateTransition(it, 0.2f, TransitionState.CANCELED) underTest.updateTransition(it, 0.5f, TransitionState.RUNNING) } - assertThat(wtfHandler.failed).isTrue() + assertThat(steps.size).isEqualTo(2) + assertThat(steps[0]) + .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED, OWNER_NAME)) + assertThat(steps[1]) + .isEqualTo( + TransitionStep(AOD, LOCKSCREEN, 0.2f, TransitionState.CANCELED, OWNER_NAME) + ) } @Test @@ -530,8 +532,6 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() { } assertThat(steps[steps.size - 1]) .isEqualTo(TransitionStep(from, to, lastValue, status, OWNER_NAME)) - - assertThat(wtfHandler.failed).isFalse() } private fun getAnimator(): ValueAnimator { @@ -541,14 +541,6 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() { } } - private class WtfHandler : TerribleFailureHandler { - var failed = false - - override fun onTerribleFailure(tag: String, what: TerribleFailure, system: Boolean) { - failed = true - } - } - companion object { private const val OWNER_NAME = "KeyguardTransitionRunner" } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt index 313292a5fab8..664a0bdedec4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt @@ -19,10 +19,13 @@ package com.android.systemui.keyguard.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.internal.policy.IKeyguardDismissCallback import com.android.systemui.SysuiTestCase +import com.android.systemui.concurrency.fakeExecutor import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository +import com.android.systemui.keyguard.dismissCallbackRegistry import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep @@ -36,6 +39,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith +import org.mockito.Mockito.mock import org.mockito.Mockito.verify @ExperimentalCoroutinesApi @@ -49,20 +53,32 @@ class AlternateBouncerViewModelTest : SysuiTestCase() { private val underTest = kosmos.alternateBouncerViewModel @Test - fun showPrimaryBouncer() = + fun onTapped() = testScope.runTest { - underTest.showPrimaryBouncer() + underTest.onTapped() verify(statusBarKeyguardViewManager).showPrimaryBouncer(any()) } @Test - fun hideAlternateBouncer() = + fun onRemovedFromWindow() = testScope.runTest { - underTest.hideAlternateBouncer() + underTest.onRemovedFromWindow() verify(statusBarKeyguardViewManager).hideAlternateBouncer(any()) } @Test + fun onBackRequested() = + testScope.runTest { + val dismissCallback = mock(IKeyguardDismissCallback::class.java) + kosmos.dismissCallbackRegistry.addCallback(dismissCallback) + + underTest.onBackRequested() + kosmos.fakeExecutor.runAllReady() + verify(statusBarKeyguardViewManager).hideAlternateBouncer(any()) + verify(dismissCallback).onDismissCancelled() + } + + @Test fun transitionToAlternateBouncer_scrimAlphaUpdate() = testScope.runTest { val scrimAlphas by collectValues(underTest.scrimAlpha) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java index d5cd86ec0b76..c8cc6b5fdf93 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java @@ -50,14 +50,18 @@ import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; import com.android.settingslib.media.LocalMediaManager; import com.android.systemui.SysuiTestCase; +import com.android.systemui.SysuiTestCaseExtKt; import com.android.systemui.animation.DialogTransitionAnimator; import com.android.systemui.broadcast.BroadcastSender; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.kosmos.Kosmos; import com.android.systemui.media.nearby.NearbyMediaDevicesManager; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.res.R; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; +import com.android.systemui.volume.panel.domain.interactor.VolumePanelGlobalStateInteractor; +import com.android.systemui.volume.panel.domain.interactor.VolumePanelGlobalStateInteractorKosmosKt; import org.junit.Before; import org.junit.Test; @@ -73,6 +77,8 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase { private static final String TEST_PACKAGE = "test_package"; + private final Kosmos mKosmos = SysuiTestCaseExtKt.testKosmos(this); + // Mock private MediaOutputBaseAdapter mMediaOutputBaseAdapter = mock(MediaOutputBaseAdapter.class); private MediaController mMediaController = mock(MediaController.class); @@ -122,6 +128,9 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase { when(mMediaController.getPackageName()).thenReturn(TEST_PACKAGE); mMediaControllers.add(mMediaController); when(mMediaSessionManager.getActiveSessions(any())).thenReturn(mMediaControllers); + VolumePanelGlobalStateInteractor volumePanelGlobalStateInteractor = + VolumePanelGlobalStateInteractorKosmosKt.getVolumePanelGlobalStateInteractor( + mKosmos); mMediaOutputController = new MediaOutputController( @@ -139,6 +148,7 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase { mPowerExemptionManager, mKeyguardManager, mFlags, + volumePanelGlobalStateInteractor, mUserTracker); // Using a fake package will cause routing operations to fail, so we intercept diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java index 87d224579e95..189a56145d27 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java @@ -51,14 +51,18 @@ import com.android.settingslib.media.BluetoothMediaDevice; import com.android.settingslib.media.LocalMediaManager; import com.android.settingslib.media.MediaDevice; import com.android.systemui.SysuiTestCase; +import com.android.systemui.SysuiTestCaseExtKt; import com.android.systemui.animation.DialogTransitionAnimator; import com.android.systemui.broadcast.BroadcastSender; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.kosmos.Kosmos; import com.android.systemui.media.nearby.NearbyMediaDevicesManager; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.res.R; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; +import com.android.systemui.volume.panel.domain.interactor.VolumePanelGlobalStateInteractor; +import com.android.systemui.volume.panel.domain.interactor.VolumePanelGlobalStateInteractorKosmosKt; import com.google.common.base.Strings; @@ -81,6 +85,8 @@ public class MediaOutputBroadcastDialogTest extends SysuiTestCase { private static final String BROADCAST_CODE_TEST = "112233"; private static final String BROADCAST_CODE_UPDATE_TEST = "11223344"; + private final Kosmos mKosmos = SysuiTestCaseExtKt.testKosmos(this); + // Mock private final MediaSessionManager mMediaSessionManager = mock(MediaSessionManager.class); private final LocalBluetoothManager mLocalBluetoothManager = mock(LocalBluetoothManager.class); @@ -123,6 +129,9 @@ public class MediaOutputBroadcastDialogTest extends SysuiTestCase { when(mLocalBluetoothLeBroadcast.getProgramInfo()).thenReturn(BROADCAST_NAME_TEST); when(mLocalBluetoothLeBroadcast.getBroadcastCode()).thenReturn( BROADCAST_CODE_TEST.getBytes(StandardCharsets.UTF_8)); + VolumePanelGlobalStateInteractor volumePanelGlobalStateInteractor = + VolumePanelGlobalStateInteractorKosmosKt.getVolumePanelGlobalStateInteractor( + mKosmos); mMediaOutputController = new MediaOutputController( @@ -140,6 +149,7 @@ public class MediaOutputBroadcastDialogTest extends SysuiTestCase { mPowerExemptionManager, mKeyguardManager, mFlags, + volumePanelGlobalStateInteractor, mUserTracker); mMediaOutputController.mLocalMediaManager = mLocalMediaManager; mMediaOutputBroadcastDialog = new MediaOutputBroadcastDialog(mContext, false, diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java index 856bc0b78215..714fad9d7478 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java @@ -72,15 +72,19 @@ import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.media.LocalMediaManager; import com.android.settingslib.media.MediaDevice; import com.android.systemui.SysuiTestCase; +import com.android.systemui.SysuiTestCaseExtKt; import com.android.systemui.animation.ActivityTransitionAnimator; import com.android.systemui.animation.DialogTransitionAnimator; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.kosmos.Kosmos; import com.android.systemui.media.nearby.NearbyMediaDevicesManager; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.res.R; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; +import com.android.systemui.volume.panel.domain.interactor.VolumePanelGlobalStateInteractor; +import com.android.systemui.volume.panel.domain.interactor.VolumePanelGlobalStateInteractorKosmosKt; import com.google.common.collect.ImmutableList; @@ -158,11 +162,16 @@ public class MediaOutputControllerTest extends SysuiTestCase { @Mock private UserTracker mUserTracker; + private final Kosmos mKosmos = SysuiTestCaseExtKt.testKosmos(this); + private FeatureFlags mFlags = mock(FeatureFlags.class); private View mDialogLaunchView = mock(View.class); private MediaOutputController.Callback mCallback = mock(MediaOutputController.Callback.class); final Notification mNotification = mock(Notification.class); + private final VolumePanelGlobalStateInteractor mVolumePanelGlobalStateInteractor = + VolumePanelGlobalStateInteractorKosmosKt.getVolumePanelGlobalStateInteractor( + mKosmos); private Context mSpyContext; private String mPackageName = null; @@ -194,6 +203,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn( mCachedBluetoothDeviceManager); + mMediaOutputController = new MediaOutputController( mSpyContext, @@ -210,6 +220,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { mPowerExemptionManager, mKeyguardManager, mFlags, + mVolumePanelGlobalStateInteractor, mUserTracker); mLocalMediaManager = spy(mMediaOutputController.mLocalMediaManager); when(mLocalMediaManager.isPreferenceRouteListingExist()).thenReturn(false); @@ -304,6 +315,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { mPowerExemptionManager, mKeyguardManager, mFlags, + mVolumePanelGlobalStateInteractor, mUserTracker); mMediaOutputController.start(mCb); @@ -346,6 +358,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { mPowerExemptionManager, mKeyguardManager, mFlags, + mVolumePanelGlobalStateInteractor, mUserTracker); mMediaOutputController.start(mCb); @@ -602,6 +615,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { mPowerExemptionManager, mKeyguardManager, mFlags, + mVolumePanelGlobalStateInteractor, mUserTracker); testMediaOutputController.start(mCb); reset(mCb); @@ -636,6 +650,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { mPowerExemptionManager, mKeyguardManager, mFlags, + mVolumePanelGlobalStateInteractor, mUserTracker); testMediaOutputController.start(mCb); reset(mCb); @@ -683,6 +698,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { mPowerExemptionManager, mKeyguardManager, mFlags, + mVolumePanelGlobalStateInteractor, mUserTracker); LocalMediaManager mockLocalMediaManager = mock(LocalMediaManager.class); @@ -710,6 +726,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { mPowerExemptionManager, mKeyguardManager, mFlags, + mVolumePanelGlobalStateInteractor, mUserTracker); LocalMediaManager mockLocalMediaManager = mock(LocalMediaManager.class); @@ -990,6 +1007,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { mPowerExemptionManager, mKeyguardManager, mFlags, + mVolumePanelGlobalStateInteractor, mUserTracker); assertThat(mMediaOutputController.getNotificationIcon()).isNull(); @@ -1193,6 +1211,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { mPowerExemptionManager, mKeyguardManager, mFlags, + mVolumePanelGlobalStateInteractor, mUserTracker); testMediaOutputController.setTemporaryAllowListExceptionIfNeeded(mMediaDevice2); diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java index f20b04ae0e5c..90c2930f8e49 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java @@ -54,14 +54,18 @@ import com.android.settingslib.flags.Flags; import com.android.settingslib.media.LocalMediaManager; import com.android.settingslib.media.MediaDevice; import com.android.systemui.SysuiTestCase; +import com.android.systemui.SysuiTestCaseExtKt; import com.android.systemui.animation.DialogTransitionAnimator; import com.android.systemui.broadcast.BroadcastSender; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.kosmos.Kosmos; import com.android.systemui.media.nearby.NearbyMediaDevicesManager; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.res.R; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; +import com.android.systemui.volume.panel.domain.interactor.VolumePanelGlobalStateInteractor; +import com.android.systemui.volume.panel.domain.interactor.VolumePanelGlobalStateInteractorKosmosKt; import org.junit.After; import org.junit.Before; @@ -84,6 +88,8 @@ public class MediaOutputDialogTest extends SysuiTestCase { @Rule public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + private final Kosmos mKosmos = SysuiTestCaseExtKt.testKosmos(this); + // Mock private final MediaSessionManager mMediaSessionManager = mock(MediaSessionManager.class); private MediaController mMediaController = mock(MediaController.class); @@ -136,6 +142,9 @@ public class MediaOutputDialogTest extends SysuiTestCase { when(mMediaSessionManager.getActiveSessionsForUser(any(), Mockito.eq(userHandle))).thenReturn( mMediaControllers); + VolumePanelGlobalStateInteractor volumePanelGlobalStateInteractor = + VolumePanelGlobalStateInteractorKosmosKt.getVolumePanelGlobalStateInteractor( + mKosmos); mMediaOutputController = new MediaOutputController( @@ -153,6 +162,7 @@ public class MediaOutputDialogTest extends SysuiTestCase { mPowerExemptionManager, mKeyguardManager, mFlags, + volumePanelGlobalStateInteractor, mUserTracker); mMediaOutputController.mLocalMediaManager = mLocalMediaManager; mMediaOutputDialog = makeTestDialog(mMediaOutputController); diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt index ffbf62aad2b3..b3bd7d15cbf3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt @@ -20,8 +20,8 @@ import android.content.Context import android.os.Handler import android.os.PowerManager import android.view.ViewGroup -import android.view.WindowManager import android.view.accessibility.AccessibilityManager +import com.android.app.viewcapture.ViewCaptureAwareWindowManager import com.android.systemui.dump.DumpManager import com.android.systemui.media.taptotransfer.MediaTttFlags import com.android.systemui.statusbar.CommandQueue @@ -36,7 +36,7 @@ class FakeMediaTttChipControllerReceiver( commandQueue: CommandQueue, context: Context, logger: MediaTttReceiverLogger, - windowManager: WindowManager, + viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager, mainExecutor: DelayableExecutor, accessibilityManager: AccessibilityManager, configurationController: ConfigurationController, @@ -55,7 +55,7 @@ class FakeMediaTttChipControllerReceiver( commandQueue, context, logger, - windowManager, + viewCaptureAwareWindowManager, mainExecutor, accessibilityManager, configurationController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt index f1d833f7fa15..9afa5ad1dd43 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt @@ -31,12 +31,14 @@ import android.view.accessibility.AccessibilityManager import android.widget.ImageView import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.app.viewcapture.ViewCapture +import com.android.app.viewcapture.ViewCaptureAwareWindowManager import com.android.internal.logging.InstanceId import com.android.internal.logging.testing.UiEventLoggerFake -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager import com.android.systemui.media.taptotransfer.MediaTttFlags +import com.android.systemui.res.R import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.temporarydisplay.TemporaryViewUiEventLogger @@ -88,6 +90,9 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { private lateinit var commandQueue: CommandQueue @Mock private lateinit var rippleController: MediaTttReceiverRippleController + @Mock + private lateinit var lazyViewCapture: Lazy<ViewCapture> + private lateinit var viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager private lateinit var commandQueueCallback: CommandQueue.Callbacks private lateinit var fakeAppIconDrawable: Drawable private lateinit var uiEventLoggerFake: UiEventLoggerFake @@ -122,11 +127,13 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { fakeWakeLockBuilder = WakeLockFake.Builder(context) fakeWakeLockBuilder.setWakeLock(fakeWakeLock) + viewCaptureAwareWindowManager = ViewCaptureAwareWindowManager(windowManager, + lazyViewCapture, isViewCaptureEnabled = false) controllerReceiver = FakeMediaTttChipControllerReceiver( commandQueue, context, logger, - windowManager, + viewCaptureAwareWindowManager, fakeExecutor, accessibilityManager, configurationController, @@ -157,7 +164,7 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { commandQueue, context, logger, - windowManager, + viewCaptureAwareWindowManager, FakeExecutor(FakeSystemClock()), accessibilityManager, configurationController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt index 111b8d4b1b30..b4cad6bb057b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt @@ -33,6 +33,8 @@ import android.widget.ImageView import android.widget.TextView import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.app.viewcapture.ViewCapture +import com.android.app.viewcapture.ViewCaptureAwareWindowManager import com.android.internal.logging.testing.UiEventLoggerFake import com.android.internal.statusbar.IUndoMediaTransferCallback import com.android.systemui.SysuiTestCase @@ -100,6 +102,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { @Mock private lateinit var windowManager: WindowManager @Mock private lateinit var vibratorHelper: VibratorHelper @Mock private lateinit var swipeHandler: SwipeChipbarAwayGestureHandler + @Mock private lateinit var lazyViewCapture: Lazy<ViewCapture> private lateinit var fakeWakeLockBuilder: WakeLockFake.Builder private lateinit var fakeWakeLock: WakeLockFake private lateinit var chipbarCoordinator: ChipbarCoordinator @@ -145,7 +148,8 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { ChipbarCoordinator( context, chipbarLogger, - windowManager, + ViewCaptureAwareWindowManager(windowManager, lazyViewCapture, + isViewCaptureEnabled = false), fakeExecutor, accessibilityManager, configurationController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt index b337ccfda772..8fbd3c8b7ebf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt @@ -24,7 +24,6 @@ import android.view.View import android.view.ViewGroup import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.systemui.Flags.FLAG_PSS_APP_SELECTOR_ABRUPT_EXIT_FIX import com.android.systemui.Flags.FLAG_PSS_APP_SELECTOR_RECENTS_SPLIT_SCREEN import com.android.systemui.SysuiTestCase import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorResultHandler @@ -141,27 +140,13 @@ class MediaProjectionRecentsViewControllerTest : SysuiTestCase() { } @Test - fun onRecentAppClicked_fullScreenTaskInForeground_flagOff_usesScaleUpAnimation() { - mSetFlagsRule.disableFlags(FLAG_PSS_APP_SELECTOR_ABRUPT_EXIT_FIX) - - controller.onRecentAppClicked(fullScreenTask, taskView) - - assertThat(getStartedTaskActivityOptions(fullScreenTask.taskId).animationType) - .isEqualTo(ActivityOptions.ANIM_SCALE_UP) - } - - @Test - fun onRecentAppClicked_fullScreenTaskInForeground_flagOn_usesDefaultAnimation() { - mSetFlagsRule.enableFlags(FLAG_PSS_APP_SELECTOR_ABRUPT_EXIT_FIX) + fun onRecentAppClicked_fullScreenTaskInForeground_usesDefaultAnimation() { assertForegroundTaskUsesDefaultCloseAnimation(fullScreenTask) } @Test fun onRecentAppClicked_splitScreenTaskInForeground_flagOn_usesDefaultAnimation() { - mSetFlagsRule.enableFlags( - FLAG_PSS_APP_SELECTOR_ABRUPT_EXIT_FIX, - FLAG_PSS_APP_SELECTOR_RECENTS_SPLIT_SCREEN - ) + mSetFlagsRule.enableFlags(FLAG_PSS_APP_SELECTOR_RECENTS_SPLIT_SCREEN) assertForegroundTaskUsesDefaultCloseAnimation(splitScreenTask) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java index a52ab0c690a4..04d140c458e8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java @@ -78,6 +78,7 @@ import android.view.inputmethod.InputMethodManager; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; +import com.android.app.viewcapture.ViewCaptureAwareWindowManager; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEventLogger; import com.android.systemui.SysuiTestCase; @@ -214,6 +215,8 @@ public class NavigationBarTest extends SysuiTestCase { @Mock private WindowManager mWindowManager; @Mock + private ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager; + @Mock private TelecomManager mTelecomManager; @Mock private InputMethodManager mInputMethodManager; @@ -619,6 +622,7 @@ public class NavigationBarTest extends SysuiTestCase { null, context, mWindowManager, + mViewCaptureAwareWindowManager, () -> mAssistManager, mock(AccessibilityManager.class), deviceProvisionedController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt index 11b0bdf3effd..7dae5ccd05c4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt @@ -21,13 +21,13 @@ import android.os.UserHandle import android.testing.TestableLooper import android.view.View import android.widget.Spinner +import android.widget.TextView import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.Dependency import com.android.systemui.SysuiTestCase import com.android.systemui.animation.DialogTransitionAnimator import com.android.systemui.broadcast.BroadcastDispatcher -import com.android.systemui.flags.FeatureFlags import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorActivity import com.android.systemui.mediaprojection.permission.ENTIRE_SCREEN @@ -60,7 +60,6 @@ class ScreenRecordPermissionDialogDelegateTest : SysuiTestCase() { @Mock private lateinit var starter: ActivityStarter @Mock private lateinit var controller: RecordingController @Mock private lateinit var userContextProvider: UserContextProvider - @Mock private lateinit var flags: FeatureFlags @Mock private lateinit var onStartRecordingClicked: Runnable @Mock private lateinit var mediaProjectionMetricsLogger: MediaProjectionMetricsLogger @@ -128,6 +127,32 @@ class ScreenRecordPermissionDialogDelegateTest : SysuiTestCase() { } @Test + fun startButtonText_entireScreenSelected() { + showDialog() + + onSpinnerItemSelected(ENTIRE_SCREEN) + + assertThat(getStartButton().text) + .isEqualTo( + context.getString(R.string.screenrecord_permission_dialog_continue_entire_screen) + ) + } + + @Test + fun startButtonText_singleAppSelected() { + showDialog() + + onSpinnerItemSelected(SINGLE_APP) + + assertThat(getStartButton().text) + .isEqualTo( + context.getString( + R.string.media_projection_entry_generic_permission_dialog_continue_single_app + ) + ) + } + + @Test fun startClicked_singleAppSelected_passesHostUidToAppSelector() { showDialog() onSpinnerItemSelected(SINGLE_APP) @@ -152,7 +177,8 @@ class ScreenRecordPermissionDialogDelegateTest : SysuiTestCase() { showDialog() val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options) - val singleApp = context.getString(R.string.screen_share_permission_dialog_option_single_app) + val singleApp = + context.getString(R.string.screenrecord_permission_dialog_option_text_single_app) assertEquals(spinner.adapter.getItem(0), singleApp) } @@ -208,8 +234,10 @@ class ScreenRecordPermissionDialogDelegateTest : SysuiTestCase() { dialog.requireViewById<View>(android.R.id.button2).performClick() } + private fun getStartButton() = dialog.requireViewById<TextView>(android.R.id.button1) + private fun clickOnStart() { - dialog.requireViewById<View>(android.R.id.button1).performClick() + getStartButton().performClick() } private fun onSpinnerItemSelected(position: Int) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt index 5de31d887878..cb5c7399b769 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt @@ -34,6 +34,7 @@ import androidx.test.filters.SmallTest import com.android.compose.animation.scene.SceneKey import com.android.systemui.Flags import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_BACK_GESTURE +import com.android.systemui.Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX import com.android.systemui.SysuiTestCase import com.android.systemui.ambient.touch.TouchHandler import com.android.systemui.ambient.touch.TouchMonitor @@ -57,9 +58,11 @@ import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope +import com.android.systemui.media.controls.controller.keyguardMediaController import com.android.systemui.res.R import com.android.systemui.scene.shared.model.sceneDataSourceDelegator import com.android.systemui.shade.domain.interactor.shadeInteractor +import com.android.systemui.statusbar.lockscreen.lockscreenSmartspaceController import com.android.systemui.statusbar.notification.stack.notificationStackScrollLayoutController import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat @@ -134,7 +137,9 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { ambientTouchComponentFactory, communalContent, kosmos.sceneDataSourceDelegator, - kosmos.notificationStackScrollLayoutController + kosmos.notificationStackScrollLayoutController, + kosmos.keyguardMediaController, + kosmos.lockscreenSmartspaceController ) } testableLooper = TestableLooper.get(this) @@ -178,7 +183,9 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { ambientTouchComponentFactory, communalContent, kosmos.sceneDataSourceDelegator, - kosmos.notificationStackScrollLayoutController + kosmos.notificationStackScrollLayoutController, + kosmos.keyguardMediaController, + kosmos.lockscreenSmartspaceController ) // First call succeeds. @@ -205,6 +212,8 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { communalContent, kosmos.sceneDataSourceDelegator, kosmos.notificationStackScrollLayoutController, + kosmos.keyguardMediaController, + kosmos.lockscreenSmartspaceController ) assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.INITIALIZED) @@ -226,6 +235,8 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { communalContent, kosmos.sceneDataSourceDelegator, kosmos.notificationStackScrollLayoutController, + kosmos.keyguardMediaController, + kosmos.lockscreenSmartspaceController ) // Only initView without attaching a view as we don't want the flows to start collecting @@ -425,7 +436,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { } @Test - @DisableFlags(FLAG_GLANCEABLE_HUB_BACK_GESTURE) + @DisableFlags(FLAG_GLANCEABLE_HUB_BACK_GESTURE, FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX) fun gestureExclusionZone_setAfterInit() = with(kosmos) { testScope.runTest { @@ -452,6 +463,27 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { @Test @DisableFlags(FLAG_GLANCEABLE_HUB_BACK_GESTURE) + @EnableFlags(FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX) + fun gestureExclusionZone_setAfterInit_fullSwipe() = + with(kosmos) { + testScope.runTest { + whenever(containerView.layoutDirection).thenReturn(View.LAYOUT_DIRECTION_LTR) + goToScene(CommunalScenes.Communal) + + assertThat(containerView.systemGestureExclusionRects) + .containsExactly( + Rect( + /* left= */ 0, + /* top= */ 0, + /* right= */ 0, + /* bottom= */ CONTAINER_HEIGHT + ) + ) + } + } + + @Test + @DisableFlags(FLAG_GLANCEABLE_HUB_BACK_GESTURE, FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX) fun gestureExclusionZone_setAfterInit_rtl() = with(kosmos) { testScope.runTest { @@ -476,8 +508,29 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { } } + @DisableFlags(FLAG_GLANCEABLE_HUB_BACK_GESTURE) + @EnableFlags(FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX) + fun gestureExclusionZone_setAfterInit_rtl_fullSwipe() = + with(kosmos) { + testScope.runTest { + whenever(containerView.layoutDirection).thenReturn(View.LAYOUT_DIRECTION_RTL) + goToScene(CommunalScenes.Communal) + + assertThat(containerView.systemGestureExclusionRects) + .containsExactly( + Rect( + /* left= */ 0, + /* top= */ 0, + /* right= */ CONTAINER_WIDTH, + /* bottom= */ CONTAINER_HEIGHT + ) + ) + } + } + @Test @EnableFlags(FLAG_GLANCEABLE_HUB_BACK_GESTURE) + @DisableFlags(FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX) fun gestureExclusionZone_setAfterInit_backGestureEnabled() = with(kosmos) { testScope.runTest { @@ -503,7 +556,28 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { } @Test + @EnableFlags(FLAG_GLANCEABLE_HUB_BACK_GESTURE, FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX) + fun gestureExclusionZone_setAfterInit_backGestureEnabled_fullSwipe() = + with(kosmos) { + testScope.runTest { + whenever(containerView.layoutDirection).thenReturn(View.LAYOUT_DIRECTION_LTR) + goToScene(CommunalScenes.Communal) + + assertThat(containerView.systemGestureExclusionRects) + .containsExactly( + Rect( + /* left= */ 0, + /* top= */ 0, + /* right= */ FAKE_INSETS.right, + /* bottom= */ CONTAINER_HEIGHT + ) + ) + } + } + + @Test @EnableFlags(FLAG_GLANCEABLE_HUB_BACK_GESTURE) + @DisableFlags(FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX) fun gestureExclusionZone_setAfterInit_backGestureEnabled_rtl() = with(kosmos) { testScope.runTest { @@ -529,6 +603,28 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { } @Test + @EnableFlags(FLAG_GLANCEABLE_HUB_BACK_GESTURE, FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX) + fun gestureExclusionZone_setAfterInit_backGestureEnabled_rtl_fullSwipe() = + with(kosmos) { + testScope.runTest { + whenever(containerView.layoutDirection).thenReturn(View.LAYOUT_DIRECTION_RTL) + goToScene(CommunalScenes.Communal) + + assertThat(containerView.systemGestureExclusionRects) + .containsExactly( + Rect( + Rect( + /* left= */ FAKE_INSETS.left, + /* top= */ 0, + /* right= */ CONTAINER_WIDTH, + /* bottom= */ CONTAINER_HEIGHT + ) + ) + ) + } + } + + @Test fun gestureExclusionZone_unsetWhenShadeOpen() = with(kosmos) { testScope.runTest { @@ -599,6 +695,30 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { } @Test + fun fullScreenSwipeGesture_doNotProcessTouchesInUmo() = + with(kosmos) { + testScope.runTest { + // Communal is closed. + goToScene(CommunalScenes.Blank) + whenever(keyguardMediaController.isWithinMediaViewBounds(any(), any())) + .thenReturn(true) + assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse() + } + } + + @Test + fun fullScreenSwipeGesture_doNotProcessTouchesInSmartspace() = + with(kosmos) { + testScope.runTest { + // Communal is closed. + goToScene(CommunalScenes.Blank) + whenever(lockscreenSmartspaceController.isWithinSmartspaceBounds(any(), any())) + .thenReturn(true) + assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse() + } + } + + @Test fun onTouchEvent_hubOpen_touchesDispatched() = with(kosmos) { testScope.runTest { diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java index e6afc1fbb15d..505f7997ef1c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java @@ -267,6 +267,7 @@ public class QuickSettingsControllerImplBaseTest extends SysuiTestCase { when(mPanelView.getParent()).thenReturn(mPanelViewParent); when(mQs.getHeader()).thenReturn(mQsHeader); + when(mQSFragment.getHeader()).thenReturn(mQsHeader); doAnswer(invocation -> { mLockscreenShadeTransitionCallback = invocation.getArgument(0); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java index e7db4690c2c3..2e9d6e85d0aa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java @@ -24,34 +24,41 @@ import static android.view.MotionEvent.ACTION_UP; import static android.view.MotionEvent.BUTTON_SECONDARY; import static android.view.MotionEvent.BUTTON_STYLUS_PRIMARY; -import static com.android.systemui.Flags.FLAG_QS_UI_REFACTOR; import static com.android.systemui.Flags.FLAG_QS_UI_REFACTOR_COMPOSE_FRAGMENT; import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import static com.android.systemui.statusbar.StatusBarState.SHADE; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.graphics.Rect; +import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.FlagsParameterization; import android.testing.TestableLooper; import android.view.MotionEvent; +import android.view.ViewGroup; import androidx.test.filters.SmallTest; import com.android.systemui.plugins.qs.QS; +import com.android.systemui.qs.flags.QSComposeFragment; import com.android.systemui.res.R; import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.InOrder; import java.util.List; @@ -65,7 +72,7 @@ public class QuickSettingsControllerImplTest extends QuickSettingsControllerImpl @Parameters(name = "{0}") public static List<FlagsParameterization> getParams() { - return progressionOf(FLAG_QS_UI_REFACTOR, FLAG_QS_UI_REFACTOR_COMPOSE_FRAGMENT); + return progressionOf(FLAG_QS_UI_REFACTOR_COMPOSE_FRAGMENT); } public QuickSettingsControllerImplTest(FlagsParameterization flags) { @@ -244,6 +251,61 @@ public class QuickSettingsControllerImplTest extends QuickSettingsControllerImpl } @Test + @DisableFlags(QSComposeFragment.FLAG_NAME) + public void onQsFragmentAttached_qsComposeFragmentDisabled_setHeaderInNSSL() { + mFragmentListener.onFragmentViewCreated(QS.TAG, mQSFragment); + + verify(mNotificationStackScrollLayoutController) + .setQsHeader((ViewGroup) mQSFragment.getHeader()); + verify(mNotificationStackScrollLayoutController, never()).setQsHeaderBoundsProvider(any()); + } + + @Test + @EnableFlags(QSComposeFragment.FLAG_NAME) + public void onQsFragmentAttached_qsComposeFragmentEnabled_setQsHeaderBoundsProviderInNSSL() { + mFragmentListener.onFragmentViewCreated(QS.TAG, mQSFragment); + + verify(mNotificationStackScrollLayoutController, never()) + .setQsHeader((ViewGroup) mQSFragment.getHeader()); + ArgumentCaptor<QSHeaderBoundsProvider> argumentCaptor = + ArgumentCaptor.forClass(QSHeaderBoundsProvider.class); + + verify(mNotificationStackScrollLayoutController) + .setQsHeaderBoundsProvider(argumentCaptor.capture()); + + argumentCaptor.getValue().getLeftProvider().invoke(); + argumentCaptor.getValue().getHeightProvider().invoke(); + argumentCaptor.getValue().getBoundsOnScreenProvider().invoke(new Rect()); + InOrder inOrderVerifier = inOrder(mQSFragment); + + inOrderVerifier.verify(mQSFragment).getHeaderLeft(); + inOrderVerifier.verify(mQSFragment).getHeaderHeight(); + inOrderVerifier.verify(mQSFragment).getHeaderBoundsOnScreen(new Rect()); + } + + @Test + @DisableFlags(QSComposeFragment.FLAG_NAME) + public void onQSFragmentDetached_qsComposeFragmentFlagDisabled_setViewToNullInNSSL() { + mFragmentListener.onFragmentViewCreated(QS.TAG, mQSFragment); + + mFragmentListener.onFragmentViewDestroyed(QS.TAG, mQSFragment); + + verify(mNotificationStackScrollLayoutController).setQsHeader(null); + verify(mNotificationStackScrollLayoutController, never()).setQsHeaderBoundsProvider(null); + } + + @Test + @EnableFlags(QSComposeFragment.FLAG_NAME) + public void onQSFragmentDetached_qsComposeFragmentFlagEnabled_setBoundsProviderToNullInNSSL() { + mFragmentListener.onFragmentViewCreated(QS.TAG, mQSFragment); + + mFragmentListener.onFragmentViewDestroyed(QS.TAG, mQSFragment); + + verify(mNotificationStackScrollLayoutController, never()).setQsHeader(null); + verify(mNotificationStackScrollLayoutController).setQsHeaderBoundsProvider(null); + } + + @Test public void onQsFragmentAttached_notFullWidth_setsFullWidthFalseOnQS() { setIsFullWidth(false); mFragmentListener.onFragmentViewCreated(QS.TAG, mQSFragment); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt index 150f53d4ad25..022825a48de3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt @@ -22,8 +22,6 @@ import android.graphics.drawable.Drawable import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.flags.FakeFeatureFlags -import com.android.systemui.flags.Flags.TRANSIT_CLOCK import com.android.systemui.plugins.clocks.ClockController import com.android.systemui.plugins.clocks.ClockId import com.android.systemui.plugins.clocks.ClockMessageBuffers @@ -76,7 +74,6 @@ class ClockRegistryTest : SysuiTestCase() { private lateinit var pluginListener: PluginListener<ClockProviderPlugin> private lateinit var registry: ClockRegistry private lateinit var pickerConfig: ClockPickerConfig - private val featureFlags = FakeFeatureFlags() companion object { private fun failFactory(clockId: ClockId): ClockController { @@ -532,44 +529,4 @@ class ClockRegistryTest : SysuiTestCase() { val actual = ClockSettings.serialize(ClockSettings("ID", null)) assertEquals(expected, actual) } - - @Test - fun testTransitClockEnabled_hasTransitClock() { - testTransitClockFlag(true) - } - - @Test - fun testTransitClockDisabled_noTransitClock() { - testTransitClockFlag(false) - } - - private fun testTransitClockFlag(flag: Boolean) { - featureFlags.set(TRANSIT_CLOCK, flag) - registry.isTransitClockEnabled = featureFlags.isEnabled(TRANSIT_CLOCK) - val plugin = FakeClockPlugin() - .addClock("clock_1") - .addClock("DIGITAL_CLOCK_METRO") - val lifecycle = FakeLifecycle("metro", plugin) - pluginListener.onPluginLoaded(plugin, mockContext, lifecycle) - - val list = registry.getClocks() - if (flag) { - assertEquals( - setOf( - ClockMetadata(DEFAULT_CLOCK_ID), - ClockMetadata("clock_1"), - ClockMetadata("DIGITAL_CLOCK_METRO") - ), - list.toSet() - ) - } else { - assertEquals( - setOf( - ClockMetadata(DEFAULT_CLOCK_ID), - ClockMetadata("clock_1") - ), - list.toSet() - ) - } - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java index 6916bbde0153..d10ea1f02367 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java @@ -15,7 +15,9 @@ package com.android.systemui.statusbar; import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE; +import static android.inputmethodservice.InputMethodService.BACK_DISPOSITION_ADJUST_NOTHING; import static android.inputmethodservice.InputMethodService.BACK_DISPOSITION_DEFAULT; +import static android.inputmethodservice.InputMethodService.IME_ACTIVE; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowInsetsController.BEHAVIOR_DEFAULT; @@ -194,9 +196,11 @@ public class CommandQueueTest extends SysuiTestCase { @Test public void testShowImeButton() { - mCommandQueue.setImeWindowStatus(DEFAULT_DISPLAY, 1, 2, true); + mCommandQueue.setImeWindowStatus(DEFAULT_DISPLAY, IME_ACTIVE, + BACK_DISPOSITION_ADJUST_NOTHING, true); waitForIdleSync(); - verify(mCallbacks).setImeWindowStatus(eq(DEFAULT_DISPLAY), eq(1), eq(2), eq(true)); + verify(mCallbacks).setImeWindowStatus(eq(DEFAULT_DISPLAY), eq(IME_ACTIVE), + eq(BACK_DISPOSITION_ADJUST_NOTHING), eq(true)); } @Test @@ -204,11 +208,13 @@ public class CommandQueueTest extends SysuiTestCase { // First show in default display to update the "last updated ime display" testShowImeButton(); - mCommandQueue.setImeWindowStatus(SECONDARY_DISPLAY, 1, 2, true); + mCommandQueue.setImeWindowStatus(SECONDARY_DISPLAY, IME_ACTIVE, + BACK_DISPOSITION_ADJUST_NOTHING, true); waitForIdleSync(); - verify(mCallbacks).setImeWindowStatus(eq(DEFAULT_DISPLAY), eq(0), + verify(mCallbacks).setImeWindowStatus(eq(DEFAULT_DISPLAY), eq(0) /* vis */, eq(BACK_DISPOSITION_DEFAULT), eq(false)); - verify(mCallbacks).setImeWindowStatus(eq(SECONDARY_DISPLAY), eq(1), eq(2), eq(true)); + verify(mCallbacks).setImeWindowStatus(eq(SECONDARY_DISPLAY), eq(IME_ACTIVE), + eq(BACK_DISPOSITION_ADJUST_NOTHING), eq(true)); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt index 25314f379e7c..5b4578153233 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt @@ -20,6 +20,8 @@ import android.app.StatusBarManager.WINDOW_STATE_HIDDEN import android.app.StatusBarManager.WINDOW_STATE_HIDING import android.app.StatusBarManager.WINDOW_STATE_SHOWING import android.app.StatusBarManager.WINDOW_STATUS_BAR +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags import android.view.InputDevice import android.view.LayoutInflater import android.view.MotionEvent @@ -104,7 +106,7 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { val parent = FrameLayout(mContext) // add parent to keep layout params view = LayoutInflater.from(mContext).inflate(R.layout.status_bar, parent, false) - as PhoneStatusBarView + as PhoneStatusBarView controller = createAndInitController(view) } } @@ -112,8 +114,8 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { @Test fun onViewAttachedAndDrawn_startListeningConfigurationControllerCallback() { val view = createViewMock() - val argumentCaptor = ArgumentCaptor.forClass( - ConfigurationController.ConfigurationListener::class.java) + val argumentCaptor = + ArgumentCaptor.forClass(ConfigurationController.ConfigurationListener::class.java) InstrumentationRegistry.getInstrumentation().runOnMainSync { controller = createAndInitController(view) } @@ -159,7 +161,7 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { fun handleTouchEventFromStatusBar_panelsNotEnabled_returnsFalseAndNoViewEvent() { `when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(false) val returnVal = - view.onTouchEvent(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)) + view.onTouchEvent(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 2f, 0)) assertThat(returnVal).isFalse() verify(shadeViewController, never()).handleExternalTouch(any()) } @@ -169,7 +171,7 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { `when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(true) `when`(shadeViewController.isViewEnabled).thenReturn(false) val returnVal = - view.onTouchEvent(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)) + view.onTouchEvent(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 2f, 0)) assertThat(returnVal).isTrue() verify(shadeViewController, never()).handleExternalTouch(any()) } @@ -178,7 +180,7 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { fun handleTouchEventFromStatusBar_viewNotEnabledButIsMoveEvent_viewReceivesEvent() { `when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(true) `when`(shadeViewController.isViewEnabled).thenReturn(false) - val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0) + val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 2f, 0) view.onTouchEvent(event) @@ -208,6 +210,50 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_STATUS_BAR_SWIPE_OVER_CHIP) + fun handleInterceptTouchEventFromStatusBar_shadeReturnsFalse_flagOff_viewReturnsFalse() { + `when`(shadeViewController.handleExternalInterceptTouch(any())).thenReturn(false) + val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 2f, 0) + + val returnVal = view.onInterceptTouchEvent(event) + + assertThat(returnVal).isFalse() + } + + @Test + @EnableFlags(com.android.systemui.Flags.FLAG_STATUS_BAR_SWIPE_OVER_CHIP) + fun handleInterceptTouchEventFromStatusBar_shadeReturnsFalse_flagOn_viewReturnsFalse() { + `when`(shadeViewController.handleExternalInterceptTouch(any())).thenReturn(false) + val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 2f, 0) + + val returnVal = view.onInterceptTouchEvent(event) + + assertThat(returnVal).isFalse() + } + + @Test + @DisableFlags(com.android.systemui.Flags.FLAG_STATUS_BAR_SWIPE_OVER_CHIP) + fun handleInterceptTouchEventFromStatusBar_shadeReturnsTrue_flagOff_viewReturnsFalse() { + `when`(shadeViewController.handleExternalInterceptTouch(any())).thenReturn(true) + val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 2f, 0) + + val returnVal = view.onInterceptTouchEvent(event) + + assertThat(returnVal).isFalse() + } + + @Test + @EnableFlags(com.android.systemui.Flags.FLAG_STATUS_BAR_SWIPE_OVER_CHIP) + fun handleInterceptTouchEventFromStatusBar_shadeReturnsTrue_flagOn_viewReturnsTrue() { + `when`(shadeViewController.handleExternalInterceptTouch(any())).thenReturn(true) + val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 2f, 0) + + val returnVal = view.onInterceptTouchEvent(event) + + assertThat(returnVal).isTrue() + } + + @Test fun onTouch_windowHidden_centralSurfacesNotNotified() { val callback = getCommandQueueCallback() callback.setWindowState(DISPLAY_ID, WINDOW_STATUS_BAR, WINDOW_STATE_HIDDEN) @@ -244,9 +290,7 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { controller = createAndInitController(view) } val statusContainer = view.requireViewById<View>(R.id.system_icons) - statusContainer.dispatchTouchEvent( - getActionUpEventFromSource(InputDevice.SOURCE_MOUSE) - ) + statusContainer.dispatchTouchEvent(getActionUpEventFromSource(InputDevice.SOURCE_MOUSE)) verify(shadeControllerImpl).animateExpandShade() } @@ -257,9 +301,10 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { controller = createAndInitController(view) } val statusContainer = view.requireViewById<View>(R.id.system_icons) - val handled = statusContainer.dispatchTouchEvent( - getActionUpEventFromSource(InputDevice.SOURCE_TOUCHSCREEN) - ) + val handled = + statusContainer.dispatchTouchEvent( + getActionUpEventFromSource(InputDevice.SOURCE_TOUCHSCREEN) + ) assertThat(handled).isFalse() } @@ -295,21 +340,21 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { private fun createAndInitController(view: PhoneStatusBarView): PhoneStatusBarViewController { return PhoneStatusBarViewController.Factory( - Optional.of(sysuiUnfoldComponent), - Optional.of(progressProvider), - featureFlags, - userChipViewModel, - centralSurfacesImpl, - statusBarWindowStateController, - shadeControllerImpl, - shadeViewController, - panelExpansionInteractor, - windowRootView, - shadeLogger, - viewUtil, - configurationController, - mStatusOverlayHoverListenerFactory - ) + Optional.of(sysuiUnfoldComponent), + Optional.of(progressProvider), + featureFlags, + userChipViewModel, + centralSurfacesImpl, + statusBarWindowStateController, + shadeControllerImpl, + shadeViewController, + panelExpansionInteractor, + windowRootView, + shadeLogger, + viewUtil, + configurationController, + mStatusOverlayHoverListenerFactory + ) .create(view) .also { it.init() } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt index eae4f23c59d6..abc50bc09e55 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt @@ -19,6 +19,8 @@ package com.android.systemui.statusbar.phone import android.content.res.Configuration import android.graphics.Insets import android.graphics.Rect +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags import android.testing.TestableLooper.RunWithLooper import android.view.DisplayCutout import android.view.DisplayShape @@ -30,6 +32,7 @@ import android.view.View import android.view.WindowInsets import android.widget.FrameLayout import androidx.test.filters.SmallTest +import com.android.systemui.Flags.FLAG_STATUS_BAR_SWIPE_OVER_CHIP import com.android.systemui.Gefingerpoken import com.android.systemui.SysuiTestCase import com.android.systemui.plugins.DarkIconDispatcher @@ -82,7 +85,8 @@ class PhoneStatusBarViewTest : SysuiTestCase() { } @Test - fun onInterceptTouchEvent_listenerNotified() { + @DisableFlags(FLAG_STATUS_BAR_SWIPE_OVER_CHIP) + fun onInterceptTouchEvent_flagOff_listenerNotified() { val handler = TestTouchEventHandler() view.setTouchEventHandler(handler) @@ -93,6 +97,66 @@ class PhoneStatusBarViewTest : SysuiTestCase() { } @Test + @EnableFlags(FLAG_STATUS_BAR_SWIPE_OVER_CHIP) + fun onInterceptTouchEvent_flagOn_listenerNotified() { + val handler = TestTouchEventHandler() + view.setTouchEventHandler(handler) + + val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) + view.onInterceptTouchEvent(event) + + assertThat(handler.lastInterceptEvent).isEqualTo(event) + } + + @Test + @DisableFlags(FLAG_STATUS_BAR_SWIPE_OVER_CHIP) + fun onInterceptTouchEvent_listenerReturnsFalse_flagOff_viewReturnsFalse() { + val handler = TestTouchEventHandler() + view.setTouchEventHandler(handler) + val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) + + handler.handleTouchReturnValue = false + + assertThat(view.onInterceptTouchEvent(event)).isFalse() + } + + @Test + @EnableFlags(FLAG_STATUS_BAR_SWIPE_OVER_CHIP) + fun onInterceptTouchEvent_listenerReturnsFalse_flagOn_viewReturnsFalse() { + val handler = TestTouchEventHandler() + view.setTouchEventHandler(handler) + val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) + + handler.handleTouchReturnValue = false + + assertThat(view.onInterceptTouchEvent(event)).isFalse() + } + + @Test + @DisableFlags(FLAG_STATUS_BAR_SWIPE_OVER_CHIP) + fun onInterceptTouchEvent_listenerReturnsTrue_flagOff_viewReturnsFalse() { + val handler = TestTouchEventHandler() + view.setTouchEventHandler(handler) + val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) + + handler.handleTouchReturnValue = true + + assertThat(view.onInterceptTouchEvent(event)).isFalse() + } + + @Test + @EnableFlags(FLAG_STATUS_BAR_SWIPE_OVER_CHIP) + fun onInterceptTouchEvent_listenerReturnsTrue_flagOn_viewReturnsTrue() { + val handler = TestTouchEventHandler() + view.setTouchEventHandler(handler) + val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) + + handler.handleTouchReturnValue = true + + assertThat(view.onInterceptTouchEvent(event)).isTrue() + } + + @Test fun onTouchEvent_listenerReturnsTrue_viewReturnsTrue() { val handler = TestTouchEventHandler() view.setTouchEventHandler(handler) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index af5e60e9cd01..9b611057c059 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -1068,7 +1068,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { public void testShowBouncerOrKeyguard_needsFullScreen() { when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn( KeyguardSecurityModel.SecurityMode.SimPin); - mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false); + mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false, false); verify(mCentralSurfaces).hideKeyguard(); verify(mPrimaryBouncerInteractor).show(true); } @@ -1084,7 +1084,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { .thenReturn(KeyguardState.LOCKSCREEN); reset(mCentralSurfaces); - mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false); + mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false, false); verify(mPrimaryBouncerInteractor).show(true); verify(mCentralSurfaces).showKeyguard(); } @@ -1092,11 +1092,26 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { @Test @DisableSceneContainer public void testShowBouncerOrKeyguard_needsFullScreen_bouncerAlreadyShowing() { + boolean isFalsingReset = false; when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn( KeyguardSecurityModel.SecurityMode.SimPin); when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(true); - mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false); + mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false, isFalsingReset); verify(mCentralSurfaces, never()).hideKeyguard(); + verify(mPrimaryBouncerInteractor).show(true); + } + + @Test + @DisableSceneContainer + public void testShowBouncerOrKeyguard_needsFullScreen_bouncerAlreadyShowing_onFalsing() { + boolean isFalsingReset = true; + when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn( + KeyguardSecurityModel.SecurityMode.SimPin); + when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(true); + mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false, isFalsingReset); + verify(mCentralSurfaces, never()).hideKeyguard(); + + // Do not refresh the full screen bouncer if the call is from falsing verify(mPrimaryBouncerInteractor, never()).show(true); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt index 8fd0b31b82f3..171520f72269 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt @@ -21,6 +21,7 @@ import android.content.Context import android.content.Intent import android.net.ConnectivityManager import android.net.ConnectivityManager.NetworkCallback +import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WLAN import android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN @@ -59,6 +60,7 @@ import android.telephony.TelephonyManager.DATA_UNKNOWN import android.telephony.TelephonyManager.ERI_OFF import android.telephony.TelephonyManager.ERI_ON import android.telephony.TelephonyManager.EXTRA_CARRIER_ID +import android.telephony.TelephonyManager.EXTRA_DATA_SPN import android.telephony.TelephonyManager.EXTRA_PLMN import android.telephony.TelephonyManager.EXTRA_SHOW_PLMN import android.telephony.TelephonyManager.EXTRA_SHOW_SPN @@ -69,6 +71,7 @@ import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.settingslib.mobile.MobileMappings +import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.FakeFeatureFlagsClassic @@ -85,7 +88,6 @@ import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionMod import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfig import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfigTest.Companion.configWithOverride import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfigTest.Companion.createTestConfig -import com.android.systemui.statusbar.pipeline.mobile.data.model.toNetworkNameModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.signalStrength @@ -93,8 +95,6 @@ import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.Mobil import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.android.systemui.util.mockito.withArgCaptor @@ -112,6 +112,8 @@ import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations +import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor @Suppress("EXPERIMENTAL_IS_NOT_ENABLED") @OptIn(ExperimentalCoroutinesApi::class) @@ -807,6 +809,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { } @Test + @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN) fun networkName_usesBroadcastInfo_returnsDerived() = testScope.runTest { var latest: NetworkNameModel? = null @@ -815,14 +818,34 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { val intent = spnIntent() val captor = argumentCaptor<BroadcastReceiver>() verify(context).registerReceiver(captor.capture(), any()) - captor.value!!.onReceive(context, intent) + captor.lastValue.onReceive(context, intent) - assertThat(latest).isEqualTo(intent.toNetworkNameModel(SEP)) + // spnIntent() sets all values to true and test strings + assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN")) job.cancel() } @Test + @DisableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN) + fun networkName_usesBroadcastInfo_returnsDerived_flagOff() = + testScope.runTest { + var latest: NetworkNameModel? = null + val job = underTest.networkName.onEach { latest = it }.launchIn(this) + + val intent = spnIntent() + val captor = argumentCaptor<BroadcastReceiver>() + verify(context).registerReceiver(captor.capture(), any()) + captor.lastValue.onReceive(context, intent) + + // spnIntent() sets all values to true and test strings + assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN")) + + job.cancel() + } + + @Test + @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN) fun networkName_broadcastNotForThisSubId_keepsOldValue() = testScope.runTest { var latest: NetworkNameModel? = null @@ -831,22 +854,48 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { val intent = spnIntent() val captor = argumentCaptor<BroadcastReceiver>() verify(context).registerReceiver(captor.capture(), any()) - captor.value!!.onReceive(context, intent) + captor.lastValue.onReceive(context, intent) + + assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN")) + + // WHEN an intent with a different subId is sent + val wrongSubIntent = spnIntent(subId = 101) + + captor.lastValue.onReceive(context, wrongSubIntent) + + // THEN the previous intent's name is still used + assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN")) + + job.cancel() + } + + @Test + @DisableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN) + fun networkName_broadcastNotForThisSubId_keepsOldValue_flagOff() = + testScope.runTest { + var latest: NetworkNameModel? = null + val job = underTest.networkName.onEach { latest = it }.launchIn(this) + + val intent = spnIntent() + val captor = argumentCaptor<BroadcastReceiver>() + verify(context).registerReceiver(captor.capture(), any()) + captor.lastValue.onReceive(context, intent) - assertThat(latest).isEqualTo(intent.toNetworkNameModel(SEP)) + assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN")) // WHEN an intent with a different subId is sent val wrongSubIntent = spnIntent(subId = 101) - captor.value!!.onReceive(context, wrongSubIntent) + captor.lastValue.onReceive(context, wrongSubIntent) // THEN the previous intent's name is still used - assertThat(latest).isEqualTo(intent.toNetworkNameModel(SEP)) + assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN")) job.cancel() } @Test + @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN) fun networkName_broadcastHasNoData_updatesToDefault() = testScope.runTest { var latest: NetworkNameModel? = null @@ -855,9 +904,9 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { val intent = spnIntent() val captor = argumentCaptor<BroadcastReceiver>() verify(context).registerReceiver(captor.capture(), any()) - captor.value!!.onReceive(context, intent) + captor.lastValue.onReceive(context, intent) - assertThat(latest).isEqualTo(intent.toNetworkNameModel(SEP)) + assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN")) val intentWithoutInfo = spnIntent( @@ -865,7 +914,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { showPlmn = false, ) - captor.value!!.onReceive(context, intentWithoutInfo) + captor.lastValue.onReceive(context, intentWithoutInfo) assertThat(latest).isEqualTo(DEFAULT_NAME_MODEL) @@ -873,6 +922,34 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { } @Test + @DisableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN) + fun networkName_broadcastHasNoData_updatesToDefault_flagOff() = + testScope.runTest { + var latest: NetworkNameModel? = null + val job = underTest.networkName.onEach { latest = it }.launchIn(this) + + val intent = spnIntent() + val captor = argumentCaptor<BroadcastReceiver>() + verify(context).registerReceiver(captor.capture(), any()) + captor.lastValue.onReceive(context, intent) + + assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN")) + + val intentWithoutInfo = + spnIntent( + showSpn = false, + showPlmn = false, + ) + + captor.lastValue.onReceive(context, intentWithoutInfo) + + assertThat(latest).isEqualTo(DEFAULT_NAME_MODEL) + + job.cancel() + } + + @Test + @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN) fun networkName_usingEagerStrategy_retainsNameBetweenSubscribers() = testScope.runTest { // Use the [StateFlow.value] getter so we can prove that the collection happens @@ -884,10 +961,172 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { val intent = spnIntent() val captor = argumentCaptor<BroadcastReceiver>() verify(context).registerReceiver(captor.capture(), any()) - captor.value!!.onReceive(context, intent) + captor.lastValue.onReceive(context, intent) + + // The value is still there despite no active subscribers + assertThat(underTest.networkName.value) + .isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN")) + } + + @Test + @DisableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN) + fun networkName_usingEagerStrategy_retainsNameBetweenSubscribers_flagOff() = + testScope.runTest { + // Use the [StateFlow.value] getter so we can prove that the collection happens + // even when there is no [Job] + + // Starts out default + assertThat(underTest.networkName.value).isEqualTo(DEFAULT_NAME_MODEL) + + val intent = spnIntent() + val captor = argumentCaptor<BroadcastReceiver>() + verify(context).registerReceiver(captor.capture(), any()) + captor.lastValue.onReceive(context, intent) // The value is still there despite no active subscribers - assertThat(underTest.networkName.value).isEqualTo(intent.toNetworkNameModel(SEP)) + assertThat(underTest.networkName.value) + .isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN")) + } + + @Test + @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN) + fun networkName_allFieldsSet_doesNotUseDataSpn() = + testScope.runTest { + val latest by collectLastValue(underTest.networkName) + val captor = argumentCaptor<BroadcastReceiver>() + verify(context).registerReceiver(captor.capture(), any()) + + val intent = + spnIntent( + subId = SUB_1_ID, + showSpn = true, + spn = SPN, + dataSpn = DATA_SPN, + showPlmn = true, + plmn = PLMN, + ) + captor.lastValue.onReceive(context, intent) + assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN")) + } + + @Test + @DisableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN) + fun networkName_allFieldsSet_flagOff() = + testScope.runTest { + val latest by collectLastValue(underTest.networkName) + val captor = argumentCaptor<BroadcastReceiver>() + verify(context).registerReceiver(captor.capture(), any()) + + val intent = + spnIntent( + subId = SUB_1_ID, + showSpn = true, + spn = SPN, + dataSpn = DATA_SPN, + showPlmn = true, + plmn = PLMN, + ) + captor.lastValue.onReceive(context, intent) + assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN")) + } + + @Test + @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN) + fun networkName_showPlmn_plmnNotNull_showSpn_spnNull_dataSpnNotNull() = + testScope.runTest { + val latest by collectLastValue(underTest.networkName) + val captor = argumentCaptor<BroadcastReceiver>() + verify(context).registerReceiver(captor.capture(), any()) + val intent = + spnIntent( + subId = SUB_1_ID, + showSpn = true, + spn = null, + dataSpn = DATA_SPN, + showPlmn = true, + plmn = PLMN, + ) + captor.lastValue.onReceive(context, intent) + assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN")) + } + + @Test + @DisableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN) + fun networkName_showPlmn_plmnNotNull_showSpn_spnNull_dataSpnNotNull_flagOff() = + testScope.runTest { + val latest by collectLastValue(underTest.networkName) + val captor = argumentCaptor<BroadcastReceiver>() + verify(context).registerReceiver(captor.capture(), any()) + val intent = + spnIntent( + subId = SUB_1_ID, + showSpn = true, + spn = null, + dataSpn = DATA_SPN, + showPlmn = true, + plmn = PLMN, + ) + captor.lastValue.onReceive(context, intent) + assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN")) + } + + @Test + fun networkName_showPlmn_noShowSPN() = + testScope.runTest { + val latest by collectLastValue(underTest.networkName) + val captor = argumentCaptor<BroadcastReceiver>() + verify(context).registerReceiver(captor.capture(), any()) + val intent = + spnIntent( + subId = SUB_1_ID, + showSpn = false, + spn = SPN, + dataSpn = DATA_SPN, + showPlmn = true, + plmn = PLMN, + ) + captor.lastValue.onReceive(context, intent) + assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN")) + } + + @Test + @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN) + fun networkName_showPlmn_plmnNull_showSpn() = + testScope.runTest { + val latest by collectLastValue(underTest.networkName) + val captor = argumentCaptor<BroadcastReceiver>() + verify(context).registerReceiver(captor.capture(), any()) + val intent = + spnIntent( + subId = SUB_1_ID, + showSpn = true, + spn = SPN, + dataSpn = DATA_SPN, + showPlmn = true, + plmn = null, + ) + captor.lastValue.onReceive(context, intent) + assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$SPN")) + } + + @Test + @DisableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN) + fun networkName_showPlmn_plmnNull_showSpn_flagOff() = + testScope.runTest { + val latest by collectLastValue(underTest.networkName) + val captor = argumentCaptor<BroadcastReceiver>() + verify(context).registerReceiver(captor.capture(), any()) + val intent = + spnIntent( + subId = SUB_1_ID, + showSpn = true, + spn = SPN, + dataSpn = DATA_SPN, + showPlmn = true, + plmn = null, + ) + captor.lastValue.onReceive(context, intent) + assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$DATA_SPN")) } @Test @@ -1128,14 +1367,16 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { private fun spnIntent( subId: Int = SUB_1_ID, showSpn: Boolean = true, - spn: String = SPN, + spn: String? = SPN, + dataSpn: String? = DATA_SPN, showPlmn: Boolean = true, - plmn: String = PLMN, + plmn: String? = PLMN, ): Intent = Intent(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED).apply { putExtra(EXTRA_SUBSCRIPTION_INDEX, subId) putExtra(EXTRA_SHOW_SPN, showSpn) putExtra(EXTRA_SPN, spn) + putExtra(EXTRA_DATA_SPN, dataSpn) putExtra(EXTRA_SHOW_PLMN, showPlmn) putExtra(EXTRA_PLMN, plmn) } @@ -1148,6 +1389,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { private const val SEP = "-" private const val SPN = "testSpn" + private const val DATA_SPN = "testDataSpn" private const val PLMN = "testPlmn" } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt index bb6ba46f1a0b..54df9e99baa5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt @@ -25,12 +25,13 @@ import android.view.WindowManager import android.view.accessibility.AccessibilityManager import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.app.viewcapture.ViewCaptureAwareWindowManager import com.android.internal.logging.InstanceId import com.android.internal.logging.testing.UiEventLoggerFake -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager +import com.android.systemui.res.R import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener import com.android.systemui.util.concurrency.DelayableExecutor @@ -77,7 +78,7 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() { @Mock private lateinit var dumpManager: DumpManager @Mock - private lateinit var windowManager: WindowManager + private lateinit var windowManager: ViewCaptureAwareWindowManager @Mock private lateinit var powerManager: PowerManager @@ -1142,7 +1143,7 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() { inner class TestController( context: Context, logger: TemporaryViewLogger<ViewInfo>, - windowManager: WindowManager, + viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager, @Main mainExecutor: DelayableExecutor, accessibilityManager: AccessibilityManager, configurationController: ConfigurationController, @@ -1154,7 +1155,7 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() { ) : TemporaryViewDisplayController<ViewInfo, TemporaryViewLogger<ViewInfo>>( context, logger, - windowManager, + viewCaptureAwareWindowManager, mainExecutor, accessibilityManager, configurationController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt index 664f2df62782..4260b6558950 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt @@ -30,6 +30,8 @@ import android.widget.TextView import androidx.core.animation.doOnCancel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.app.viewcapture.ViewCapture +import com.android.app.viewcapture.ViewCaptureAwareWindowManager import com.android.internal.logging.InstanceId import com.android.internal.logging.testing.UiEventLoggerFake import com.android.systemui.SysuiTestCase @@ -84,6 +86,7 @@ class ChipbarCoordinatorTest : SysuiTestCase() { @Mock private lateinit var viewUtil: ViewUtil @Mock private lateinit var vibratorHelper: VibratorHelper @Mock private lateinit var swipeGestureHandler: SwipeChipbarAwayGestureHandler + @Mock private lateinit var lazyViewCapture: Lazy<ViewCapture> private lateinit var chipbarAnimator: TestChipbarAnimator private lateinit var fakeWakeLockBuilder: WakeLockFake.Builder private lateinit var fakeWakeLock: WakeLockFake @@ -112,7 +115,8 @@ class ChipbarCoordinatorTest : SysuiTestCase() { ChipbarCoordinator( context, logger, - windowManager, + ViewCaptureAwareWindowManager(windowManager, lazyViewCapture, + isViewCaptureEnabled = false), fakeExecutor, accessibilityManager, configurationController, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/graphics/ImageLoaderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/graphics/ImageLoaderKosmos.kt new file mode 100644 index 000000000000..6b8919dd86fe --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/graphics/ImageLoaderKosmos.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.graphics + +import android.content.testableContext +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testDispatcher + +var Kosmos.imageLoader by Kosmos.Fixture { ImageLoader(testableContext, testDispatcher) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderKosmos.kt index 2919d3f575c6..1e95fc12bdb5 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderKosmos.kt @@ -17,7 +17,6 @@ package com.android.systemui.keyguard.ui.binder import android.content.applicationContext -import android.view.layoutInflater import android.view.mockedLayoutInflater import android.view.windowManager import com.android.systemui.biometrics.domain.interactor.fingerprintPropertyInteractor @@ -25,7 +24,6 @@ import com.android.systemui.biometrics.domain.interactor.udfpsOverlayInteractor import com.android.systemui.common.ui.domain.interactor.configurationInteractor import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor import com.android.systemui.deviceentry.ui.viewmodel.AlternateBouncerUdfpsAccessibilityOverlayViewModel -import com.android.systemui.keyguard.dismissCallbackRegistry import com.android.systemui.keyguard.ui.SwipeUpAnywhereGestureHandler import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerDependencies import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerMessageAreaViewModel @@ -50,10 +48,10 @@ val Kosmos.alternateBouncerViewBinder by alternateBouncerDependencies = { alternateBouncerDependencies }, windowManager = { windowManager }, layoutInflater = { mockedLayoutInflater }, - dismissCallbackRegistry = dismissCallbackRegistry, ) } +@ExperimentalCoroutinesApi private val Kosmos.alternateBouncerDependencies by Kosmos.Fixture { AlternateBouncerDependencies( @@ -69,6 +67,7 @@ private val Kosmos.alternateBouncerDependencies by ) } +@ExperimentalCoroutinesApi private val Kosmos.alternateBouncerUdfpsIconViewModel by Kosmos.Fixture { AlternateBouncerUdfpsIconViewModel( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelKosmos.kt index bdd4afa3da7d..29583153ccc6 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelKosmos.kt @@ -18,6 +18,8 @@ package com.android.systemui.keyguard.ui.viewmodel +import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor +import com.android.systemui.keyguard.dismissCallbackRegistry import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture @@ -28,5 +30,7 @@ val Kosmos.alternateBouncerViewModel by Fixture { AlternateBouncerViewModel( statusBarKeyguardViewManager = statusBarKeyguardViewManager, keyguardTransitionInteractor = keyguardTransitionInteractor, + dismissCallbackRegistry = dismissCallbackRegistry, + alternateBouncerInteractor = { alternateBouncerInteractor }, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/controller/KeyguardMediaController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/controller/KeyguardMediaController.kt new file mode 100644 index 000000000000..aed320c02f75 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/controller/KeyguardMediaController.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.media.controls.controller + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.media.controls.ui.controller.KeyguardMediaController +import com.android.systemui.util.mockito.mock + +val Kosmos.keyguardMediaController by Kosmos.Fixture { mock<KeyguardMediaController>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/airplane/AirplaneModeTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/airplane/AirplaneModeTileKosmos.kt new file mode 100644 index 000000000000..73b1859acc3d --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/airplane/AirplaneModeTileKosmos.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.airplane + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.qsEventLogger +import com.android.systemui.statusbar.connectivity.ConnectivityModule + +val Kosmos.qsAirplaneModeTileConfig by + Kosmos.Fixture { ConnectivityModule.provideAirplaneModeTileConfig(qsEventLogger) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt index b6194e3f511d..bbe753e8c7a5 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt @@ -27,7 +27,9 @@ import kotlinx.coroutines.flow.filterNotNull class FakeQSSceneAdapter( private val inflateDelegate: suspend (Context) -> View, override val qqsHeight: Int = 0, + override val squishedQqsHeight: Int = 0, override val qsHeight: Int = 0, + override val squishedQsHeight: Int = 0, ) : QSSceneAdapter { private val _customizerState = MutableStateFlow<CustomizerState>(CustomizerState.Hidden) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt index 8e76a0bf5a13..53b6a2ee226b 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt @@ -18,6 +18,7 @@ package com.android.systemui.scene.domain.startable import com.android.internal.logging.uiEventLogger import com.android.systemui.authentication.domain.interactor.authenticationInteractor +import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.bouncerInteractor import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor import com.android.systemui.classifier.falsingCollector @@ -80,5 +81,6 @@ val Kosmos.sceneContainerStartable by Fixture { keyguardEnabledInteractor = keyguardEnabledInteractor, dismissCallbackRegistry = dismissCallbackRegistry, statusBarStateController = sysuiStatusBarStateController, + alternateBouncerInteractor = alternateBouncerInteractor, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneContentViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneContentViewModelKosmos.kt deleted file mode 100644 index 92401024c91f..000000000000 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneContentViewModelKosmos.kt +++ /dev/null @@ -1,34 +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. - */ - -@file:OptIn(ExperimentalCoroutinesApi::class) - -package com.android.systemui.shade.ui.viewmodel - -import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor -import com.android.systemui.kosmos.Kosmos -import com.android.systemui.kosmos.Kosmos.Fixture -import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeSceneContentViewModel -import com.android.systemui.scene.domain.interactor.sceneInteractor -import kotlinx.coroutines.ExperimentalCoroutinesApi - -val Kosmos.notificationsShadeSceneContentViewModel: - NotificationsShadeSceneContentViewModel by Fixture { - NotificationsShadeSceneContentViewModel( - deviceEntryInteractor = deviceEntryInteractor, - sceneInteractor = sceneInteractor, - ) -} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt new file mode 100644 index 000000000000..2a522ce18ed3 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.lockscreen + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.mockito.mock + +val Kosmos.lockscreenSmartspaceController by + Kosmos.Fixture { mock<LockscreenSmartspaceController>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt index 20dc668e4ff6..1542bb349a97 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel -import com.android.systemui.dump.dumpManager import com.android.systemui.flags.featureFlagsClassic import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture @@ -26,7 +25,6 @@ import com.android.systemui.statusbar.notification.stack.domain.interactor.notif val Kosmos.notificationsPlaceholderViewModel by Fixture { NotificationsPlaceholderViewModel( - dumpManager = dumpManager, interactor = notificationStackAppearanceInteractor, shadeInteractor = shadeInteractor, headsUpNotificationInteractor = headsUpNotificationInteractor, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/data/repository/VolumePanelGlobalStateRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/data/repository/VolumePanelGlobalStateRepositoryKosmos.kt index 2ba1211a9bdb..0b438d183544 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/data/repository/VolumePanelGlobalStateRepositoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/data/repository/VolumePanelGlobalStateRepositoryKosmos.kt @@ -18,6 +18,7 @@ package com.android.systemui.volume.panel.data.repository import com.android.systemui.dump.dumpManager import com.android.systemui.kosmos.Kosmos +import com.android.systemui.volume.shared.volumePanelLogger val Kosmos.volumePanelGlobalStateRepository by - Kosmos.Fixture { VolumePanelGlobalStateRepository(dumpManager) } + Kosmos.Fixture { VolumePanelGlobalStateRepository(dumpManager, volumePanelLogger) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorKosmos.kt index a18f498e5441..3804a9f21080 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorKosmos.kt @@ -28,6 +28,7 @@ import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria import com.android.systemui.volume.panel.domain.defaultCriteria import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey import com.android.systemui.volume.panel.ui.composable.enabledComponents +import com.android.systemui.volume.shared.volumePanelLogger import javax.inject.Provider var Kosmos.criteriaByKey: Map<VolumePanelComponentKey, Provider<ComponentAvailabilityCriteria>> by @@ -50,6 +51,7 @@ var Kosmos.componentsInteractor: ComponentsInteractor by enabledComponents, { defaultCriteria }, testScope.backgroundScope, + volumePanelLogger, criteriaByKey, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelKosmos.kt index 34a008f92518..c4fb9e486c4d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelKosmos.kt @@ -18,17 +18,19 @@ package com.android.systemui.volume.panel.ui.viewmodel import android.content.applicationContext import com.android.systemui.broadcast.broadcastDispatcher +import com.android.systemui.dump.dumpManager import com.android.systemui.kosmos.Kosmos -import com.android.systemui.kosmos.testScope +import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.statusbar.policy.configurationController import com.android.systemui.volume.panel.dagger.factory.volumePanelComponentFactory import com.android.systemui.volume.panel.domain.VolumePanelStartable import com.android.systemui.volume.panel.domain.interactor.volumePanelGlobalStateInteractor +import com.android.systemui.volume.shared.volumePanelLogger var Kosmos.volumePanelStartables: Set<VolumePanelStartable> by Kosmos.Fixture { emptySet() } var Kosmos.volumePanelViewModel: VolumePanelViewModel by - Kosmos.Fixture { volumePanelViewModelFactory.create(testScope.backgroundScope) } + Kosmos.Fixture { volumePanelViewModelFactory.create(applicationCoroutineScope) } val Kosmos.volumePanelViewModelFactory: VolumePanelViewModel.Factory by Kosmos.Fixture { @@ -37,6 +39,8 @@ val Kosmos.volumePanelViewModelFactory: VolumePanelViewModel.Factory by volumePanelComponentFactory, configurationController, broadcastDispatcher, + dumpManager, + volumePanelLogger, volumePanelGlobalStateInteractor, ) } diff --git a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt index 68f185eba42c..cc9b70e387e8 100644 --- a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt +++ b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt @@ -281,6 +281,12 @@ android.os.connectivity.WifiBatteryStats com.android.server.LocalServices +com.android.internal.graphics.cam.Cam +com.android.internal.graphics.cam.CamUtils +com.android.internal.graphics.cam.Frame +com.android.internal.graphics.cam.HctSolver +com.android.internal.graphics.ColorUtils + com.android.internal.util.BitUtils com.android.internal.util.BitwiseInputStream com.android.internal.util.BitwiseOutputStream diff --git a/services/Android.bp b/services/Android.bp index ded7379ad487..0006455f41b0 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -120,6 +120,7 @@ filegroup { ":services.backup-sources", ":services.companion-sources", ":services.contentcapture-sources", + ":services.appfunctions-sources", ":services.contentsuggestions-sources", ":services.contextualsearch-sources", ":services.coverage-sources", @@ -217,6 +218,7 @@ system_java_library { "services.autofill", "services.backup", "services.companion", + "services.appfunctions", "services.contentcapture", "services.contentsuggestions", "services.contextualsearch", diff --git a/services/accessibility/TEST_MAPPING b/services/accessibility/TEST_MAPPING index 299d33fbbe6d..38b4148f202b 100644 --- a/services/accessibility/TEST_MAPPING +++ b/services/accessibility/TEST_MAPPING @@ -36,21 +36,7 @@ ] }, { - "name": "FrameworksCoreTests", - "options": [ - { - "include-filter": "android.accessibilityservice" - }, - { - "include-filter": "android.view.accessibility" - }, - { - "include-filter": "com.android.internal.accessibility" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "FrameworksCoreTests_accessibility_NO_FLAKES" } ], "postsubmit": [ diff --git a/services/appfunctions/Android.bp b/services/appfunctions/Android.bp new file mode 100644 index 000000000000..f8ee823ef0c9 --- /dev/null +++ b/services/appfunctions/Android.bp @@ -0,0 +1,25 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +filegroup { + name: "services.appfunctions-sources", + srcs: ["java/**/*.java"], + path: "java", + visibility: ["//frameworks/base/services"], +} + +java_library_static { + name: "services.appfunctions", + defaults: ["platform_service_defaults"], + srcs: [ + ":services.appfunctions-sources", + "java/**/*.logtags", + ], + libs: ["services.core"], +} diff --git a/services/appfunctions/OWNERS b/services/appfunctions/OWNERS new file mode 100644 index 000000000000..b3108944a3ce --- /dev/null +++ b/services/appfunctions/OWNERS @@ -0,0 +1 @@ +include /core/java/android/app/appfunctions/OWNERS diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerService.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerService.java new file mode 100644 index 000000000000..f30e770be32b --- /dev/null +++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerService.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.appfunctions; + +import static android.app.appfunctions.flags.Flags.enableAppFunctionManager; + +import android.app.appfunctions.IAppFunctionManager; +import android.content.Context; + +import com.android.server.SystemService; + +/** + * Service that manages app functions. + */ +public class AppFunctionManagerService extends SystemService { + + public AppFunctionManagerService(Context context) { + super(context); + } + + @Override + public void onStart() { + if (enableAppFunctionManager()) { + publishBinderService(Context.APP_FUNCTION_SERVICE, new AppFunctionManagerStub()); + } + } + + private static class AppFunctionManagerStub extends IAppFunctionManager.Stub { + + } +} diff --git a/services/autofill/java/com/android/server/autofill/Helper.java b/services/autofill/java/com/android/server/autofill/Helper.java index 668852b46ea2..cd2a535aa2c5 100644 --- a/services/autofill/java/com/android/server/autofill/Helper.java +++ b/services/autofill/java/com/android/server/autofill/Helper.java @@ -16,10 +16,13 @@ package com.android.server.autofill; +import static android.Manifest.permission.INTERACT_ACROSS_USERS; + import static com.android.server.autofill.Helper.sDebug; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.assist.AssistStructure; @@ -29,6 +32,7 @@ import android.content.ComponentName; import android.content.Context; import android.hardware.display.DisplayManager; import android.metrics.LogMaker; +import android.os.UserHandle; import android.os.UserManager; import android.service.autofill.Dataset; import android.service.autofill.FillResponse; @@ -57,7 +61,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.concurrent.atomic.AtomicBoolean; - public final class Helper { private static final String TAG = "AutofillHelper"; @@ -69,7 +72,7 @@ public final class Helper { * {@code cmd autofill set log_level debug} or through * {@link android.provider.Settings.Global#AUTOFILL_LOGGING_LEVEL}. */ - public static boolean sDebug = false; + public static boolean sDebug = true; /** * Defines a logging flag that can be dynamically changed at runtime using @@ -104,6 +107,26 @@ public final class Helper { } /** + * Creates the context as the foreground user + * + * <p>Returns the current context as the current foreground user + */ + @RequiresPermission(INTERACT_ACROSS_USERS) + public static Context getUserContext(Context context) { + int userId = ActivityManager.getCurrentUser(); + Context c = context.createContextAsUser(UserHandle.of(userId), /* flags= */ 0); + if (sDebug) { + Slog.d( + TAG, + "Current User: " + + userId + + ", context created as: " + + c.getContentResolver().getUserId()); + } + return c; + } + + /** * Checks the URI permissions of the remote view, * to see if the current userId is able to access it. * diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index b7508b4f07b7..6dea8b052173 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -5084,11 +5084,17 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // Try to get the custom Icon, if one was passed through FillResponse int iconResourceId = response.getIconResourceId(); if (iconResourceId != 0) { - serviceIcon = mService.getMaster().getContext().getPackageManager() - .getDrawable( - mService.getServicePackageName(), - iconResourceId, - null); + long token = Binder.clearCallingIdentity(); + try { + serviceIcon = + mService.getMaster() + .getContext() + .getPackageManager() + .getDrawable( + mService.getServicePackageName(), iconResourceId, null); + } finally { + Binder.restoreCallingIdentity(token); + } } // Custom icon wasn't fetched, use the default package icon instead @@ -5114,11 +5120,19 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // Try to get the custom Service name, if one was passed through FillResponse int customServiceNameId = response.getServiceDisplayNameResourceId(); if (customServiceNameId != 0) { - serviceLabel = mService.getMaster().getContext().getPackageManager() - .getText( - mService.getServicePackageName(), - customServiceNameId, - null); + long token = Binder.clearCallingIdentity(); + try { + serviceLabel = + mService.getMaster() + .getContext() + .getPackageManager() + .getText( + mService.getServicePackageName(), + customServiceNameId, + null); + } finally { + Binder.restoreCallingIdentity(token); + } } // Custom label wasn't fetched, use the default package name instead diff --git a/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java b/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java index fa414e3b172b..5a71b895a57c 100644 --- a/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java +++ b/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java @@ -95,6 +95,7 @@ final class DialogFillUi { private final ComponentName mComponentName; private final int mThemeId; private final @NonNull Context mContext; + private final @NonNull Context mUserContext; private final @NonNull UiCallback mCallback; private final @NonNull ListView mListView; private final @Nullable ItemsAdapter mAdapter; @@ -104,6 +105,8 @@ final class DialogFillUi { private @Nullable AnnounceFilterResult mAnnounceFilterResult; private boolean mDestroyed; + // System has all permissions, see b/228957088 + @SuppressWarnings("AndroidFrameworkRequiresPermission") DialogFillUi(@NonNull Context context, @NonNull FillResponse response, @NonNull AutofillId focusedViewId, @Nullable String filterText, @Nullable Drawable serviceIcon, @Nullable String servicePackageName, @@ -117,6 +120,7 @@ final class DialogFillUi { mComponentName = componentName; mContext = new ContextThemeWrapper(context, mThemeId); + mUserContext = Helper.getUserContext(mContext); final LayoutInflater inflater = LayoutInflater.from(mContext); final View decor = inflater.inflate(R.layout.autofill_fill_dialog, null); @@ -224,7 +228,7 @@ final class DialogFillUi { }; final View content = presentation.applyWithTheme( - mContext, (ViewGroup) decor, interceptionHandler, mThemeId); + mUserContext, (ViewGroup) decor, interceptionHandler, mThemeId); container.addView(content); container.setVisibility(View.VISIBLE); } @@ -263,7 +267,7 @@ final class DialogFillUi { return true; }; final View content = presentation.applyWithTheme( - mContext, (ViewGroup) decor, interceptionHandler, mThemeId); + mUserContext, (ViewGroup) decor, interceptionHandler, mThemeId); container.addView(content); container.setVisibility(View.VISIBLE); container.setFocusable(true); @@ -305,7 +309,7 @@ final class DialogFillUi { try { if (sVerbose) Slog.v(TAG, "setting remote view for " + focusedViewId); view = presentation.applyWithTheme( - mContext, null, interceptionHandler, mThemeId); + mUserContext, null, interceptionHandler, mThemeId); } catch (RuntimeException e) { Slog.e(TAG, "Error inflating remote views", e); continue; diff --git a/services/autofill/java/com/android/server/autofill/ui/FillUi.java b/services/autofill/java/com/android/server/autofill/ui/FillUi.java index 1bda70deac06..14a3211a3e35 100644 --- a/services/autofill/java/com/android/server/autofill/ui/FillUi.java +++ b/services/autofill/java/com/android/server/autofill/ui/FillUi.java @@ -107,6 +107,7 @@ final class FillUi { new AutofillWindowPresenter(); private final @NonNull Context mContext; + private final @NonNull Context mUserContext; private final @NonNull AnchoredWindow mWindow; @@ -141,6 +142,8 @@ final class FillUi { return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK); } + // System has all permissions, see b/228957088 + @SuppressWarnings("AndroidFrameworkRequiresPermission") FillUi(@NonNull Context context, @NonNull FillResponse response, @NonNull AutofillId focusedViewId, @Nullable String filterText, @NonNull OverlayControl overlayControl, @NonNull CharSequence serviceLabel, @@ -153,6 +156,7 @@ final class FillUi { mCallback = callback; mFullScreen = isFullScreen(context); mContext = new ContextThemeWrapper(context, mThemeId); + mUserContext = Helper.getUserContext(mContext); mMaxInputLengthForAutofill = maxInputLengthForAutofill; final LayoutInflater inflater = LayoutInflater.from(mContext); @@ -245,7 +249,7 @@ final class FillUi { throw new RuntimeException("Permission error accessing RemoteView"); } content = response.getPresentation().applyWithTheme( - mContext, decor, interceptionHandler, mThemeId); + mUserContext, decor, interceptionHandler, mThemeId); container.addView(content); } catch (RuntimeException e) { callback.onCanceled(); @@ -286,7 +290,7 @@ final class FillUi { if (headerPresentation != null) { interactionBlocker = newInteractionBlocker(); mHeader = headerPresentation.applyWithTheme( - mContext, null, interactionBlocker, mThemeId); + mUserContext, null, interactionBlocker, mThemeId); final LinearLayout headerContainer = decor.findViewById(R.id.autofill_dataset_header); applyCancelAction(mHeader, response.getCancelIds()); @@ -305,7 +309,7 @@ final class FillUi { interactionBlocker = newInteractionBlocker(); } mFooter = footerPresentation.applyWithTheme( - mContext, null, interactionBlocker, mThemeId); + mUserContext, null, interactionBlocker, mThemeId); applyCancelAction(mFooter, response.getCancelIds()); // Footer not supported on some platform e.g. TV if (sVerbose) Slog.v(TAG, "adding footer"); @@ -334,7 +338,7 @@ final class FillUi { try { if (sVerbose) Slog.v(TAG, "setting remote view for " + focusedViewId); view = presentation.applyWithTheme( - mContext, null, interceptionHandler, mThemeId); + mUserContext, null, interceptionHandler, mThemeId); } catch (RuntimeException e) { Slog.e(TAG, "Error inflating remote views", e); continue; @@ -812,6 +816,7 @@ final class FillUi { pw.print(prefix); pw.print("mContentHeight: "); pw.println(mContentHeight); pw.print(prefix); pw.print("mDestroyed: "); pw.println(mDestroyed); pw.print(prefix); pw.print("mContext: "); pw.println(mContext); + pw.print(prefix); pw.print("mUserContext: "); pw.println(mUserContext); pw.print(prefix); pw.print("theme id: "); pw.print(mThemeId); switch (mThemeId) { case THEME_ID_DARK: diff --git a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java index 4d42f154a392..2ecce0b46662 100644 --- a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java +++ b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java @@ -176,6 +176,8 @@ final class SaveUi { private boolean mDestroyed; + // System has all permissions, see b/228957088 + @SuppressWarnings("AndroidFrameworkRequiresPermission") SaveUi(@NonNull Context context, @NonNull PendingUi pendingUi, @NonNull CharSequence serviceLabel, @NonNull Drawable serviceIcon, @Nullable String servicePackageName, @NonNull ComponentName componentName, @@ -193,7 +195,7 @@ final class SaveUi { mComponentName = componentName; mCompatMode = compatMode; - context = new ContextThemeWrapper(context, mThemeId) { + context = new ContextThemeWrapper(Helper.getUserContext(context), mThemeId) { @Override public void startActivity(Intent intent) { if (resolveActivity(intent) == null) { @@ -235,6 +237,7 @@ final class SaveUi { return null; } }; + final LayoutInflater inflater = LayoutInflater.from(context); final View view = inflater.inflate(R.layout.autofill_save, null); diff --git a/services/core/java/com/android/server/AlarmManagerInternal.java b/services/core/java/com/android/server/AlarmManagerInternal.java index b7f2b8d4ffe6..191137ade3a3 100644 --- a/services/core/java/com/android/server/AlarmManagerInternal.java +++ b/services/core/java/com/android/server/AlarmManagerInternal.java @@ -17,6 +17,7 @@ package com.android.server; import android.annotation.CurrentTimeMillisLong; +import android.annotation.UserIdInt; import android.app.PendingIntent; import com.android.server.SystemClockTime.TimeConfidence; @@ -36,6 +37,16 @@ public interface AlarmManagerInternal { /** Returns true if AlarmManager is delaying alarms due to device idle. */ boolean isIdling(); + /** + * Returns the time at which the next alarm for the given user is going to trigger, or 0 if + * there is none. + * + * <p>This value is UTC wall clock time in milliseconds, as returned by + * {@link System#currentTimeMillis()} for example. + * @see android.app.AlarmManager.AlarmClockInfo#getTriggerTime() + */ + long getNextAlarmTriggerTimeForUser(@UserIdInt int userId); + public void removeAlarmsForUid(int uid); public void registerInFlightListener(InFlightListener callback); diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java index e2ab0d9f2683..d80e40c5898a 100644 --- a/services/core/java/com/android/server/SystemConfig.java +++ b/services/core/java/com/android/server/SystemConfig.java @@ -371,6 +371,10 @@ public class SystemConfig { // exempt from ECM (i.e., they will never be considered "restricted"). private final ArraySet<SignedPackage> mEnhancedConfirmationTrustedInstallers = new ArraySet<>(); + // A map of UIDs defined by OEMs, mapping from name to value. The UIDs will be registered at the + // start of the system which allows OEMs to create and register their system services. + @NonNull private final ArrayMap<String, Integer> mOemDefinedUids = new ArrayMap<>(); + /** * Map of system pre-defined, uniquely named actors; keys are namespace, * value maps actor name to package name. @@ -594,6 +598,10 @@ public class SystemConfig { return mEnhancedConfirmationTrustedInstallers; } + @NonNull + public ArrayMap<String, Integer> getOemDefinedUids() { + return mOemDefinedUids; + } /** * Only use for testing. Do NOT use in production code. * @param readPermissions false to create an empty SystemConfig; true to read the permissions. @@ -1628,6 +1636,26 @@ public class SystemConfig { } } } break; + case "oem-defined-uid": { + final String uidName = parser.getAttributeValue(null, "name"); + final String uidValue = parser.getAttributeValue(null, "uid"); + if (TextUtils.isEmpty(uidName)) { + Slog.w(TAG, "<" + name + "> without valid uid name in " + permFile + + " at " + parser.getPositionDescription()); + } else if (TextUtils.isEmpty(uidValue)) { + Slog.w(TAG, "<" + name + "> without valid uid value in " + permFile + + " at " + parser.getPositionDescription()); + } else { + try { + final int oemDefinedUid = Integer.parseInt(uidValue); + mOemDefinedUids.put(uidName, oemDefinedUid); + } catch (NumberFormatException e) { + Slog.w(TAG, "<" + name + "> with invalid uid value: " + + uidValue + " in " + permFile + + " at " + parser.getPositionDescription()); + } + } + } break; case "enhanced-confirmation-trusted-package": { if (android.permission.flags.Flags.enhancedConfirmationModeApisEnabled()) { SignedPackage signedPackage = parseEnhancedConfirmationTrustedPackage( diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 2e1416b2887b..d4f729cfbaf6 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -6962,7 +6962,8 @@ public final class ActiveServices { } private boolean collectPackageServicesLocked(String packageName, Set<String> filterByClasses, - boolean evenPersistent, boolean doit, ArrayMap<ComponentName, ServiceRecord> services) { + boolean evenPersistent, boolean doit, int minOomAdj, + ArrayMap<ComponentName, ServiceRecord> services) { boolean didSomething = false; for (int i = services.size() - 1; i >= 0; i--) { ServiceRecord service = services.valueAt(i); @@ -6970,6 +6971,11 @@ public final class ActiveServices { || (service.packageName.equals(packageName) && (filterByClasses == null || filterByClasses.contains(service.name.getClassName()))); + if (service.app != null && service.app.mState.getCurAdj() < minOomAdj) { + Slog.i(TAG, "Skip force stopping service " + service + + ": below minimum oom adj level"); + continue; + } if (sameComponent && (service.app == null || evenPersistent || !service.app.isPersistent())) { if (!doit) { @@ -6993,6 +6999,12 @@ public final class ActiveServices { boolean bringDownDisabledPackageServicesLocked(String packageName, Set<String> filterByClasses, int userId, boolean evenPersistent, boolean fullStop, boolean doit) { + return bringDownDisabledPackageServicesLocked(packageName, filterByClasses, userId, + evenPersistent, fullStop, doit, ProcessList.INVALID_ADJ); + } + + boolean bringDownDisabledPackageServicesLocked(String packageName, Set<String> filterByClasses, + int userId, boolean evenPersistent, boolean fullStop, boolean doit, int minOomAdj) { boolean didSomething = false; if (mTmpCollectionResults != null) { @@ -7002,7 +7014,8 @@ public final class ActiveServices { if (userId == UserHandle.USER_ALL) { for (int i = mServiceMap.size() - 1; i >= 0; i--) { didSomething |= collectPackageServicesLocked(packageName, filterByClasses, - evenPersistent, doit, mServiceMap.valueAt(i).mServicesByInstanceName); + evenPersistent, doit, minOomAdj, + mServiceMap.valueAt(i).mServicesByInstanceName); if (!doit && didSomething) { return true; } @@ -7015,7 +7028,7 @@ public final class ActiveServices { if (smap != null) { ArrayMap<ComponentName, ServiceRecord> items = smap.mServicesByInstanceName; didSomething = collectPackageServicesLocked(packageName, filterByClasses, - evenPersistent, doit, items); + evenPersistent, doit, minOomAdj, items); } if (doit && filterByClasses == null) { forceStopPackageLocked(packageName, userId); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 9d8f3374d6ad..4a18cb1f5ed8 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -4377,6 +4377,16 @@ public class ActivityManagerService extends IActivityManager.Stub } @GuardedBy("this") + final boolean forceStopUserPackagesLocked(int userId, String reasonString, + boolean evenImportantServices) { + int minOomAdj = evenImportantServices ? ProcessList.INVALID_ADJ + : ProcessList.FOREGROUND_APP_ADJ; + return forceStopPackageInternalLocked(null, -1, false, false, + true, false, false, false, userId, reasonString, + ApplicationExitInfo.REASON_USER_STOPPED, minOomAdj); + } + + @GuardedBy("this") final boolean forceStopPackageLocked(String packageName, int appId, boolean callerWillRestart, boolean purgeCache, boolean doit, boolean evenPersistent, boolean uninstalling, boolean packageStateStopped, @@ -4385,7 +4395,6 @@ public class ActivityManagerService extends IActivityManager.Stub : ApplicationExitInfo.REASON_USER_REQUESTED; return forceStopPackageLocked(packageName, appId, callerWillRestart, purgeCache, doit, evenPersistent, uninstalling, packageStateStopped, userId, reasonString, reason); - } @GuardedBy("this") @@ -4393,6 +4402,16 @@ public class ActivityManagerService extends IActivityManager.Stub boolean callerWillRestart, boolean purgeCache, boolean doit, boolean evenPersistent, boolean uninstalling, boolean packageStateStopped, int userId, String reasonString, int reason) { + return forceStopPackageInternalLocked(packageName, appId, callerWillRestart, purgeCache, + doit, evenPersistent, uninstalling, packageStateStopped, userId, reasonString, + reason, ProcessList.INVALID_ADJ); + } + + @GuardedBy("this") + private boolean forceStopPackageInternalLocked(String packageName, int appId, + boolean callerWillRestart, boolean purgeCache, boolean doit, + boolean evenPersistent, boolean uninstalling, boolean packageStateStopped, + int userId, String reasonString, int reason, int minOomAdj) { int i; if (userId == UserHandle.USER_ALL && packageName == null) { @@ -4431,7 +4450,7 @@ public class ActivityManagerService extends IActivityManager.Stub } didSomething |= mProcessList.killPackageProcessesLSP(packageName, appId, userId, - ProcessList.INVALID_ADJ, callerWillRestart, false /* allowRestart */, doit, + minOomAdj, callerWillRestart, false /* allowRestart */, doit, evenPersistent, true /* setRemoved */, uninstalling, reason, subReason, @@ -4440,7 +4459,8 @@ public class ActivityManagerService extends IActivityManager.Stub } if (mServices.bringDownDisabledPackageServicesLocked( - packageName, null /* filterByClasses */, userId, evenPersistent, true, doit)) { + packageName, null /* filterByClasses */, userId, evenPersistent, + true, doit, minOomAdj)) { if (!doit) { return true; } @@ -19872,6 +19892,11 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override + public boolean isEarlyPackageKillEnabledForUserSwitch(int fromUserId, int toUserId) { + return mUserController.isEarlyPackageKillEnabledForUserSwitch(fromUserId, toUserId); + } + + @Override public void setStopUserOnSwitch(int value) { ActivityManagerService.this.setStopUserOnSwitch(value); } diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java index 4a31fd1f46e4..4c87e1ce357c 100644 --- a/services/core/java/com/android/server/am/AppRestrictionController.java +++ b/services/core/java/com/android/server/am/AppRestrictionController.java @@ -308,7 +308,7 @@ public final class AppRestrictionController { /** * Cache the package name and information about if it's a system module. */ - @GuardedBy("mLock") + @GuardedBy("mSystemModulesCache") private final HashMap<String, Boolean> mSystemModulesCache = new HashMap<>(); /** @@ -1603,7 +1603,7 @@ public final class AppRestrictionController { if (moduleInfos == null) { return; } - synchronized (mLock) { + synchronized (mSystemModulesCache) { for (ModuleInfo info : moduleInfos) { mSystemModulesCache.put(info.getPackageName(), Boolean.TRUE); } @@ -1611,7 +1611,7 @@ public final class AppRestrictionController { } private boolean isSystemModule(String packageName) { - synchronized (mLock) { + synchronized (mSystemModulesCache) { final Boolean val = mSystemModulesCache.get(packageName); if (val != null) { return val.booleanValue(); @@ -1639,7 +1639,7 @@ public final class AppRestrictionController { } } // Update the cache. - synchronized (mLock) { + synchronized (mSystemModulesCache) { mSystemModulesCache.put(packageName, isSystemModule); } return isSystemModule; diff --git a/services/core/java/com/android/server/am/TEST_MAPPING b/services/core/java/com/android/server/am/TEST_MAPPING index bac513221c78..45d7206d43bc 100644 --- a/services/core/java/com/android/server/am/TEST_MAPPING +++ b/services/core/java/com/android/server/am/TEST_MAPPING @@ -79,11 +79,7 @@ }, { "file_patterns": ["Battery[^/]*\\.java", "MeasuredEnergy[^/]*\\.java"], - "name": "FrameworksCoreTests", - "options": [ - { "include-filter": "com.android.internal.os.BatteryStatsTests" }, - { "exclude-annotation": "com.android.internal.os.SkipPresubmit" } - ] + "name": "FrameworksCoreTests_battery_stats" }, { "file_patterns": ["Battery[^/]*\\.java", "MeasuredEnergy[^/]*\\.java"], diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index 30efa3e87fc6..bdba6bcf66c0 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -131,6 +131,7 @@ import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.ObjectUtils; import com.android.internal.util.Preconditions; import com.android.internal.widget.LockPatternUtils; +import com.android.server.AlarmManagerInternal; import com.android.server.FactoryResetter; import com.android.server.FgThread; import com.android.server.LocalServices; @@ -247,6 +248,12 @@ class UserController implements Handler.Callback { private static final int USER_COMPLETED_EVENT_DELAY_MS = 5 * 1000; /** + * If a user has an alarm in the next this many milliseconds, avoid stopping it due to + * scheduled background stopping. + */ + private static final long TIME_BEFORE_USERS_ALARM_TO_AVOID_STOPPING_MS = 60 * 60_000; // 60 mins + + /** * Maximum number of users we allow to be running at a time, including system user. * * <p>This parameter only affects how many background users will be stopped when switching to a @@ -439,6 +446,15 @@ class UserController implements Handler.Callback { @GuardedBy("mLock") private final List<PendingUserStart> mPendingUserStarts = new ArrayList<>(); + /** + * Contains users which cannot abort the shutdown process. + * + * <p> For example, we don't abort shutdown for users whose processes have already been stopped + * due to {@link #isEarlyPackageKillEnabledForUserSwitch(int, int)}. + */ + @GuardedBy("mLock") + private final ArraySet<Integer> mDoNotAbortShutdownUserIds = new ArraySet<>(); + private final UserLifecycleListener mUserLifecycleListener = new UserLifecycleListener() { @Override public void onUserCreated(UserInfo user, Object token) { @@ -509,11 +525,11 @@ class UserController implements Handler.Callback { } } - private boolean shouldStopUserOnSwitch() { + private boolean isStopUserOnSwitchEnabled() { synchronized (mLock) { if (mStopUserOnSwitch != STOP_USER_ON_SWITCH_DEFAULT) { final boolean value = mStopUserOnSwitch == STOP_USER_ON_SWITCH_TRUE; - Slogf.i(TAG, "shouldStopUserOnSwitch(): returning overridden value (%b)", value); + Slogf.i(TAG, "isStopUserOnSwitchEnabled(): returning overridden value (%b)", value); return value; } } @@ -521,6 +537,26 @@ class UserController implements Handler.Callback { return property == -1 ? mDelayUserDataLocking : property == 1; } + /** + * Get whether or not the previous user's packages will be killed before the user is + * stopped during a user switch. + * + * <p> The primary use case of this method is for {@link com.android.server.SystemService} + * classes to call this API in their + * {@link com.android.server.SystemService#onUserSwitching} method implementation to prevent + * restarting any of the previous user's processes that will be killed during the user switch. + */ + boolean isEarlyPackageKillEnabledForUserSwitch(int fromUserId, int toUserId) { + // NOTE: The logic in this method could be extended to cover other cases where + // the previous user is also stopped like: guest users, ephemeral users, + // and users with DISALLOW_RUN_IN_BACKGROUND. Currently, this is not done + // because early killing is not enabled for these cases by default. + if (fromUserId == UserHandle.USER_SYSTEM) { + return false; + } + return isStopUserOnSwitchEnabled(); + } + void finishUserSwitch(UserState uss) { // This call holds the AM lock so we post to the handler. mHandler.post(() -> { @@ -1247,6 +1283,7 @@ class UserController implements Handler.Callback { return; } uss.setState(UserState.STATE_SHUTDOWN); + mDoNotAbortShutdownUserIds.remove(userId); } TimingsTraceAndSlog t = new TimingsTraceAndSlog(); t.traceBegin("setUserState-STATE_SHUTDOWN-" + userId + "-[stopUser]"); @@ -1555,7 +1592,8 @@ class UserController implements Handler.Callback { private void stopPackagesOfStoppedUser(@UserIdInt int userId, String reason) { if (DEBUG_MU) Slogf.i(TAG, "stopPackagesOfStoppedUser(%d): %s", userId, reason); - mInjector.activityManagerForceStopPackage(userId, reason); + mInjector.activityManagerForceStopUserPackages(userId, reason, + /* evenImportantServices= */ true); if (mInjector.getUserManager().isPreCreated(userId)) { // Don't fire intent for precreated. return; @@ -1608,6 +1646,21 @@ class UserController implements Handler.Callback { } } + private void stopPreviousUserPackagesIfEnabled(int fromUserId, int toUserId) { + if (!android.multiuser.Flags.stopPreviousUserApps() + || !isEarlyPackageKillEnabledForUserSwitch(fromUserId, toUserId)) { + return; + } + // Stop the previous user's packages early to reduce resource usage + // during user switching. Only do this when the previous user will + // be stopped regardless. + synchronized (mLock) { + mDoNotAbortShutdownUserIds.add(fromUserId); + } + mInjector.activityManagerForceStopUserPackages(fromUserId, + "early stop user packages", /* evenImportantServices= */ false); + } + void scheduleStartProfiles() { // Parent user transition to RUNNING_UNLOCKING happens on FgThread, so it is busy, there is // a chance the profile will reach RUNNING_LOCKED while parent is still locked, so no @@ -1889,7 +1942,8 @@ class UserController implements Handler.Callback { updateStartedUserArrayLU(); needStart = true; updateUmState = true; - } else if (uss.state == UserState.STATE_SHUTDOWN) { + } else if (uss.state == UserState.STATE_SHUTDOWN + || mDoNotAbortShutdownUserIds.contains(userId)) { Slogf.i(TAG, "User #" + userId + " is shutting down - will start after full shutdown"); mPendingUserStarts.add(new PendingUserStart(userId, userStartMode, @@ -2293,7 +2347,7 @@ class UserController implements Handler.Callback { hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND, oldUserId); synchronized (mLock) { // If running in background is disabled or mStopUserOnSwitch mode, stop the user. - if (hasRestriction || shouldStopUserOnSwitch()) { + if (hasRestriction || isStopUserOnSwitchEnabled()) { Slogf.i(TAG, "Stopping user %d and its profiles on user switch", oldUserId); stopUsersLU(oldUserId, /* allowDelayedLocking= */ false, null, null); return; @@ -2371,6 +2425,12 @@ class UserController implements Handler.Callback { void processScheduledStopOfBackgroundUser(Integer userIdInteger) { final int userId = userIdInteger; Slogf.d(TAG, "Considering stopping background user %d due to inactivity", userId); + + if (avoidStoppingUserDueToUpcomingAlarm(userId)) { + // We want this user running soon for alarm-purposes, so don't stop it now. Reschedule. + scheduleStopOfBackgroundUser(userId); + return; + } synchronized (mLock) { if (getCurrentOrTargetUserIdLU() == userId) { return; @@ -2390,6 +2450,18 @@ class UserController implements Handler.Callback { } } + /** + * Returns whether we should avoid stopping the user now due to it having an alarm set to fire + * soon. + */ + private boolean avoidStoppingUserDueToUpcomingAlarm(@UserIdInt int userId) { + final long alarmWallclockMs + = mInjector.getAlarmManagerInternal().getNextAlarmTriggerTimeForUser(userId); + return System.currentTimeMillis() < alarmWallclockMs + && (alarmWallclockMs + < System.currentTimeMillis() + TIME_BEFORE_USERS_ALARM_TO_AVOID_STOPPING_MS); + } + private void timeoutUserSwitch(UserState uss, int oldUserId, int newUserId) { TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG); t.traceBegin("timeoutUserSwitch-" + oldUserId + "-to-" + newUserId); @@ -3425,7 +3497,7 @@ class UserController implements Handler.Callback { pw.println(" mLastActiveUsersForDelayedLocking:" + mLastActiveUsersForDelayedLocking); pw.println(" mDelayUserDataLocking:" + mDelayUserDataLocking); pw.println(" mAllowUserUnlocking:" + mAllowUserUnlocking); - pw.println(" shouldStopUserOnSwitch():" + shouldStopUserOnSwitch()); + pw.println(" isStopUserOnSwitchEnabled():" + isStopUserOnSwitchEnabled()); pw.println(" mStopUserOnSwitch:" + mStopUserOnSwitch); pw.println(" mMaxRunningUsers:" + mMaxRunningUsers); pw.println(" mBackgroundUserScheduledStopTimeSecs:" @@ -3522,6 +3594,7 @@ class UserController implements Handler.Callback { Integer.toString(msg.arg1), msg.arg1); mInjector.getSystemServiceManager().onUserSwitching(msg.arg2, msg.arg1); + stopPreviousUserPackagesIfEnabled(msg.arg2, msg.arg1); scheduleOnUserCompletedEvent(msg.arg1, UserCompletedEventType.EVENT_TYPE_USER_SWITCHING, USER_COMPLETED_EVENT_DELAY_MS); @@ -3860,6 +3933,10 @@ class UserController implements Handler.Callback { return mPowerManagerInternal; } + AlarmManagerInternal getAlarmManagerInternal() { + return LocalServices.getService(AlarmManagerInternal.class); + } + KeyguardManager getKeyguardManager() { return mService.mContext.getSystemService(KeyguardManager.class); } @@ -3896,10 +3973,10 @@ class UserController implements Handler.Callback { }.sendNext(); } - void activityManagerForceStopPackage(@UserIdInt int userId, String reason) { + void activityManagerForceStopUserPackages(@UserIdInt int userId, String reason, + boolean evenImportantServices) { synchronized (mService) { - mService.forceStopPackageLocked(null, -1, false, false, true, false, false, false, - userId, reason); + mService.forceStopUserPackagesLocked(userId, reason, evenImportantServices); } }; diff --git a/services/core/java/com/android/server/appop/DiscreteRegistry.java b/services/core/java/com/android/server/appop/DiscreteRegistry.java index 539dbca30d6b..2ce4623a19b4 100644 --- a/services/core/java/com/android/server/appop/DiscreteRegistry.java +++ b/services/core/java/com/android/server/appop/DiscreteRegistry.java @@ -1413,11 +1413,11 @@ final class DiscreteRegistry { pw.print("-"); pw.print(flagsToString(mOpFlag)); pw.print("] at "); - date.setTime(discretizeTimeStamp(mNoteTime)); + date.setTime(mNoteTime); pw.print(sdf.format(date)); if (mNoteDuration != -1) { pw.print(" for "); - pw.print(discretizeDuration(mNoteDuration)); + pw.print(mNoteDuration); pw.print(" milliseconds "); } if (mAttributionFlags != AppOpsManager.ATTRIBUTION_FLAGS_NONE) { diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java index df29ca45930e..fb8a81be4b89 100644 --- a/services/core/java/com/android/server/biometrics/Utils.java +++ b/services/core/java/com/android/server/biometrics/Utils.java @@ -586,6 +586,8 @@ public class Utils { } } + // LINT.IfChange + /** * Checks if a client package is running in the background. * @@ -618,4 +620,6 @@ public class Utils { return true; } + // LINT.ThenChange(frameworks/base/packages/SystemUI/shared/biometrics/src/com/android + // /systemui/biometrics/Utils.kt) } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java index 0fdd57d64d8d..dca14914a572 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java @@ -264,4 +264,11 @@ public class BiometricTestSessionImpl extends ITestSession.Stub { } }); } + + @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC) + @Override + public int getSensorId() { + super.getSensorId_enforcePermission(); + return mSensorId; + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java index 8dc560b0e0b5..caa2c1c34ff7 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java @@ -293,4 +293,11 @@ class BiometricTestSessionImpl extends ITestSession.Stub { } }); } + + @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC) + @Override + public int getSensorId() { + super.getSensorId_enforcePermission(); + return mSensorId; + } }
\ No newline at end of file diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java index 3161b770dca6..310f592ddf5c 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java @@ -485,6 +485,7 @@ public class HdmiCecMessageValidator { * @return true if the hour is valid */ private static boolean isValidHour(int value) { + value = bcdToDecimal(value); return isWithinRange(value, 0, 23); } @@ -496,6 +497,7 @@ public class HdmiCecMessageValidator { * @return true if the minute is valid */ private static boolean isValidMinute(int value) { + value = bcdToDecimal(value); return isWithinRange(value, 0, 59); } @@ -507,10 +509,24 @@ public class HdmiCecMessageValidator { * @return true if the duration hours is valid */ private static boolean isValidDurationHours(int value) { + value = bcdToDecimal(value); return isWithinRange(value, 0, 99); } /** + * Convert BCD value to decimal value. + * + * @param value BCD value + * @return decimal value + */ + private static int bcdToDecimal(int value) { + int tens = (value & 0xF0) >> 4; + int ones = (value & 0x0F); + + return tens * 10 + ones; + } + + /** * Check if the given value is a valid recording sequence. A valid value is adheres to range * description defined in CEC 1.4 Specification : Operand Descriptions (Section 17) * diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index e555761e34e1..a69c7efc5b21 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -20,6 +20,8 @@ import static android.provider.DeviceConfig.NAMESPACE_INPUT_NATIVE_BOOT; import static android.view.KeyEvent.KEYCODE_UNKNOWN; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; +import static com.android.hardware.input.Flags.touchpadVisualizer; + import android.Manifest; import android.annotation.EnforcePermission; import android.annotation.NonNull; @@ -121,6 +123,7 @@ import com.android.server.LocalServices; import com.android.server.Watchdog; import com.android.server.input.InputManagerInternal.LidSwitchCallback; import com.android.server.input.debug.FocusEventDebugView; +import com.android.server.input.debug.TouchpadDebugViewController; import com.android.server.inputmethod.InputMethodManagerInternal; import com.android.server.policy.WindowManagerPolicy; @@ -303,6 +306,9 @@ public class InputManagerService extends IInputManager.Stub // Manages battery state for input devices. private final BatteryController mBatteryController; + @Nullable + private final TouchpadDebugViewController mTouchpadDebugViewController; + // Manages Keyboard backlight private final KeyboardBacklightControllerInterface mKeyboardBacklightController; @@ -460,6 +466,9 @@ public class InputManagerService extends IInputManager.Stub mSettingsObserver = new InputSettingsObserver(mContext, mHandler, this, mNative); mKeyboardLayoutManager = new KeyboardLayoutManager(mContext, mNative, mDataStore, injector.getLooper()); + mTouchpadDebugViewController = + touchpadVisualizer() ? new TouchpadDebugViewController(mContext, + injector.getLooper()) : null; mBatteryController = new BatteryController(mContext, mNative, injector.getLooper(), injector.getUEventManager()); mKeyboardBacklightController = InputFeatureFlagProvider.isKeyboardBacklightControlEnabled() @@ -589,6 +598,9 @@ public class InputManagerService extends IInputManager.Stub mKeyRemapper.systemRunning(); mPointerIconCache.systemRunning(); mKeyboardGlyphManager.systemRunning(); + if (mTouchpadDebugViewController != null) { + mTouchpadDebugViewController.systemRunning(); + } } private void reloadDeviceAliases() { diff --git a/services/core/java/com/android/server/input/OWNERS b/services/core/java/com/android/server/input/OWNERS index 4c20c4dc9d35..e2834ec246b6 100644 --- a/services/core/java/com/android/server/input/OWNERS +++ b/services/core/java/com/android/server/input/OWNERS @@ -1 +1,2 @@ +# Bug component: 136048 include /INPUT_OWNERS diff --git a/services/core/java/com/android/server/input/debug/TouchpadDebugView.java b/services/core/java/com/android/server/input/debug/TouchpadDebugView.java new file mode 100644 index 000000000000..5fca771c48b9 --- /dev/null +++ b/services/core/java/com/android/server/input/debug/TouchpadDebugView.java @@ -0,0 +1,71 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.input.debug; + +import android.content.Context; +import android.graphics.Color; +import android.view.Gravity; +import android.widget.LinearLayout; +import android.widget.TextView; + +public class TouchpadDebugView extends LinearLayout { + + /** + * Input device ID for the touchpad that this debug view is displaying. + */ + private final int mTouchpadId; + + public TouchpadDebugView(Context context, int touchpadId) { + super(context); + mTouchpadId = touchpadId; + init(context); + } + + private void init(Context context) { + setOrientation(VERTICAL); + setLayoutParams(new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT)); + setBackgroundColor(Color.TRANSPARENT); + + // TODO(b/286551975): Replace this content with the touchpad debug view. + + TextView textView1 = new TextView(context); + textView1.setBackgroundColor(Color.parseColor("#FFFF0000")); + textView1.setTextSize(20); + textView1.setText("Touchpad Debug View 1"); + textView1.setGravity(Gravity.CENTER); + textView1.setTextColor(Color.WHITE); + + textView1.setLayoutParams(new LayoutParams(1000, 200)); + + TextView textView2 = new TextView(context); + textView2.setBackgroundColor(Color.BLUE); + textView2.setTextSize(20); + textView2.setText("Touchpad Debug View 2"); + textView2.setGravity(Gravity.CENTER); + textView2.setTextColor(Color.WHITE); + textView2.setLayoutParams(new LayoutParams(1000, 200)); + + addView(textView1); + addView(textView2); + } + + public int getTouchpadId() { + return mTouchpadId; + } +} diff --git a/services/core/java/com/android/server/input/debug/TouchpadDebugViewController.java b/services/core/java/com/android/server/input/debug/TouchpadDebugViewController.java new file mode 100644 index 000000000000..9c2aa3677655 --- /dev/null +++ b/services/core/java/com/android/server/input/debug/TouchpadDebugViewController.java @@ -0,0 +1,125 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.input.debug; + +import android.annotation.Nullable; +import android.content.Context; +import android.graphics.PixelFormat; +import android.hardware.display.DisplayManager; +import android.hardware.input.InputManager; +import android.os.Handler; +import android.os.Looper; +import android.util.Slog; +import android.view.Display; +import android.view.Gravity; +import android.view.InputDevice; +import android.view.WindowManager; + +import java.util.Objects; + +public class TouchpadDebugViewController { + + private static final String TAG = "TouchpadDebugViewController"; + + private final Context mContext; + private final Handler mHandler; + @Nullable + private TouchpadDebugView mTouchpadDebugView; + + public TouchpadDebugViewController(Context context, Looper looper) { + final DisplayManager displayManager = Objects.requireNonNull( + context.getSystemService(DisplayManager.class)); + final Display defaultDisplay = displayManager.getDisplay(Display.DEFAULT_DISPLAY); + mContext = context.createDisplayContext(defaultDisplay); + mHandler = new Handler(looper); + } + + public void systemRunning() { + final InputManager inputManager = Objects.requireNonNull( + mContext.getSystemService(InputManager.class)); + inputManager.registerInputDeviceListener(mInputDeviceListener, mHandler); + for (int deviceId : inputManager.getInputDeviceIds()) { + mInputDeviceListener.onInputDeviceAdded(deviceId); + } + } + + private final InputManager.InputDeviceListener mInputDeviceListener = + new InputManager.InputDeviceListener() { + @Override + public void onInputDeviceAdded(int deviceId) { + final InputManager inputManager = Objects.requireNonNull( + mContext.getSystemService(InputManager.class)); + InputDevice inputDevice = inputManager.getInputDevice(deviceId); + + if (Objects.requireNonNull(inputDevice).supportsSource( + InputDevice.SOURCE_TOUCHPAD | InputDevice.SOURCE_MOUSE)) { + showDebugView(deviceId); + } + } + + @Override + public void onInputDeviceRemoved(int deviceId) { + hideDebugView(deviceId); + } + + @Override + public void onInputDeviceChanged(int deviceId) { + } + }; + + private void showDebugView(int touchpadId) { + if (mTouchpadDebugView != null) { + return; + } + final WindowManager wm = Objects.requireNonNull( + mContext.getSystemService(WindowManager.class)); + + mTouchpadDebugView = new TouchpadDebugView(mContext, touchpadId); + + final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(); + lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; + lp.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; + lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; + lp.setFitInsetsTypes(0); + lp.layoutInDisplayCutoutMode = + WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; + lp.format = PixelFormat.TRANSLUCENT; + lp.setTitle("TouchpadDebugView - display " + mContext.getDisplayId()); + lp.inputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL; + + lp.x = 40; + lp.y = 100; + lp.width = WindowManager.LayoutParams.WRAP_CONTENT; + lp.height = WindowManager.LayoutParams.WRAP_CONTENT; + lp.gravity = Gravity.TOP | Gravity.LEFT; + + wm.addView(mTouchpadDebugView, lp); + Slog.d(TAG, "Touchpad debug view created."); + } + + private void hideDebugView(int touchpadId) { + if (mTouchpadDebugView == null || mTouchpadDebugView.getTouchpadId() != touchpadId) { + return; + } + final WindowManager wm = Objects.requireNonNull( + mContext.getSystemService(WindowManager.class)); + wm.removeView(mTouchpadDebugView); + mTouchpadDebugView = null; + Slog.d(TAG, "Touchpad debug view removed."); + } +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java index 079b7242b1f3..ec1993a9b444 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java @@ -18,6 +18,7 @@ package com.android.server.inputmethod; import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED; import static android.content.Context.DEVICE_ID_DEFAULT; +import static android.inputmethodservice.InputMethodService.BACK_DISPOSITION_DEFAULT; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.Display.INVALID_DISPLAY; @@ -32,6 +33,8 @@ import android.content.Intent; import android.content.ServiceConnection; import android.content.pm.PackageManagerInternal; import android.inputmethodservice.InputMethodService; +import android.inputmethodservice.InputMethodService.BackDispositionMode; +import android.inputmethodservice.InputMethodService.ImeWindowVisibility; import android.os.Binder; import android.os.IBinder; import android.os.Process; @@ -99,24 +102,16 @@ final class InputMethodBindingController { /** * A set of status bits regarding the active IME. * - * <p>This value is a combination of following two bits:</p> - * <dl> - * <dt>{@link InputMethodService#IME_ACTIVE}</dt> - * <dd> - * If this bit is ON, connected IME is ready to accept touch/key events. - * </dd> - * <dt>{@link InputMethodService#IME_VISIBLE}</dt> - * <dd> - * If this bit is ON, some of IME view, e.g. software input, candidate view, is visible. - * </dd> - * </dl> - * <em>Do not update this value outside of {@link #setImeWindowStatus(IBinder, int, int)} and - * {@link InputMethodBindingController#unbindCurrentMethod()}.</em> + * <em>Do not update this value outside of {@link #setImeWindowVis} and + * {@link InputMethodBindingController#unbindCurrentMethod}.</em> */ - @GuardedBy("ImfLock.class") private int mImeWindowVis; + @ImeWindowVisibility + @GuardedBy("ImfLock.class") + private int mImeWindowVis; + @BackDispositionMode @GuardedBy("ImfLock.class") - private int mBackDisposition = InputMethodService.BACK_DISPOSITION_DEFAULT; + private int mBackDisposition = BACK_DISPOSITION_DEFAULT; @Nullable private CountDownLatch mLatchForTesting; @@ -718,22 +713,24 @@ final class InputMethodBindingController { } @GuardedBy("ImfLock.class") - void setImeWindowVis(int imeWindowVis) { + void setImeWindowVis(@ImeWindowVisibility int imeWindowVis) { mImeWindowVis = imeWindowVis; } + @ImeWindowVisibility @GuardedBy("ImfLock.class") int getImeWindowVis() { return mImeWindowVis; } + @BackDispositionMode @GuardedBy("ImfLock.class") int getBackDisposition() { return mBackDisposition; } @GuardedBy("ImfLock.class") - void setBackDisposition(int backDisposition) { + void setBackDisposition(@BackDispositionMode int backDisposition) { mBackDisposition = backDisposition; } } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 8afbd56728e4..36542837e582 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -86,6 +86,8 @@ import android.content.pm.UserInfo; import android.content.res.Resources; import android.hardware.input.InputManager; import android.inputmethodservice.InputMethodService; +import android.inputmethodservice.InputMethodService.BackDispositionMode; +import android.inputmethodservice.InputMethodService.ImeWindowVisibility; import android.media.AudioManagerInternal; import android.net.Uri; import android.os.Binder; @@ -2618,7 +2620,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } @GuardedBy("ImfLock.class") - private boolean shouldShowImeSwitcherLocked(int visibility, @UserIdInt int userId) { + private boolean shouldShowImeSwitcherLocked(@ImeWindowVisibility int visibility, + @UserIdInt int userId) { if (!mShowOngoingImeSwitcherForPhones) return false; // When the IME switcher dialog is shown, the IME switcher button should be hidden. // TODO(b/305849394): Make mMenuController multi-user aware. @@ -2722,8 +2725,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @BinderThread @GuardedBy("ImfLock.class") @SuppressWarnings("deprecation") - private void setImeWindowStatusLocked(int vis, int backDisposition, - @NonNull UserData userData) { + private void setImeWindowStatusLocked(@ImeWindowVisibility int vis, + @BackDispositionMode int backDisposition, @NonNull UserData userData) { final int topFocusedDisplayId = mWindowManagerInternal.getTopFocusedDisplayId(); final int userId = userData.mUserId; @@ -2772,7 +2775,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. final int userId = resolveImeUserIdFromDisplayIdLocked(displayId); if (disableImeIcon) { final var bindingController = getInputMethodBindingController(userId); - updateSystemUiLocked(0, bindingController.getBackDisposition(), userId); + updateSystemUiLocked(0 /* vis */, bindingController.getBackDisposition(), userId); } else { updateSystemUiLocked(userId); } @@ -2787,7 +2790,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } @GuardedBy("ImfLock.class") - private void updateSystemUiLocked(int vis, int backDisposition, @UserIdInt int userId) { + private void updateSystemUiLocked(@ImeWindowVisibility int vis, + @BackDispositionMode int backDisposition, @UserIdInt int userId) { // To minimize app compat risk, ignore background users' request for single-user mode. // TODO(b/357178609): generalize the logic and remove this special rule. if (!mConcurrentMultiUserModeEnabled && userId != mCurrentImeUserId) { @@ -6812,7 +6816,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @BinderThread @Override - public void setImeWindowStatusAsync(int vis, int backDisposition) { + public void setImeWindowStatusAsync(@ImeWindowVisibility int vis, + @BackDispositionMode int backDisposition) { synchronized (ImfLock.class) { if (!calledWithValidTokenLocked(mToken, mUserData)) { return; diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java index 4dbbfa2be334..202543c7e7a5 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java @@ -512,6 +512,9 @@ final class InputMethodSubtypeSwitchingController { /** * Gets the next input method and subtype from the given ones. * + * <p>If the given input method and subtype are not found, this returns the most recent + * input method and subtype.</p> + * * @param imi the input method to find the next value from. * @param subtype the input method subtype to find the next value from, if any. * @param onlyCurrentIme whether to consider only subtypes of the current input method. @@ -523,17 +526,20 @@ final class InputMethodSubtypeSwitchingController { public ImeSubtypeListItem next(@NonNull InputMethodInfo imi, @Nullable InputMethodSubtype subtype, boolean onlyCurrentIme, boolean useRecency, boolean forward) { - final int size = mItems.size(); - if (size <= 1) { + if (mItems.isEmpty()) { return null; } final int index = getIndex(imi, subtype, useRecency); if (index < 0) { - return null; + Slog.w(TAG, "Trying to switch away from input method: " + imi + + " and subtype " + subtype + " which are not in the list," + + " falling back to most recent item in list."); + return mItems.get(mRecencyMap[0]); } final int incrementSign = (forward ? 1 : -1); + final int size = mItems.size(); for (int i = 1; i < size; i++) { final int nextIndex = (index + i * incrementSign + size) % size; final int mappedIndex = useRecency ? mRecencyMap[nextIndex] : nextIndex; @@ -554,7 +560,7 @@ final class InputMethodSubtypeSwitchingController { */ public boolean setMostRecent(@NonNull InputMethodInfo imi, @Nullable InputMethodSubtype subtype) { - if (mItems.size() <= 1) { + if (mItems.isEmpty()) { return false; } @@ -608,247 +614,69 @@ final class InputMethodSubtypeSwitchingController { } } - @VisibleForTesting - public static class ControllerImpl { - - @NonNull - private final DynamicRotationList mSwitchingAwareRotationList; - @NonNull - private final StaticRotationList mSwitchingUnawareRotationList; - /** List of input methods and subtypes. */ - @Nullable - private final RotationList mRotationList; - /** List of input methods and subtypes suitable for hardware keyboards. */ - @Nullable - private final RotationList mHardwareRotationList; - - /** - * Whether there was a user action since the last input method and subtype switch. - * Used to determine the switching behaviour for {@link #MODE_AUTO}. - */ - private boolean mUserActionSinceSwitch; - - @NonNull - public static ControllerImpl createFrom(@Nullable ControllerImpl currentInstance, - @NonNull List<ImeSubtypeListItem> sortedEnabledItems, - @NonNull List<ImeSubtypeListItem> hardwareKeyboardItems) { - final var switchingAwareImeSubtypes = filterImeSubtypeList(sortedEnabledItems, - true /* supportsSwitchingToNextInputMethod */); - final var switchingUnawareImeSubtypes = filterImeSubtypeList(sortedEnabledItems, - false /* supportsSwitchingToNextInputMethod */); - - final DynamicRotationList switchingAwareRotationList; - if (currentInstance != null && Objects.equals( - currentInstance.mSwitchingAwareRotationList.mImeSubtypeList, - switchingAwareImeSubtypes)) { - // Can reuse the current instance. - switchingAwareRotationList = currentInstance.mSwitchingAwareRotationList; - } else { - switchingAwareRotationList = new DynamicRotationList(switchingAwareImeSubtypes); - } - - final StaticRotationList switchingUnawareRotationList; - if (currentInstance != null && Objects.equals( - currentInstance.mSwitchingUnawareRotationList.mImeSubtypeList, - switchingUnawareImeSubtypes)) { - // Can reuse the current instance. - switchingUnawareRotationList = currentInstance.mSwitchingUnawareRotationList; - } else { - switchingUnawareRotationList = new StaticRotationList(switchingUnawareImeSubtypes); - } - - final RotationList rotationList; - if (!Flags.imeSwitcherRevamp()) { - rotationList = null; - } else if (currentInstance != null && currentInstance.mRotationList != null - && Objects.equals( - currentInstance.mRotationList.mItems, sortedEnabledItems)) { - // Can reuse the current instance. - rotationList = currentInstance.mRotationList; - } else { - rotationList = new RotationList(sortedEnabledItems); - } - - final RotationList hardwareRotationList; - if (!Flags.imeSwitcherRevamp()) { - hardwareRotationList = null; - } else if (currentInstance != null && currentInstance.mHardwareRotationList != null - && Objects.equals( - currentInstance.mHardwareRotationList.mItems, hardwareKeyboardItems)) { - // Can reuse the current instance. - hardwareRotationList = currentInstance.mHardwareRotationList; - } else { - hardwareRotationList = new RotationList(hardwareKeyboardItems); - } - - return new ControllerImpl(switchingAwareRotationList, switchingUnawareRotationList, - rotationList, hardwareRotationList); - } - - private ControllerImpl(@NonNull DynamicRotationList switchingAwareRotationList, - @NonNull StaticRotationList switchingUnawareRotationList, - @Nullable RotationList rotationList, - @Nullable RotationList hardwareRotationList) { - mSwitchingAwareRotationList = switchingAwareRotationList; - mSwitchingUnawareRotationList = switchingUnawareRotationList; - mRotationList = rotationList; - mHardwareRotationList = hardwareRotationList; - } + @NonNull + private DynamicRotationList mSwitchingAwareRotationList = + new DynamicRotationList(Collections.emptyList()); + @NonNull + private StaticRotationList mSwitchingUnawareRotationList = + new StaticRotationList(Collections.emptyList()); + /** List of input methods and subtypes. */ + @NonNull + private RotationList mRotationList = new RotationList(Collections.emptyList()); + /** List of input methods and subtypes suitable for hardware keyboards. */ + @NonNull + private RotationList mHardwareRotationList = new RotationList(Collections.emptyList()); - @Nullable - public ImeSubtypeListItem getNextInputMethod(boolean onlyCurrentIme, - @Nullable InputMethodInfo imi, @Nullable InputMethodSubtype subtype, - @SwitchMode int mode, boolean forward) { - if (imi == null) { - return null; - } - if (Flags.imeSwitcherRevamp() && mRotationList != null) { - return mRotationList.next(imi, subtype, onlyCurrentIme, - isRecency(mode, forward), forward); - } else if (imi.supportsSwitchingToNextInputMethod()) { - return mSwitchingAwareRotationList.getNextInputMethodLocked(onlyCurrentIme, imi, - subtype); - } else { - return mSwitchingUnawareRotationList.getNextInputMethodLocked(onlyCurrentIme, imi, - subtype); - } - } + /** + * Whether there was a user action since the last input method and subtype switch. + * Used to determine the switching behaviour for {@link #MODE_AUTO}. + */ + private boolean mUserActionSinceSwitch; - @Nullable - public ImeSubtypeListItem getNextInputMethodForHardware(boolean onlyCurrentIme, - @NonNull InputMethodInfo imi, @Nullable InputMethodSubtype subtype, - @SwitchMode int mode, boolean forward) { - if (Flags.imeSwitcherRevamp() && mHardwareRotationList != null) { - return mHardwareRotationList.next(imi, subtype, onlyCurrentIme, - isRecency(mode, forward), forward); - } - return null; - } + /** + * Updates the list of input methods and subtypes used for switching. If the given items are + * equal to the existing ones (regardless of recency order), the update is skipped and the + * current recency order is kept. Otherwise, the recency order is reset. + * + * @param sortedEnabledItems the sorted list of enabled input methods and subtypes. + * @param hardwareKeyboardItems the unsorted list of enabled input method and subtypes + * suitable for hardware keyboards. + */ + @VisibleForTesting + void update(@NonNull List<ImeSubtypeListItem> sortedEnabledItems, + @NonNull List<ImeSubtypeListItem> hardwareKeyboardItems) { + final var switchingAwareImeSubtypes = filterImeSubtypeList(sortedEnabledItems, + true /* supportsSwitchingToNextInputMethod */); + final var switchingUnawareImeSubtypes = filterImeSubtypeList(sortedEnabledItems, + false /* supportsSwitchingToNextInputMethod */); - /** - * Called when the user took an action that should update the recency of the current - * input method and subtype in the switching list. - * - * @param imi the currently selected input method. - * @param subtype the currently selected input method subtype, if any. - * @return {@code true} if the recency was updated, otherwise {@code false}. - * @see android.inputmethodservice.InputMethodServiceInternal#notifyUserActionIfNecessary() - */ - public boolean onUserActionLocked(@NonNull InputMethodInfo imi, - @Nullable InputMethodSubtype subtype) { - boolean recencyUpdated = false; - if (Flags.imeSwitcherRevamp()) { - if (mRotationList != null) { - recencyUpdated |= mRotationList.setMostRecent(imi, subtype); - } - if (mHardwareRotationList != null) { - recencyUpdated |= mHardwareRotationList.setMostRecent(imi, subtype); - } - if (recencyUpdated) { - mUserActionSinceSwitch = true; - } - } else if (imi.supportsSwitchingToNextInputMethod()) { - mSwitchingAwareRotationList.onUserAction(imi, subtype); - } - return recencyUpdated; + if (!Objects.equals(mSwitchingAwareRotationList.mImeSubtypeList, + switchingAwareImeSubtypes)) { + mSwitchingAwareRotationList = new DynamicRotationList(switchingAwareImeSubtypes); } - /** Called when the input method and subtype was changed. */ - public void onInputMethodSubtypeChanged() { - mUserActionSinceSwitch = false; + if (!Objects.equals(mSwitchingUnawareRotationList.mImeSubtypeList, + switchingUnawareImeSubtypes)) { + mSwitchingUnawareRotationList = new StaticRotationList(switchingUnawareImeSubtypes); } - /** - * Whether the given mode and direction result in recency or static order. - * - * <p>{@link #MODE_AUTO} resolves to the recency order for the first forwards switch - * after an {@link #onUserActionLocked user action}, and otherwise to the static order.</p> - * - * @param mode the switching mode. - * @param forward the switching direction. - * @return {@code true} for the recency order, otherwise {@code false}. - */ - private boolean isRecency(@SwitchMode int mode, boolean forward) { - if (mode == MODE_AUTO && mUserActionSinceSwitch && forward) { - return true; - } else { - return mode == MODE_RECENT; - } + if (Flags.imeSwitcherRevamp() + && !Objects.equals(mRotationList.mItems, sortedEnabledItems)) { + mRotationList = new RotationList(sortedEnabledItems); } - @NonNull - private static List<ImeSubtypeListItem> filterImeSubtypeList( - @NonNull List<ImeSubtypeListItem> items, - boolean supportsSwitchingToNextInputMethod) { - final ArrayList<ImeSubtypeListItem> result = new ArrayList<>(); - final int numItems = items.size(); - for (int i = 0; i < numItems; i++) { - final ImeSubtypeListItem item = items.get(i); - if (item.mImi.supportsSwitchingToNextInputMethod() - == supportsSwitchingToNextInputMethod) { - result.add(item); - } - } - return result; + if (Flags.imeSwitcherRevamp() + && !Objects.equals(mHardwareRotationList.mItems, hardwareKeyboardItems)) { + mHardwareRotationList = new RotationList(hardwareKeyboardItems); } - - protected void dump(@NonNull Printer pw, @NonNull String prefix) { - pw.println(prefix + "mSwitchingAwareRotationList:"); - mSwitchingAwareRotationList.dump(pw, prefix + " "); - pw.println(prefix + "mSwitchingUnawareRotationList:"); - mSwitchingUnawareRotationList.dump(pw, prefix + " "); - if (Flags.imeSwitcherRevamp()) { - if (mRotationList != null) { - pw.println(prefix + "mRotationList:"); - mRotationList.dump(pw, prefix + " "); - } - if (mHardwareRotationList != null) { - pw.println(prefix + "mHardwareRotationList:"); - mHardwareRotationList.dump(pw, prefix + " "); - } - pw.println(prefix + "User action since last switch: " + mUserActionSinceSwitch); - } - } - } - - @NonNull - private ControllerImpl mController; - - InputMethodSubtypeSwitchingController() { - mController = ControllerImpl.createFrom(null, Collections.emptyList(), - Collections.emptyList()); - } - - /** - * Called when the user took an action that should update the recency of the current - * input method and subtype in the switching list. - * - * @param imi the currently selected input method. - * @param subtype the currently selected input method subtype, if any. - * @see android.inputmethodservice.InputMethodServiceInternal#notifyUserActionIfNecessary() - */ - public void onUserActionLocked(@NonNull InputMethodInfo imi, - @Nullable InputMethodSubtype subtype) { - mController.onUserActionLocked(imi, subtype); - } - - /** Called when the input method and subtype was changed. */ - public void onInputMethodSubtypeChanged() { - mController.onInputMethodSubtypeChanged(); - } - - public void resetCircularListLocked(@NonNull Context context, - @NonNull InputMethodSettings settings) { - mController = ControllerImpl.createFrom(mController, - getSortedInputMethodAndSubtypeList( - false /* includeAuxiliarySubtypes */, false /* isScreenLocked */, - false /* forImeMenu */, context, settings), - getInputMethodAndSubtypeListForHardwareKeyboard(context, settings)); } /** * Gets the next input method and subtype, starting from the given ones, in the given direction. * + * <p>If the given input method and subtype are not found, this returns the most recent + * input method and subtype.</p> + * * @param onlyCurrentIme whether to consider only subtypes of the current input method. * @param imi the input method to find the next value from. * @param subtype the input method subtype to find the next value from, if any. @@ -860,13 +688,28 @@ final class InputMethodSubtypeSwitchingController { public ImeSubtypeListItem getNextInputMethodLocked(boolean onlyCurrentIme, @Nullable InputMethodInfo imi, @Nullable InputMethodSubtype subtype, @SwitchMode int mode, boolean forward) { - return mController.getNextInputMethod(onlyCurrentIme, imi, subtype, mode, forward); + if (imi == null) { + return null; + } + if (Flags.imeSwitcherRevamp()) { + return mRotationList.next(imi, subtype, onlyCurrentIme, + isRecency(mode, forward), forward); + } else if (imi.supportsSwitchingToNextInputMethod()) { + return mSwitchingAwareRotationList.getNextInputMethodLocked(onlyCurrentIme, imi, + subtype); + } else { + return mSwitchingUnawareRotationList.getNextInputMethodLocked(onlyCurrentIme, imi, + subtype); + } } /** * Gets the next input method and subtype suitable for hardware keyboards, starting from the * given ones, in the given direction. * + * <p>If the given input method and subtype are not found, this returns the most recent + * input method and subtype.</p> + * * @param onlyCurrentIme whether to consider only subtypes of the current input method. * @param imi the input method to find the next value from. * @param subtype the input method subtype to find the next value from, if any. @@ -878,11 +721,98 @@ final class InputMethodSubtypeSwitchingController { public ImeSubtypeListItem getNextInputMethodForHardware(boolean onlyCurrentIme, @NonNull InputMethodInfo imi, @Nullable InputMethodSubtype subtype, @SwitchMode int mode, boolean forward) { - return mController.getNextInputMethodForHardware(onlyCurrentIme, imi, subtype, mode, - forward); + if (Flags.imeSwitcherRevamp()) { + return mHardwareRotationList.next(imi, subtype, onlyCurrentIme, + isRecency(mode, forward), forward); + } + return null; + } + + /** + * Called when the user took an action that should update the recency of the current + * input method and subtype in the switching list. + * + * @param imi the currently selected input method. + * @param subtype the currently selected input method subtype, if any. + * @return {@code true} if the recency was updated, otherwise {@code false}. + * @see android.inputmethodservice.InputMethodServiceInternal#notifyUserActionIfNecessary() + */ + public boolean onUserActionLocked(@NonNull InputMethodInfo imi, + @Nullable InputMethodSubtype subtype) { + boolean recencyUpdated = false; + if (Flags.imeSwitcherRevamp()) { + recencyUpdated |= mRotationList.setMostRecent(imi, subtype); + recencyUpdated |= mHardwareRotationList.setMostRecent(imi, subtype); + if (recencyUpdated) { + mUserActionSinceSwitch = true; + } + } else if (imi.supportsSwitchingToNextInputMethod()) { + mSwitchingAwareRotationList.onUserAction(imi, subtype); + } + return recencyUpdated; + } + + /** Called when the input method and subtype was changed. */ + public void onInputMethodSubtypeChanged() { + mUserActionSinceSwitch = false; } - public void dump(@NonNull Printer pw, @NonNull String prefix) { - mController.dump(pw, prefix); + /** + * Whether the given mode and direction result in recency or static order. + * + * <p>{@link #MODE_AUTO} resolves to the recency order for the first forwards switch + * after an {@link #onUserActionLocked user action}, and otherwise to the static order.</p> + * + * @param mode the switching mode. + * @param forward the switching direction. + * @return {@code true} for the recency order, otherwise {@code false}. + */ + private boolean isRecency(@SwitchMode int mode, boolean forward) { + if (mode == MODE_AUTO && mUserActionSinceSwitch && forward) { + return true; + } else { + return mode == MODE_RECENT; + } + } + + @NonNull + private static List<ImeSubtypeListItem> filterImeSubtypeList( + @NonNull List<ImeSubtypeListItem> items, + boolean supportsSwitchingToNextInputMethod) { + final ArrayList<ImeSubtypeListItem> result = new ArrayList<>(); + final int numItems = items.size(); + for (int i = 0; i < numItems; i++) { + final ImeSubtypeListItem item = items.get(i); + if (item.mImi.supportsSwitchingToNextInputMethod() + == supportsSwitchingToNextInputMethod) { + result.add(item); + } + } + return result; + } + + void dump(@NonNull Printer pw, @NonNull String prefix) { + pw.println(prefix + "mSwitchingAwareRotationList:"); + mSwitchingAwareRotationList.dump(pw, prefix + " "); + pw.println(prefix + "mSwitchingUnawareRotationList:"); + mSwitchingUnawareRotationList.dump(pw, prefix + " "); + if (Flags.imeSwitcherRevamp()) { + pw.println(prefix + "mRotationList:"); + mRotationList.dump(pw, prefix + " "); + pw.println(prefix + "mHardwareRotationList:"); + mHardwareRotationList.dump(pw, prefix + " "); + pw.println(prefix + "User action since last switch: " + mUserActionSinceSwitch); + } + } + + InputMethodSubtypeSwitchingController() { + } + + public void resetCircularListLocked(@NonNull Context context, + @NonNull InputMethodSettings settings) { + update(getSortedInputMethodAndSubtypeList( + false /* includeAuxiliarySubtypes */, false /* isScreenLocked */, + false /* forImeMenu */, context, settings), + getInputMethodAndSubtypeListForHardwareKeyboard(context, settings)); } } diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index 17f6561cb757..a6f4c0e597d1 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -530,6 +530,12 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { */ private boolean mUseDifferentDelaysForBackgroundChain; + /** + * Core uids and apps without the internet permission will not have any firewall rules applied + * to them. + */ + private boolean mNeverApplyRulesToCoreUids; + // See main javadoc for instructions on how to use these locks. final Object mUidRulesFirstLock = new Object(); final Object mNetworkPoliciesSecondLock = new Object(); @@ -760,7 +766,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { /** List of apps indexed by uid and whether they have the internet permission */ @GuardedBy("mUidRulesFirstLock") - private final SparseBooleanArray mInternetPermissionMap = new SparseBooleanArray(); + @VisibleForTesting + final SparseBooleanArray mInternetPermissionMap = new SparseBooleanArray(); /** * Map of uid -> UidStateCallbackInfo objects holding the data received from @@ -1038,6 +1045,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { mUseMeteredFirewallChains = Flags.useMeteredFirewallChains(); mUseDifferentDelaysForBackgroundChain = Flags.useDifferentDelaysForBackgroundChain(); + mNeverApplyRulesToCoreUids = Flags.neverApplyRulesToCoreUids(); synchronized (mUidRulesFirstLock) { synchronized (mNetworkPoliciesSecondLock) { @@ -4088,6 +4096,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { + mUseMeteredFirewallChains); fout.println(Flags.FLAG_USE_DIFFERENT_DELAYS_FOR_BACKGROUND_CHAIN + ": " + mUseDifferentDelaysForBackgroundChain); + fout.println(Flags.FLAG_NEVER_APPLY_RULES_TO_CORE_UIDS + ": " + + mNeverApplyRulesToCoreUids); fout.println(); fout.println("mRestrictBackgroundLowPowerMode: " + mRestrictBackgroundLowPowerMode); @@ -4878,6 +4888,12 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { int[] idleUids = mUsageStats.getIdleUidsForUser(user.id); for (int uid : idleUids) { if (!mPowerSaveTempWhitelistAppIds.get(UserHandle.getAppId(uid), false)) { + if (mNeverApplyRulesToCoreUids && !isUidValidForRulesUL(uid)) { + // This check is needed to keep mUidFirewallStandbyRules free of any + // such uids. Doing this keeps it in sync with the actual rules applied + // in the underlying connectivity stack. + continue; + } // quick check: if this uid doesn't have INTERNET permission, it // doesn't have network access anyway, so it is a waste to mess // with it here. @@ -5180,6 +5196,11 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { @GuardedBy("mUidRulesFirstLock") private boolean isUidValidForAllowlistRulesUL(int uid) { + return isUidValidForRulesUL(uid); + } + + @GuardedBy("mUidRulesFirstLock") + private boolean isUidValidForRulesUL(int uid) { return UserHandle.isApp(uid) && hasInternetPermissionUL(uid); } @@ -6194,41 +6215,33 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } } - private void addSdkSandboxUidsIfNeeded(SparseIntArray uidRules) { - final int size = uidRules.size(); - final SparseIntArray sdkSandboxUids = new SparseIntArray(); - for (int index = 0; index < size; index++) { - final int uid = uidRules.keyAt(index); - final int rule = uidRules.valueAt(index); - if (Process.isApplicationUid(uid)) { - sdkSandboxUids.put(Process.toSdkSandboxUid(uid), rule); - } - } - - for (int index = 0; index < sdkSandboxUids.size(); index++) { - final int uid = sdkSandboxUids.keyAt(index); - final int rule = sdkSandboxUids.valueAt(index); - uidRules.put(uid, rule); - } - } - /** * Set uid rules on a particular firewall chain. This is going to synchronize the rules given * here to netd. It will clean up dead rules and make sure the target chain only contains rules * specified here. */ + @GuardedBy("mUidRulesFirstLock") private void setUidFirewallRulesUL(int chain, SparseIntArray uidRules) { - addSdkSandboxUidsIfNeeded(uidRules); try { int size = uidRules.size(); - int[] uids = new int[size]; - int[] rules = new int[size]; + final IntArray uids = new IntArray(size); + final IntArray rules = new IntArray(size); for(int index = size - 1; index >= 0; --index) { - uids[index] = uidRules.keyAt(index); - rules[index] = uidRules.valueAt(index); + final int uid = uidRules.keyAt(index); + if (mNeverApplyRulesToCoreUids && !isUidValidForRulesUL(uid)) { + continue; + } + uids.add(uid); + rules.add(uidRules.valueAt(index)); + if (Process.isApplicationUid(uid)) { + uids.add(Process.toSdkSandboxUid(uid)); + rules.add(uidRules.valueAt(index)); + } } - mNetworkManager.setFirewallUidRules(chain, uids, rules); - mLogger.firewallRulesChanged(chain, uids, rules); + final int[] uidArray = uids.toArray(); + final int[] ruleArray = rules.toArray(); + mNetworkManager.setFirewallUidRules(chain, uidArray, ruleArray); + mLogger.firewallRulesChanged(chain, uidArray, ruleArray); } catch (IllegalStateException e) { Log.wtf(TAG, "problem setting firewall uid rules", e); } catch (RemoteException e) { @@ -6241,6 +6254,9 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { */ @GuardedBy("mUidRulesFirstLock") private void setUidFirewallRuleUL(int chain, int uid, int rule) { + if (mNeverApplyRulesToCoreUids && !isUidValidForRulesUL(uid)) { + return; + } if (Trace.isTagEnabled(Trace.TRACE_TAG_NETWORK)) { Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "setUidFirewallRuleUL: " + chain + "/" + uid + "/" + rule); @@ -6249,8 +6265,6 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { if (chain == FIREWALL_CHAIN_STANDBY) { mUidFirewallStandbyRules.put(uid, rule); } - // Note that we do not need keep a separate cache of uid rules for chains that we do - // not call #setUidFirewallRulesUL for. try { mNetworkManager.setFirewallUidRule(chain, uid, rule); @@ -6295,6 +6309,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { * Resets all firewall rules associated with an UID. */ private void resetUidFirewallRules(int uid) { + // Resetting rules for uids with isUidValidForRulesUL = false should be OK as no rules + // should be previously set and the downstream code will skip no-op changes. try { mNetworkManager.setFirewallUidRule(FIREWALL_CHAIN_DOZABLE, uid, FIREWALL_RULE_DEFAULT); diff --git a/services/core/java/com/android/server/net/flags.aconfig b/services/core/java/com/android/server/net/flags.aconfig index 586baf022897..7f04e665567e 100644 --- a/services/core/java/com/android/server/net/flags.aconfig +++ b/services/core/java/com/android/server/net/flags.aconfig @@ -27,3 +27,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "never_apply_rules_to_core_uids" + namespace: "backstage_power" + description: "Removes all rule bookkeeping and evaluation logic for core uids and uids without the internet permission" + bug: "356956588" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java index a44e55344fe3..66e61c076030 100644 --- a/services/core/java/com/android/server/notification/ConditionProviders.java +++ b/services/core/java/com/android/server/notification/ConditionProviders.java @@ -16,9 +16,6 @@ package com.android.server.notification; -import static android.service.notification.Condition.STATE_TRUE; -import static android.service.notification.ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI; - import android.app.INotificationManager; import android.app.NotificationManager; import android.content.ComponentName; @@ -322,20 +319,7 @@ public class ConditionProviders extends ManagedServices { final Condition c = conditions[i]; final ConditionRecord r = getRecordLocked(c.id, info.component, true /*create*/); r.info = info; - if (android.app.Flags.modesUi()) { - // if user turned on the mode, ignore the update unless the app also wants the - // mode on. this will update the origin of the mode and let the owner turn it - // off when the context ends - if (r.condition != null && r.condition.source == ORIGIN_USER_IN_SYSTEMUI) { - if (r.condition.state == STATE_TRUE && c.state == STATE_TRUE) { - r.condition = c; - } - } else { - r.condition = c; - } - } else { - r.condition = c; - } + r.condition = c; } } final int N = conditions.length; diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java index b4459cb2fe92..981891669e7c 100644 --- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java +++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java @@ -1519,7 +1519,14 @@ public final class NotificationAttentionHelper { @Override public void setLastNotificationUpdateTimeMs(NotificationRecord record, long timestampMillis) { - super.setLastNotificationUpdateTimeMs(record, timestampMillis); + if (Flags.politeNotificationsAttnUpdate()) { + // Set last update per package/channel only for exempt notifications + if (isAvalancheExempted(record)) { + super.setLastNotificationUpdateTimeMs(record, timestampMillis); + } + } else { + super.setLastNotificationUpdateTimeMs(record, timestampMillis); + } mLastNotificationTimestamp = timestampMillis; mAppStrategy.setLastNotificationUpdateTimeMs(record, timestampMillis); } diff --git a/services/core/java/com/android/server/notification/ZenModeConditions.java b/services/core/java/com/android/server/notification/ZenModeConditions.java index 268d835e704c..d495ef5ce108 100644 --- a/services/core/java/com/android/server/notification/ZenModeConditions.java +++ b/services/core/java/com/android/server/notification/ZenModeConditions.java @@ -82,7 +82,7 @@ public class ZenModeConditions implements ConditionProviders.Callback { for (ZenRule automaticRule : config.automaticRules.values()) { if (automaticRule.component != null) { evaluateRule(automaticRule, current, trigger, processSubscriptions, false); - updateSnoozing(automaticRule); + automaticRule.reconsiderConditionOverride(); } } @@ -187,13 +187,4 @@ public class ZenModeConditions implements ConditionProviders.Callback { + rule.conditionId); } } - - private boolean updateSnoozing(ZenRule rule) { - if (rule != null && rule.snoozing && !rule.isTrueOrUnknown()) { - rule.snoozing = false; - if (DEBUG) Log.d(TAG, "Snoozing reset for " + rule.conditionId); - return true; - } - return false; - } } diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index 8c280edf03c0..0f50260a55d2 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -37,6 +37,8 @@ import static android.service.notification.ZenModeConfig.ORIGIN_SYSTEM; import static android.service.notification.ZenModeConfig.ORIGIN_UNKNOWN; import static android.service.notification.ZenModeConfig.ORIGIN_USER_IN_APP; import static android.service.notification.ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI; +import static android.service.notification.ZenModeConfig.ZenRule.OVERRIDE_ACTIVATE; +import static android.service.notification.ZenModeConfig.ZenRule.OVERRIDE_DEACTIVATE; import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE; import static com.android.internal.util.Preconditions.checkArgument; @@ -643,10 +645,10 @@ public class ZenModeHelper { if ((rule.userModifiedFields & AutomaticZenRule.FIELD_INTERRUPTION_FILTER) == 0) { rule.zenMode = zenMode; } - rule.snoozing = false; rule.condition = new Condition(rule.conditionId, mContext.getString(R.string.zen_mode_implicit_activated), STATE_TRUE); + rule.resetConditionOverride(); setConfigLocked(newConfig, /* triggeringComponent= */ null, ORIGIN_APP, "applyGlobalZenModeAsImplicitZenRule", callingUid); @@ -867,8 +869,8 @@ public class ZenModeHelper { ZenRule deletedRule = ruleToRemove.copy(); deletedRule.deletionInstant = Instant.now(mClock); // If the rule is restored it shouldn't be active (or snoozed). - deletedRule.snoozing = false; deletedRule.condition = null; + deletedRule.resetConditionOverride(); // Overwrites a previously-deleted rule with the same conditionId, but that's okay. config.deletedRules.put(deletedKey, deletedRule); } @@ -885,7 +887,12 @@ public class ZenModeHelper { if (rule == null || !canManageAutomaticZenRule(rule)) { return Condition.STATE_UNKNOWN; } - return rule.condition != null ? rule.condition.state : STATE_FALSE; + if (Flags.modesApi() && Flags.modesUi()) { + return rule.isAutomaticActive() ? STATE_TRUE : STATE_FALSE; + } else { + // Buggy, does not consider snoozing! + return rule.condition != null ? rule.condition.state : STATE_FALSE; + } } } @@ -943,12 +950,43 @@ public class ZenModeHelper { } for (ZenRule rule : rules) { - rule.condition = condition; - updateSnoozing(rule); + applyConditionAndReconsiderOverride(rule, condition, origin); setConfigLocked(config, rule.component, origin, "conditionChanged", callingUid); } } + private static void applyConditionAndReconsiderOverride(ZenRule rule, Condition condition, + int origin) { + if (Flags.modesApi() && Flags.modesUi()) { + if (origin == ORIGIN_USER_IN_SYSTEMUI && condition != null + && condition.source == SOURCE_USER_ACTION) { + // Apply as override, instead of actual condition. + // If the new override is the reverse of a previous (still active) override, try + // removing the previous override, as long as the resulting state, based on the + // previous owner-provided condition, is the desired one (active or inactive). + // This allows the rule owner to resume controlling the rule after + // snoozing-unsnoozing or activating-stopping. + if (condition.state == STATE_TRUE) { + rule.resetConditionOverride(); + if (!rule.isAutomaticActive()) { + rule.setConditionOverride(OVERRIDE_ACTIVATE); + } + } else if (condition.state == STATE_FALSE) { + rule.resetConditionOverride(); + if (rule.isAutomaticActive()) { + rule.setConditionOverride(OVERRIDE_DEACTIVATE); + } + } + } else { + rule.condition = condition; + rule.reconsiderConditionOverride(); + } + } else { + rule.condition = condition; + rule.reconsiderConditionOverride(); + } + } + private static List<ZenRule> findMatchingRules(ZenModeConfig config, Uri id, Condition condition) { List<ZenRule> matchingRules = new ArrayList<>(); @@ -971,15 +1009,6 @@ public class ZenModeHelper { return true; } - private boolean updateSnoozing(ZenRule rule) { - if (rule != null && rule.snoozing && !rule.isTrueOrUnknown()) { - rule.snoozing = false; - if (DEBUG) Log.d(TAG, "Snoozing reset for " + rule.conditionId); - return true; - } - return false; - } - public int getCurrentInstanceCount(ComponentName cn) { if (cn == null) { return 0; @@ -1181,7 +1210,7 @@ public class ZenModeHelper { if (rule.enabled != azr.isEnabled()) { rule.enabled = azr.isEnabled(); - rule.snoozing = false; + rule.resetConditionOverride(); modified = true; } if (!Objects.equals(rule.configurationActivity, azr.getConfigurationActivity())) { @@ -1271,7 +1300,7 @@ public class ZenModeHelper { return modified; } else { if (rule.enabled != azr.isEnabled()) { - rule.snoozing = false; + rule.resetConditionOverride(); } rule.name = azr.getName(); rule.condition = null; @@ -1573,18 +1602,16 @@ public class ZenModeHelper { // For API calls (different origin) keep old behavior of snoozing all rules. for (ZenRule automaticRule : newConfig.automaticRules.values()) { if (automaticRule.isAutomaticActive()) { - automaticRule.snoozing = true; + automaticRule.setConditionOverride(OVERRIDE_DEACTIVATE); } } } } else { if (zenMode == Global.ZEN_MODE_OFF) { newConfig.manualRule = null; - // User deactivation of DND means just turning off the manual DND rule. - // For API calls (different origin) keep old behavior of snoozing all rules. for (ZenRule automaticRule : newConfig.automaticRules.values()) { if (automaticRule.isAutomaticActive()) { - automaticRule.snoozing = true; + automaticRule.setConditionOverride(OVERRIDE_DEACTIVATE); } } @@ -1626,13 +1653,11 @@ public class ZenModeHelper { void dump(ProtoOutputStream proto) { proto.write(ZenModeProto.ZEN_MODE, mZenMode); synchronized (mConfigLock) { - if (mConfig.manualRule != null) { + if (mConfig.isManualActive()) { mConfig.manualRule.dumpDebug(proto, ZenModeProto.ENABLED_ACTIVE_CONDITIONS); } for (ZenRule rule : mConfig.automaticRules.values()) { - if (rule.enabled && rule.condition != null - && rule.condition.state == STATE_TRUE - && !rule.snoozing) { + if (rule.isAutomaticActive()) { rule.dumpDebug(proto, ZenModeProto.ENABLED_ACTIVE_CONDITIONS); } } @@ -1695,8 +1720,8 @@ public class ZenModeHelper { for (ZenRule automaticRule : config.automaticRules.values()) { if (forRestore) { // don't restore transient state from restored automatic rules - automaticRule.snoozing = false; automaticRule.condition = null; + automaticRule.resetConditionOverride(); automaticRule.creationTime = time; } diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java index 46585a50ea36..6303ecd53dbb 100644 --- a/services/core/java/com/android/server/om/OverlayManagerService.java +++ b/services/core/java/com/android/server/om/OverlayManagerService.java @@ -80,6 +80,7 @@ import android.util.EventLog; import android.util.Slog; import android.util.SparseArray; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.KeepForWeakReference; import com.android.internal.content.PackageMonitor; import com.android.internal.content.om.OverlayConfig; @@ -1180,6 +1181,7 @@ public final class OverlayManagerService extends SystemService { // intent, querying the PackageManagerService for the actual current // state may lead to contradictions within OMS. Better then to lag // behind until all pending intents have been processed. + @GuardedBy("itself") private final ArrayMap<String, PackageStateUsers> mCache = new ArrayMap<>(); private final ArraySet<Integer> mInitializedUsers = new ArraySet<>(); @@ -1207,10 +1209,12 @@ public final class OverlayManagerService extends SystemService { } final ArrayMap<String, PackageState> userPackages = new ArrayMap<>(); - for (int i = 0, n = mCache.size(); i < n; i++) { - final PackageStateUsers pkg = mCache.valueAt(i); - if (pkg.mInstalledUsers.contains(userId)) { - userPackages.put(mCache.keyAt(i), pkg.mPackageState); + synchronized (mCache) { + for (int i = 0, n = mCache.size(); i < n; i++) { + final PackageStateUsers pkg = mCache.valueAt(i); + if (pkg.mInstalledUsers.contains(userId)) { + userPackages.put(mCache.keyAt(i), pkg.mPackageState); + } } } return userPackages; @@ -1220,7 +1224,11 @@ public final class OverlayManagerService extends SystemService { @Nullable public PackageState getPackageStateForUser(@NonNull final String packageName, final int userId) { - final PackageStateUsers pkg = mCache.get(packageName); + final PackageStateUsers pkg; + + synchronized (mCache) { + pkg = mCache.get(packageName); + } if (pkg != null && pkg.mInstalledUsers.contains(userId)) { return pkg.mPackageState; } @@ -1251,12 +1259,15 @@ public final class OverlayManagerService extends SystemService { @NonNull private PackageState addPackageUser(@NonNull final PackageState pkg, final int user) { - PackageStateUsers pkgUsers = mCache.get(pkg.getPackageName()); - if (pkgUsers == null) { - pkgUsers = new PackageStateUsers(pkg); - mCache.put(pkg.getPackageName(), pkgUsers); - } else { - pkgUsers.mPackageState = pkg; + PackageStateUsers pkgUsers; + synchronized (mCache) { + pkgUsers = mCache.get(pkg.getPackageName()); + if (pkgUsers == null) { + pkgUsers = new PackageStateUsers(pkg); + mCache.put(pkg.getPackageName(), pkgUsers); + } else { + pkgUsers.mPackageState = pkg; + } } pkgUsers.mInstalledUsers.add(user); return pkgUsers.mPackageState; @@ -1265,18 +1276,24 @@ public final class OverlayManagerService extends SystemService { @NonNull private void removePackageUser(@NonNull final String packageName, final int user) { - final PackageStateUsers pkgUsers = mCache.get(packageName); - if (pkgUsers == null) { - return; + // synchronize should include the call to the other removePackageUser() method so that + // the access and modification happen under the same lock. + synchronized (mCache) { + final PackageStateUsers pkgUsers = mCache.get(packageName); + if (pkgUsers == null) { + return; + } + removePackageUser(pkgUsers, user); } - removePackageUser(pkgUsers, user); } @NonNull private void removePackageUser(@NonNull final PackageStateUsers pkg, final int user) { pkg.mInstalledUsers.remove(user); if (pkg.mInstalledUsers.isEmpty()) { - mCache.remove(pkg.mPackageState.getPackageName()); + synchronized (mCache) { + mCache.remove(pkg.mPackageState.getPackageName()); + } } } @@ -1386,8 +1403,10 @@ public final class OverlayManagerService extends SystemService { public void forgetAllPackageInfos(final int userId) { // Iterate in reverse order since removing the package in all users will remove the // package from the cache. - for (int i = mCache.size() - 1; i >= 0; i--) { - removePackageUser(mCache.valueAt(i), userId); + synchronized (mCache) { + for (int i = mCache.size() - 1; i >= 0; i--) { + removePackageUser(mCache.valueAt(i), userId); + } } } @@ -1405,22 +1424,23 @@ public final class OverlayManagerService extends SystemService { public void dump(@NonNull final PrintWriter pw, @NonNull DumpState dumpState) { pw.println("AndroidPackage cache"); + synchronized (mCache) { + if (!dumpState.isVerbose()) { + pw.println(TAB1 + mCache.size() + " package(s)"); + return; + } - if (!dumpState.isVerbose()) { - pw.println(TAB1 + mCache.size() + " package(s)"); - return; - } - - if (mCache.size() == 0) { - pw.println(TAB1 + "<empty>"); - return; - } + if (mCache.size() == 0) { + pw.println(TAB1 + "<empty>"); + return; + } - for (int i = 0, n = mCache.size(); i < n; i++) { - final String packageName = mCache.keyAt(i); - final PackageStateUsers pkg = mCache.valueAt(i); - pw.print(TAB1 + packageName + ": " + pkg.mPackageState + " users="); - pw.println(TextUtils.join(", ", pkg.mInstalledUsers)); + for (int i = 0, n = mCache.size(); i < n; i++) { + final String packageName = mCache.keyAt(i); + final PackageStateUsers pkg = mCache.valueAt(i); + pw.print(TAB1 + packageName + ": " + pkg.mPackageState + " users="); + pw.println(TextUtils.join(", ", pkg.mInstalledUsers)); + } } } } diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 98e3e24c36b9..22b4d5def8f4 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -686,6 +686,9 @@ final class InstallPackageHelper { (installFlags & PackageManager.INSTALL_INSTANT_APP) != 0; final boolean fullApp = (installFlags & PackageManager.INSTALL_FULL_APP) != 0; + final boolean isPackageDeviceAdmin = mPm.isPackageDeviceAdmin(packageName, userId); + final boolean isProtectedPackage = mPm.mProtectedPackages != null + && mPm.mProtectedPackages.isPackageStateProtected(userId, packageName); // writer synchronized (mPm.mLock) { @@ -694,7 +697,8 @@ final class InstallPackageHelper { if (pkgSetting == null || pkgSetting.getPkg() == null) { return Pair.create(PackageManager.INSTALL_FAILED_INVALID_URI, intentSender); } - if (instantApp && (pkgSetting.isSystem() || pkgSetting.isUpdatedSystemApp())) { + if (instantApp && (pkgSetting.isSystem() || pkgSetting.isUpdatedSystemApp() + || isPackageDeviceAdmin || isProtectedPackage)) { return Pair.create(PackageManager.INSTALL_FAILED_INVALID_URI, intentSender); } if (!snapshot.canViewInstantApps(callingUid, UserHandle.getUserId(callingUid))) { diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 20859da4dd56..2124ff6b07e0 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -2034,6 +2034,10 @@ public class PackageManagerService implements PackageSender, TestUtilityService // CHECKSTYLE:ON IndentationCheck t.traceEnd(); + t.traceBegin("get system config"); + SystemConfig systemConfig = injector.getSystemConfig(); + t.traceEnd(); + t.traceBegin("addSharedUsers"); mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID, ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); @@ -2053,6 +2057,13 @@ public class PackageManagerService implements PackageSender, TestUtilityService ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); mSettings.addSharedUserLPw("android.uid.uwb", UWB_UID, ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + final ArrayMap<String, Integer> oemDefinedUids = systemConfig.getOemDefinedUids(); + final int numOemDefinedUids = oemDefinedUids.size(); + for (int i = 0; i < numOemDefinedUids; i++) { + mSettings.addOemSharedUserLPw(oemDefinedUids.keyAt(i), oemDefinedUids.valueAt(i), + ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + } + t.traceEnd(); String separateProcesses = SystemProperties.get("debug.separate_processes"); @@ -2084,10 +2095,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService mContext.getSystemService(DisplayManager.class) .getDisplay(Display.DEFAULT_DISPLAY).getMetrics(mMetrics); - t.traceBegin("get system config"); - SystemConfig systemConfig = injector.getSystemConfig(); mAvailableFeatures = systemConfig.getAvailableFeatures(); - t.traceEnd(); mProtectedPackages = new ProtectedPackages(mContext); diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 55280b4cdc5b..4c9be21f1386 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -977,6 +977,21 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile return null; } + SharedUserSetting addOemSharedUserLPw(String name, int uid, int pkgFlags, int pkgPrivateFlags) { + if (!name.startsWith("android.uid")) { + PackageManagerService.reportSettingsProblem(Log.ERROR, + "Failed to add oem defined shared user because of invalid name: " + name); + return null; + } + // OEM defined uids must be in the OEM reserved range + if (uid < 2900 || uid > 2999) { + PackageManagerService.reportSettingsProblem(Log.ERROR, + "Failed to add oem defined shared user because of invalid uid: " + uid); + return null; + } + return addSharedUserLPw(name, uid, pkgFlags, pkgPrivateFlags); + } + SharedUserSetting addSharedUserLPw(String name, int uid, int pkgFlags, int pkgPrivateFlags) { SharedUserSetting s = mSharedUsers.get(name); if (s != null) { diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING index c40608d12a0e..c95d88e8c697 100644 --- a/services/core/java/com/android/server/pm/TEST_MAPPING +++ b/services/core/java/com/android/server/pm/TEST_MAPPING @@ -163,6 +163,22 @@ }, { "name": "CtsUpdateOwnershipEnforcementTestCases" + }, + { + "name": "CtsPackageInstallerCUJTestCases", + "file_patterns": [ + "core/java/.*Install.*", + "services/core/.*Install.*", + "services/core/java/com/android/server/pm/.*" + ], + "options":[ + { + "exclude-annotation":"androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation":"org.junit.Ignore" + } + ] } ], "imports": [ diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 829ee27287c2..2b639fa43c69 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -379,6 +379,10 @@ public class UserManagerService extends IUserManager.Stub { /** Count down latch to wait while boot user is not set.*/ private final CountDownLatch mBootUserLatch = new CountDownLatch(1); + + /** Current boot phase. */ + private @SystemService.BootPhase int mCurrentBootPhase; + /** * Internal non-parcelable wrapper for UserInfo that is not exposed to other system apps. */ @@ -968,6 +972,7 @@ public class UserManagerService extends IUserManager.Stub { @Override public void onBootPhase(int phase) { + mUms.mCurrentBootPhase = phase; if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) { mUms.cleanupPartialUsers(); @@ -2105,6 +2110,10 @@ public class UserManagerService extends IUserManager.Stub { @Override public void setUserAdmin(@UserIdInt int userId) { checkManageUserAndAcrossUsersFullPermission("set user admin"); + if (Flags.unicornModeRefactoringForHsumReadOnly()) { + checkAdminStatusChangeAllowed(userId); + } + mUserJourneyLogger.logUserJourneyBegin(userId, USER_JOURNEY_GRANT_ADMIN); UserData user; synchronized (mPackagesLock) { @@ -2133,6 +2142,10 @@ public class UserManagerService extends IUserManager.Stub { @Override public void revokeUserAdmin(@UserIdInt int userId) { checkManageUserAndAcrossUsersFullPermission("revoke admin privileges"); + if (Flags.unicornModeRefactoringForHsumReadOnly()) { + checkAdminStatusChangeAllowed(userId); + } + mUserJourneyLogger.logUserJourneyBegin(userId, USER_JOURNEY_REVOKE_ADMIN); UserData user; synchronized (mPackagesLock) { @@ -4065,6 +4078,26 @@ public class UserManagerService extends IUserManager.Stub { } } + /** + * Checks if changing the admin status of a target user is restricted + * due to the DISALLOW_GRANT_ADMIN restriction. If either the calling + * user or the target user has this restriction, a SecurityException + * is thrown. + * + * @param targetUser The user ID of the user whose admin status is being + * considered for change. + * @throws SecurityException if the admin status change is restricted due + * to the DISALLOW_GRANT_ADMIN restriction. + */ + private void checkAdminStatusChangeAllowed(int targetUser) { + if (hasUserRestriction(UserManager.DISALLOW_GRANT_ADMIN, UserHandle.getCallingUserId()) + || hasUserRestriction(UserManager.DISALLOW_GRANT_ADMIN, targetUser)) { + throw new SecurityException( + "Admin status change is restricted. The DISALLOW_GRANT_ADMIN " + + "restriction is applied either on the current or the target user."); + } + } + @GuardedBy({"mPackagesLock"}) private void writeBitmapLP(UserInfo info, Bitmap bitmap) { try { @@ -5443,6 +5476,13 @@ public class UserManagerService extends IUserManager.Stub { enforceUserRestriction(restriction, UserHandle.getCallingUserId(), "Cannot add user"); + if (Flags.unicornModeRefactoringForHsumReadOnly()) { + if ((flags & UserInfo.FLAG_ADMIN) != 0) { + enforceUserRestriction(UserManager.DISALLOW_GRANT_ADMIN, + UserHandle.getCallingUserId(), "Cannot create ADMIN user"); + } + } + return createUserInternalUnchecked(name, userType, flags, parentId, /* preCreate= */ false, disallowedPackages, /* token= */ null); } @@ -6169,6 +6209,11 @@ public class UserManagerService extends IUserManager.Stub { Slog.w(LOG_TAG, "Cannot remove user. " + restriction + " is enabled."); return false; } + if (mCurrentBootPhase < SystemService.PHASE_ACTIVITY_MANAGER_READY) { + Slog.w(LOG_TAG, "Cannot remove user, removeUser is called too early during boot. " + + "ActivityManager is not ready yet."); + return false; + } return removeUserWithProfilesUnchecked(userId); } diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java index b28da55b5196..1a2a196fe4e8 100644 --- a/services/core/java/com/android/server/power/Notifier.java +++ b/services/core/java/com/android/server/power/Notifier.java @@ -360,7 +360,13 @@ public class Notifier { IWakeLockCallback callback, int newFlags, String newTag, String newPackageName, int newOwnerUid, int newOwnerPid, WorkSource newWorkSource, String newHistoryTag, IWakeLockCallback newCallback) { - + // Todo(b/359154665): We do this because the newWorkSource can potentially be updated + // before the request is processed on the notifier thread. This would generally happen is + // the Worksource's set method is called, which as of this comment happens only in + // PowerManager#setWorksource and WifiManager#WifiLock#setWorksource. Both these places + // need to be updated and the WorkSource#set should be deprecated to avoid falling into + // such traps + newWorkSource = (newWorkSource == null) ? null : new WorkSource(newWorkSource); final int monitorType = getBatteryStatsWakeLockMonitorType(flags); final int newMonitorType = getBatteryStatsWakeLockMonitorType(newFlags); if (workSource != null && newWorkSource != null diff --git a/services/core/java/com/android/server/powerstats/PowerStatsService.java b/services/core/java/com/android/server/powerstats/PowerStatsService.java index 8f99d2836c63..1ca267e99c95 100644 --- a/services/core/java/com/android/server/powerstats/PowerStatsService.java +++ b/services/core/java/com/android/server/powerstats/PowerStatsService.java @@ -62,6 +62,7 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import java.util.Locale; +import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; @@ -207,19 +208,28 @@ public class PowerStatsService extends SystemService { private final IBinder mService = new IPowerStatsService.Stub() { @Override - public void getSupportedPowerMonitors(ResultReceiver resultReceiver) { + public void getSupportedPowerMonitors(@NonNull ResultReceiver resultReceiver) { + if (Flags.verifyNonNullArguments()) { + Objects.requireNonNull(resultReceiver); + } getHandler().post(() -> getSupportedPowerMonitorsImpl(resultReceiver)); } @Override - public void getPowerMonitorReadings(int[] powerMonitorIds, ResultReceiver resultReceiver) { + public void getPowerMonitorReadings(@NonNull int[] powerMonitorIds, + @NonNull ResultReceiver resultReceiver) { + if (Flags.verifyNonNullArguments()) { + Objects.requireNonNull(powerMonitorIds); + Objects.requireNonNull(resultReceiver); + } int callingUid = Binder.getCallingUid(); getHandler().post(() -> getPowerMonitorReadingsImpl(powerMonitorIds, resultReceiver, callingUid)); } @Override - protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, + @Nullable String[] args) { if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; if (mPowerStatsLogger == null) { @@ -263,6 +273,11 @@ public class PowerStatsService extends SystemService { } }; + @VisibleForTesting + IPowerStatsService getIPowerStatsServiceForTest() { + return (IPowerStatsService) mService; + } + private class DeviceConfigListener implements DeviceConfig.OnPropertiesChangedListener { public Executor mExecutor = new HandlerExecutor(getHandler()); diff --git a/services/core/java/com/android/server/powerstats/flags.aconfig b/services/core/java/com/android/server/powerstats/flags.aconfig index 0a4a7510aab8..29ad7dc2b18a 100644 --- a/services/core/java/com/android/server/powerstats/flags.aconfig +++ b/services/core/java/com/android/server/powerstats/flags.aconfig @@ -10,4 +10,15 @@ flag { metadata { purpose: PURPOSE_BUGFIX } +} + +flag { + name: "verify_non_null_arguments" + namespace: "backstage_power" + description: "Verify arguments passed are non-null" + bug: "356731520" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } }
\ No newline at end of file diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java index a4a29a02f362..2faa68a9948f 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java @@ -20,6 +20,8 @@ import android.annotation.Nullable; import android.app.ITransientNotificationCallback; import android.content.ComponentName; import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback; +import android.inputmethodservice.InputMethodService.BackDispositionMode; +import android.inputmethodservice.InputMethodService.ImeWindowVisibility; import android.os.Bundle; import android.os.IBinder; import android.os.UserHandle; @@ -54,13 +56,12 @@ public interface StatusBarManagerInternal { * Used by InputMethodManagerService to notify the IME status. * * @param displayId The display to which the IME is bound to. - * @param vis Bit flags about the IME visibility. - * (e.g. {@link android.inputmethodservice.InputMethodService#IME_ACTIVE}) - * @param backDisposition Bit flags about the IME back disposition. - * (e.g. {@link android.inputmethodservice.InputMethodService#BACK_DISPOSITION_DEFAULT}) + * @param vis The IME visibility. + * @param backDisposition The IME back disposition. * @param showImeSwitcher {@code true} when the IME switcher button should be shown. */ - void setImeWindowStatus(int displayId, int vis, int backDisposition, boolean showImeSwitcher); + void setImeWindowStatus(int displayId, @ImeWindowVisibility int vis, + @BackDispositionMode int backDisposition, boolean showImeSwitcher); /** * See {@link android.app.StatusBarManager#setIcon(String, int, int, String)}. diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index c3601b3c3090..7d812ee8c5cd 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -26,6 +26,10 @@ import static android.app.StatusBarManager.NAV_BAR_MODE_KIDS; import static android.app.StatusBarManager.NavBarMode; import static android.app.StatusBarManager.SessionFlags; import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.inputmethodservice.InputMethodService.BACK_DISPOSITION_DEFAULT; +import static android.os.UserHandle.USER_SYSTEM; +import static android.os.UserHandle.getCallingUserId; +import static android.os.UserManager.isVisibleBackgroundUsersEnabled; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.ViewRootImpl.CLIENT_TRANSIENT; import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY; @@ -60,6 +64,8 @@ import android.hardware.biometrics.PromptInfo; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManager.DisplayListener; import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback; +import android.inputmethodservice.InputMethodService.BackDispositionMode; +import android.inputmethodservice.InputMethodService.ImeWindowVisibility; import android.media.INearbyMediaDevicesProvider; import android.media.MediaRoute2Info; import android.net.Uri; @@ -113,6 +119,7 @@ import com.android.server.LocalServices; import com.android.server.UiThread; import com.android.server.inputmethod.InputMethodManagerInternal; import com.android.server.notification.NotificationDelegate; +import com.android.server.pm.UserManagerService; import com.android.server.policy.GlobalActionsProvider; import com.android.server.power.ShutdownCheckPoints; import com.android.server.power.ShutdownThread; @@ -145,9 +152,9 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D /** * In apps targeting {@link android.os.Build.VERSION_CODES#TIRAMISU} or higher, calling - * {@link android.service.quicksettings.TileService#requestListeningState} will check that the - * calling package (uid) and the package of the target {@link android.content.ComponentName} - * match. It'll also make sure that the context used can take actions on behalf of the current + * {@link android.service.quicksettings.TileService#requestListeningState} will check that the + * calling package (uid) and the package of the target {@link android.content.ComponentName} + * match. It'll also make sure that the context used can take actions on behalf of the current * user. */ @ChangeId @@ -197,6 +204,9 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D private IOverlayManager mOverlayManager; + private final boolean mVisibleBackgroundUsersEnabled; + private final UserManagerService mUserManager; + private class DeathRecipient implements IBinder.DeathRecipient { public void binderDied() { mBar.asBinder().unlinkToDeath(this,0); @@ -297,6 +307,9 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D mTileRequestTracker = new TileRequestTracker(mContext); mSessionMonitor = new SessionMonitor(mContext); + + mVisibleBackgroundUsersEnabled = isVisibleBackgroundUsersEnabled(); + mUserManager = UserManagerService.getInstance(); } /** @@ -534,8 +547,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D } @Override - public void setImeWindowStatus(int displayId, int vis, int backDisposition, - boolean showImeSwitcher) { + public void setImeWindowStatus(int displayId, @ImeWindowVisibility int vis, + @BackDispositionMode int backDisposition, boolean showImeSwitcher) { StatusBarManagerService.this.setImeWindowStatus(displayId, vis, backDisposition, showImeSwitcher); } @@ -911,6 +924,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D @Override public void expandNotificationsPanel() { enforceExpandStatusBar(); + enforceValidCallingUser(); if (isDisable2FlagSet(DISABLE2_NOTIFICATION_SHADE)) { return; @@ -926,6 +940,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D @Override public void collapsePanels() { + enforceValidCallingUser(); + if (!checkCanCollapseStatusBar("collapsePanels")) { return; } @@ -940,6 +956,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D @Override public void togglePanel() { + enforceValidCallingUser(); + if (!checkCanCollapseStatusBar("togglePanel")) { return; } @@ -959,6 +977,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D @Override public void expandSettingsPanel(String subPanel) { enforceExpandStatusBar(); + enforceValidCallingUser(); if (mBar != null) { try { @@ -973,6 +992,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D addQsTileToFrontOrEnd(component, false); } else { enforceStatusBarOrShell(); + enforceValidCallingUser(); if (mBar != null) { try { @@ -985,6 +1005,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D private void addQsTileToFrontOrEnd(ComponentName tile, boolean end) { enforceStatusBarOrShell(); + enforceValidCallingUser(); if (mBar != null) { try { @@ -996,6 +1017,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D public void remTile(ComponentName component) { enforceStatusBarOrShell(); + enforceValidCallingUser(); if (mBar != null) { try { @@ -1018,6 +1040,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D public void clickTile(ComponentName component) { enforceStatusBarOrShell(); + enforceValidCallingUser(); if (mBar != null) { try { @@ -1029,6 +1052,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D @Override public void handleSystemKey(KeyEvent key) throws RemoteException { + enforceValidCallingUser(); + if (!checkCanCollapseStatusBar("handleSystemKey")) { return; } @@ -1053,6 +1078,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D @Override public void showPinningEnterExitToast(boolean entering) throws RemoteException { + enforceValidCallingUser(); + if (mBar != null) { try { mBar.showPinningEnterExitToast(entering); @@ -1063,6 +1090,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D @Override public void showPinningEscapeToast() throws RemoteException { + enforceValidCallingUser(); + if (mBar != null) { try { mBar.showPinningEscapeToast(); @@ -1076,6 +1105,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D int[] sensorIds, boolean credentialAllowed, boolean requireConfirmation, int userId, long operationId, String opPackageName, long requestId) { enforceBiometricDialog(); + enforceValidCallingUser(); + if (mBar != null) { try { mBar.showAuthenticationDialog(promptInfo, receiver, sensorIds, credentialAllowed, @@ -1088,6 +1119,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D @Override public void onBiometricAuthenticated(@Modality int modality) { enforceBiometricDialog(); + enforceValidCallingUser(); + if (mBar != null) { try { mBar.onBiometricAuthenticated(modality); @@ -1099,6 +1132,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D @Override public void onBiometricHelp(@Modality int modality, String message) { enforceBiometricDialog(); + enforceValidCallingUser(); + if (mBar != null) { try { mBar.onBiometricHelp(modality, message); @@ -1110,6 +1145,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D @Override public void onBiometricError(int modality, int error, int vendorCode) { enforceBiometricDialog(); + enforceValidCallingUser(); + if (mBar != null) { try { mBar.onBiometricError(modality, error, vendorCode); @@ -1121,6 +1158,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D @Override public void hideAuthenticationDialog(long requestId) { enforceBiometricDialog(); + enforceValidCallingUser(); + if (mBar != null) { try { mBar.hideAuthenticationDialog(requestId); @@ -1132,6 +1171,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D @Override public void setBiometicContextListener(IBiometricContextListener listener) { enforceStatusBarService(); + enforceValidCallingUser(); + synchronized (mLock) { mBiometricContextListener = listener; } @@ -1146,6 +1187,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D @Override public void setUdfpsRefreshRateCallback(IUdfpsRefreshRateRequestCallback callback) { enforceStatusBarService(); + enforceValidCallingUser(); + if (mBar != null) { try { mBar.setUdfpsRefreshRateCallback(callback); @@ -1156,6 +1199,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D @Override public void startTracing() { + enforceValidCallingUser(); + if (mBar != null) { try { mBar.startTracing(); @@ -1167,6 +1212,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D @Override public void stopTracing() { + enforceValidCallingUser(); + if (mBar != null) { try { mTracingEnabled = false; @@ -1190,6 +1237,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D @Override public void disableForUser(int what, IBinder token, String pkg, int userId) { enforceStatusBar(); + enforceValidCallingUser(); synchronized (mLock) { disableLocked(DEFAULT_DISPLAY, userId, what, token, pkg, 1); @@ -1293,6 +1341,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D public void setIcon(String slot, String iconPackage, int iconId, int iconLevel, String contentDescription) { enforceStatusBar(); + enforceValidCallingUser(); synchronized (mIcons) { StatusBarIcon icon = new StatusBarIcon(iconPackage, UserHandle.SYSTEM, iconId, @@ -1313,6 +1362,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D @Override public void setIconVisibility(String slot, boolean visibility) { enforceStatusBar(); + enforceValidCallingUser(); synchronized (mIcons) { StatusBarIcon icon = mIcons.get(slot); @@ -1336,6 +1386,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D @Override public void removeIcon(String slot) { enforceStatusBar(); + enforceValidCallingUser(); synchronized (mIcons) { mIcons.remove(slot); @@ -1351,9 +1402,10 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D } @Override - public void setImeWindowStatus(int displayId, final int vis, final int backDisposition, - final boolean showImeSwitcher) { + public void setImeWindowStatus(int displayId, @ImeWindowVisibility final int vis, + @BackDispositionMode final int backDisposition, final boolean showImeSwitcher) { enforceStatusBar(); + enforceValidCallingUser(); if (SPEW) { Slog.d(TAG, "setImeWindowStatus vis=" + vis + " backDisposition=" + backDisposition); @@ -1418,8 +1470,10 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D private String mPackageName = "none"; private int mDisabled1 = 0; private int mDisabled2 = 0; + @ImeWindowVisibility private int mImeWindowVis = 0; - private int mImeBackDisposition = 0; + @BackDispositionMode + private int mImeBackDisposition = BACK_DISPOSITION_DEFAULT; private boolean mShowImeSwitcher = false; private LetterboxDetails[] mLetterboxDetails = new LetterboxDetails[0]; @@ -1462,7 +1516,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D return mDisabled1 == disabled1 && mDisabled2 == disabled2; } - private void setImeWindowState(final int vis, final int backDisposition, + private void setImeWindowState(@ImeWindowVisibility final int vis, + @BackDispositionMode final int backDisposition, final boolean showImeSwitcher) { mImeWindowVis = vis; mImeBackDisposition = backDisposition; @@ -1544,6 +1599,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D @Override public RegisterStatusBarResult registerStatusBar(IStatusBar bar) { enforceStatusBarService(); + enforceValidCallingUser(); Slog.i(TAG, "registerStatusBar bar=" + bar); mBar = bar; @@ -1594,6 +1650,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D @Override public void onPanelRevealed(boolean clearNotificationEffects, int numItems) { enforceStatusBarService(); + enforceValidCallingUser(); + final long identity = Binder.clearCallingIdentity(); try { mNotificationDelegate.onPanelRevealed(clearNotificationEffects, numItems); @@ -1605,6 +1663,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D @Override public void clearNotificationEffects() throws RemoteException { enforceStatusBarService(); + enforceValidCallingUser(); + final long identity = Binder.clearCallingIdentity(); try { mNotificationDelegate.clearEffects(); @@ -1616,6 +1676,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D @Override public void onPanelHidden() throws RemoteException { enforceStatusBarService(); + enforceValidCallingUser(); + final long identity = Binder.clearCallingIdentity(); try { mNotificationDelegate.onPanelHidden(); @@ -1630,6 +1692,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D @Override public void shutdown() { enforceStatusBarService(); + enforceValidCallingUser(); + String reason = PowerManager.SHUTDOWN_USER_REQUESTED; ShutdownCheckPoints.recordCheckPoint(Binder.getCallingPid(), reason); final long identity = Binder.clearCallingIdentity(); @@ -1649,6 +1713,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D @Override public void reboot(boolean safeMode) { enforceStatusBarService(); + enforceValidCallingUser(); + String reason = safeMode ? PowerManager.REBOOT_SAFE_MODE : PowerManager.SHUTDOWN_USER_REQUESTED; @@ -1675,6 +1741,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D @Override public void restart() { enforceStatusBarService(); + enforceValidCallingUser(); + final long identity = Binder.clearCallingIdentity(); try { mHandler.post(() -> { @@ -1688,6 +1756,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D @Override public void onGlobalActionsShown() { enforceStatusBarService(); + enforceValidCallingUser(); + final long identity = Binder.clearCallingIdentity(); try { if (mGlobalActionListener == null) return; @@ -1700,6 +1770,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D @Override public void onGlobalActionsHidden() { enforceStatusBarService(); + enforceValidCallingUser(); + final long identity = Binder.clearCallingIdentity(); try { if (mGlobalActionListener == null) return; @@ -1711,6 +1783,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D @Override public void onNotificationClick(String key, NotificationVisibility nv) { + // enforceValidCallingUser is not required here as the NotificationManagerService + // will handle multi-user scenarios enforceStatusBarService(); final int callingUid = Binder.getCallingUid(); final int callingPid = Binder.getCallingPid(); @@ -1726,6 +1800,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D public void onNotificationActionClick( String key, int actionIndex, Notification.Action action, NotificationVisibility nv, boolean generatedByAssistant) { + // enforceValidCallingUser is not required here as the NotificationManagerService + // will handle multi-user scenarios enforceStatusBarService(); final int callingUid = Binder.getCallingUid(); final int callingPid = Binder.getCallingPid(); @@ -1741,6 +1817,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D @Override public void onNotificationError(String pkg, String tag, int id, int uid, int initialPid, String message, int userId) { + // enforceValidCallingUser is not required here as the NotificationManagerService + // will handle multi-user scenarios enforceStatusBarService(); final int callingUid = Binder.getCallingUid(); final int callingPid = Binder.getCallingPid(); @@ -1759,6 +1837,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D @NotificationStats.DismissalSurface int dismissalSurface, @NotificationStats.DismissalSentiment int dismissalSentiment, NotificationVisibility nv) { + // enforceValidCallingUser is not required here as the NotificationManagerService + // will handle multi-user scenarios enforceStatusBarService(); final int callingUid = Binder.getCallingUid(); final int callingPid = Binder.getCallingPid(); @@ -1776,6 +1856,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D NotificationVisibility[] newlyVisibleKeys, NotificationVisibility[] noLongerVisibleKeys) throws RemoteException { enforceStatusBarService(); + enforceValidCallingUser(); + final long identity = Binder.clearCallingIdentity(); try { mNotificationDelegate.onNotificationVisibilityChanged( @@ -1789,6 +1871,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D public void onNotificationExpansionChanged(String key, boolean userAction, boolean expanded, int location) throws RemoteException { enforceStatusBarService(); + enforceValidCallingUser(); + final long identity = Binder.clearCallingIdentity(); try { mNotificationDelegate.onNotificationExpansionChanged( @@ -1801,6 +1885,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D @Override public void onNotificationDirectReplied(String key) throws RemoteException { enforceStatusBarService(); + enforceValidCallingUser(); + final long identity = Binder.clearCallingIdentity(); try { mNotificationDelegate.onNotificationDirectReplied(key); @@ -1813,6 +1899,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D public void onNotificationSmartSuggestionsAdded(String key, int smartReplyCount, int smartActionCount, boolean generatedByAssistant, boolean editBeforeSending) { enforceStatusBarService(); + enforceValidCallingUser(); + final long identity = Binder.clearCallingIdentity(); try { mNotificationDelegate.onNotificationSmartSuggestionsAdded(key, smartReplyCount, @@ -1827,6 +1915,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D String key, int replyIndex, CharSequence reply, int notificationLocation, boolean modifiedBeforeSending) throws RemoteException { enforceStatusBarService(); + enforceValidCallingUser(); + final long identity = Binder.clearCallingIdentity(); try { mNotificationDelegate.onNotificationSmartReplySent(key, replyIndex, reply, @@ -1839,6 +1929,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D @Override public void onNotificationSettingsViewed(String key) throws RemoteException { enforceStatusBarService(); + enforceValidCallingUser(); + final long identity = Binder.clearCallingIdentity(); try { mNotificationDelegate.onNotificationSettingsViewed(key); @@ -1863,6 +1955,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D @Override public void onNotificationBubbleChanged(String key, boolean isBubble, int flags) { enforceStatusBarService(); + enforceValidCallingUser(); + final long identity = Binder.clearCallingIdentity(); try { mNotificationDelegate.onNotificationBubbleChanged(key, isBubble, flags); @@ -1874,6 +1968,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D @Override public void onBubbleMetadataFlagChanged(String key, int flags) { enforceStatusBarService(); + enforceValidCallingUser(); + final long identity = Binder.clearCallingIdentity(); try { mNotificationDelegate.onBubbleMetadataFlagChanged(key, flags); @@ -1885,6 +1981,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D @Override public void hideCurrentInputMethodForBubbles(int displayId) { enforceStatusBarService(); + enforceValidCallingUser(); + final long token = Binder.clearCallingIdentity(); try { InputMethodManagerInternal.get().hideInputMethod( @@ -1923,6 +2021,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D @Override public void onNotificationFeedbackReceived(String key, Bundle feedback) { enforceStatusBarService(); + enforceValidCallingUser(); + final long identity = Binder.clearCallingIdentity(); try { mNotificationDelegate.onNotificationFeedbackReceived(key, feedback); @@ -1942,6 +2042,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D @Override public void showInattentiveSleepWarning() { enforceStatusBarService(); + enforceValidCallingUser(); + IStatusBar bar = mBar; if (bar != null) { try { @@ -1954,6 +2056,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D @Override public void dismissInattentiveSleepWarning(boolean animated) { enforceStatusBarService(); + enforceValidCallingUser(); + IStatusBar bar = mBar; if (bar != null) { try { @@ -1966,6 +2070,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D @Override public void suppressAmbientDisplay(boolean suppress) { enforceStatusBarService(); + enforceValidCallingUser(); + IStatusBar bar = mBar; if (bar != null) { try { @@ -2179,6 +2285,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D @Override public void cancelRequestAddTile(@NonNull String packageName) { enforceStatusBar(); + enforceValidCallingUser(); + cancelRequestAddTileInternal(packageName); } @@ -2202,23 +2310,31 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D @Override public void onSessionStarted(@SessionFlags int sessionType, InstanceId instance) { + enforceValidCallingUser(); + mSessionMonitor.onSessionStarted(sessionType, instance); } @Override public void onSessionEnded(@SessionFlags int sessionType, InstanceId instance) { + enforceValidCallingUser(); + mSessionMonitor.onSessionEnded(sessionType, instance); } @Override public void registerSessionListener(@SessionFlags int sessionFlags, ISessionListener listener) { + enforceValidCallingUser(); + mSessionMonitor.registerSessionListener(sessionFlags, listener); } @Override public void unregisterSessionListener(@SessionFlags int sessionFlags, ISessionListener listener) { + enforceValidCallingUser(); + mSessionMonitor.unregisterSessionListener(sessionFlags, listener); } @@ -2233,6 +2349,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D */ public void setNavBarMode(@NavBarMode int navBarMode) { enforceStatusBar(); + enforceValidCallingUser(); + if (navBarMode != NAV_BAR_MODE_DEFAULT && navBarMode != NAV_BAR_MODE_KIDS) { throw new IllegalArgumentException("Supplied navBarMode not supported: " + navBarMode); } @@ -2243,6 +2361,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D throw new SecurityException("Calling user id: " + callingUserId + ", cannot call on behalf of current user id: " + mCurrentUserId + "."); } + final long userIdentity = Binder.clearCallingIdentity(); try { Settings.Secure.putIntForUser(mContext.getContentResolver(), @@ -2316,6 +2435,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D @Nullable IUndoMediaTransferCallback undoCallback ) { enforceMediaContentControl(); + enforceValidCallingUser(); + IStatusBar bar = mBar; if (bar != null) { try { @@ -2340,6 +2461,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D @Nullable Icon appIcon, @Nullable CharSequence appName) { enforceMediaContentControl(); + enforceValidCallingUser(); + IStatusBar bar = mBar; if (bar != null) { try { @@ -2367,6 +2490,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D @NonNull INearbyMediaDevicesProvider provider ) { enforceMediaContentControl(); + enforceValidCallingUser(); + IStatusBar bar = mBar; if (bar != null) { try { @@ -2393,6 +2518,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D @NonNull INearbyMediaDevicesProvider provider ) { enforceMediaContentControl(); + enforceValidCallingUser(); + IStatusBar bar = mBar; if (bar != null) { try { @@ -2407,6 +2534,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D @Override public void showRearDisplayDialog(int currentState) { enforceControlDeviceStatePermission(); + enforceValidCallingUser(); + IStatusBar bar = mBar; if (bar != null) { try { @@ -2579,4 +2708,32 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D private static final Context getUiContext() { return ActivityThread.currentActivityThread().getSystemUiContext(); } + + /** + * This method validates whether the calling user is allowed to control the status bar + * on a device that enables visible background users. + * Only system or current user or the user that belongs to the same profile group as the + * current user is permitted to control the status bar. + */ + private void enforceValidCallingUser() { + if (!mVisibleBackgroundUsersEnabled) { + return; + } + + int callingUserId = getCallingUserId(); + if (callingUserId == USER_SYSTEM || callingUserId == mCurrentUserId) { + return; + } + final long ident = Binder.clearCallingIdentity(); + try { + if (mUserManager.isSameProfileGroup(callingUserId, mCurrentUserId)) { + return; + } + } finally { + Binder.restoreCallingIdentity(ident); + } + + throw new SecurityException("User " + callingUserId + + " is not permitted to use this method"); + } } diff --git a/services/core/java/com/android/server/vibrator/HalVibration.java b/services/core/java/com/android/server/vibrator/HalVibration.java index 46bd7af159da..fe0cf5909970 100644 --- a/services/core/java/com/android/server/vibrator/HalVibration.java +++ b/services/core/java/com/android/server/vibrator/HalVibration.java @@ -170,9 +170,11 @@ final class HalVibration extends Vibration { /** Return {@link VibrationStats.StatsInfo} with read-only metrics about this vibration. */ public VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis) { - int vibrationType = isRepeating() - ? FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__REPEATED - : FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE; + int vibrationType = mEffectToPlay.hasVendorEffects() + ? FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__VENDOR + : isRepeating() + ? FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__REPEATED + : FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE; return new VibrationStats.StatsInfo( callerInfo.uid, vibrationType, callerInfo.attrs.getUsage(), mStatus, stats, completionUptimeMillis); diff --git a/services/core/java/com/android/server/vibrator/PerformVendorEffectVibratorStep.java b/services/core/java/com/android/server/vibrator/PerformVendorEffectVibratorStep.java index 8f36118543ed..407f3d996798 100644 --- a/services/core/java/com/android/server/vibrator/PerformVendorEffectVibratorStep.java +++ b/services/core/java/com/android/server/vibrator/PerformVendorEffectVibratorStep.java @@ -52,6 +52,7 @@ final class PerformVendorEffectVibratorStep extends AbstractVibratorStep { long vibratorOnResult = controller.on(effect, getVibration().id); vibratorOnResult = Math.min(vibratorOnResult, VENDOR_EFFECT_MAX_DURATION_MS); handleVibratorOnResult(vibratorOnResult); + getVibration().stats.reportPerformVendorEffect(vibratorOnResult); return List.of(new CompleteEffectVibratorStep(conductor, startTime, /* cancelled= */ false, controller, mPendingVibratorOffDeadline)); } finally { diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java index f2f5eda7c05a..0d6778c18759 100644 --- a/services/core/java/com/android/server/vibrator/VibrationSettings.java +++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java @@ -16,7 +16,6 @@ package com.android.server.vibrator; -import static android.os.VibrationAttributes.CATEGORY_KEYBOARD; import static android.os.VibrationAttributes.USAGE_ACCESSIBILITY; import static android.os.VibrationAttributes.USAGE_ALARM; import static android.os.VibrationAttributes.USAGE_COMMUNICATION_REQUEST; @@ -191,8 +190,6 @@ final class VibrationSettings { @GuardedBy("mLock") private boolean mVibrateOn; @GuardedBy("mLock") - private boolean mKeyboardVibrationOn; - @GuardedBy("mLock") private int mRingerMode; @GuardedBy("mLock") private boolean mOnWirelessCharger; @@ -532,14 +529,6 @@ final class VibrationSettings { return false; } - if (mVibrationConfig.isKeyboardVibrationSettingsSupported()) { - int category = callerInfo.attrs.getCategory(); - if (usage == USAGE_TOUCH && category == CATEGORY_KEYBOARD) { - // Keyboard touch has a different user setting. - return mKeyboardVibrationOn; - } - } - // Apply individual user setting based on usage. return getCurrentIntensity(usage) != Vibrator.VIBRATION_INTENSITY_OFF; } @@ -556,10 +545,11 @@ final class VibrationSettings { mVibrateInputDevices = loadSystemSetting(Settings.System.VIBRATE_INPUT_DEVICES, 0, userHandle) > 0; mVibrateOn = loadSystemSetting(Settings.System.VIBRATE_ON, 1, userHandle) > 0; - mKeyboardVibrationOn = loadSystemSetting( - Settings.System.KEYBOARD_VIBRATION_ENABLED, 1, userHandle) > 0; - int keyboardIntensity = getDefaultIntensity(USAGE_IME_FEEDBACK); + boolean isKeyboardVibrationOn = loadSystemSetting( + Settings.System.KEYBOARD_VIBRATION_ENABLED, 1, userHandle) > 0; + int keyboardIntensity = toIntensity(isKeyboardVibrationOn, + getDefaultIntensity(USAGE_IME_FEEDBACK)); int alarmIntensity = toIntensity( loadSystemSetting(Settings.System.ALARM_VIBRATION_INTENSITY, -1, userHandle), getDefaultIntensity(USAGE_ALARM)); @@ -654,7 +644,6 @@ final class VibrationSettings { return "VibrationSettings{" + "mVibratorConfig=" + mVibrationConfig + ", mVibrateOn=" + mVibrateOn - + ", mKeyboardVibrationOn=" + mKeyboardVibrationOn + ", mVibrateInputDevices=" + mVibrateInputDevices + ", mBatterySaverMode=" + mBatterySaverMode + ", mRingerMode=" + ringerModeToString(mRingerMode) @@ -671,7 +660,6 @@ final class VibrationSettings { pw.println("VibrationSettings:"); pw.increaseIndent(); pw.println("vibrateOn = " + mVibrateOn); - pw.println("keyboardVibrationOn = " + mKeyboardVibrationOn); pw.println("vibrateInputDevices = " + mVibrateInputDevices); pw.println("batterySaverMode = " + mBatterySaverMode); pw.println("ringerMode = " + ringerModeToString(mRingerMode)); @@ -698,8 +686,6 @@ final class VibrationSettings { void dump(ProtoOutputStream proto) { synchronized (mLock) { proto.write(VibratorManagerServiceDumpProto.VIBRATE_ON, mVibrateOn); - proto.write(VibratorManagerServiceDumpProto.KEYBOARD_VIBRATION_ON, - mKeyboardVibrationOn); proto.write(VibratorManagerServiceDumpProto.LOW_POWER_MODE, mBatterySaverMode); proto.write(VibratorManagerServiceDumpProto.ALARM_INTENSITY, getCurrentIntensity(USAGE_ALARM)); @@ -774,6 +760,11 @@ final class VibrationSettings { return value; } + @VibrationIntensity + private int toIntensity(boolean enabled, @VibrationIntensity int defaultValue) { + return enabled ? defaultValue : Vibrator.VIBRATION_INTENSITY_OFF; + } + private boolean loadBooleanSetting(String settingKey, int userHandle) { return loadSystemSetting(settingKey, 0, userHandle) != 0; } diff --git a/services/core/java/com/android/server/vibrator/VibrationStats.java b/services/core/java/com/android/server/vibrator/VibrationStats.java index dd66809e7ae6..8179d6aea9ca 100644 --- a/services/core/java/com/android/server/vibrator/VibrationStats.java +++ b/services/core/java/com/android/server/vibrator/VibrationStats.java @@ -79,6 +79,7 @@ final class VibrationStats { private int mVibratorSetAmplitudeCount; private int mVibratorSetExternalControlCount; private int mVibratorPerformCount; + private int mVibratorPerformVendorCount; private int mVibratorComposeCount; private int mVibratorComposePwleCount; @@ -239,6 +240,11 @@ final class VibrationStats { } } + /** Report a call to vibrator method to trigger a vendor vibration effect. */ + void reportPerformVendorEffect(long halResult) { + mVibratorPerformVendorCount++; + } + /** Report a call to vibrator method to trigger a vibration as a composition of primitives. */ void reportComposePrimitives(long halResult, PrimitiveSegment[] primitives) { mVibratorComposeCount++; @@ -313,6 +319,7 @@ final class VibrationStats { public final int halOnCount; public final int halOffCount; public final int halPerformCount; + public final int halPerformVendorCount; public final int halSetAmplitudeCount; public final int halSetExternalControlCount; public final int halCompositionSize; @@ -357,6 +364,7 @@ final class VibrationStats { halOnCount = stats.mVibratorOnCount; halOffCount = stats.mVibratorOffCount; halPerformCount = stats.mVibratorPerformCount; + halPerformVendorCount = stats.mVibratorPerformVendorCount; halSetAmplitudeCount = stats.mVibratorSetAmplitudeCount; halSetExternalControlCount = stats.mVibratorSetExternalControlCount; halCompositionSize = stats.mVibrationCompositionTotalSize; @@ -390,7 +398,8 @@ final class VibrationStats { halOnCount, halOffCount, halPerformCount, halSetAmplitudeCount, halSetExternalControlCount, halSupportedCompositionPrimitivesUsed, halSupportedEffectsUsed, halUnsupportedCompositionPrimitivesUsed, - halUnsupportedEffectsUsed, halCompositionSize, halPwleSize, adaptiveScale); + halUnsupportedEffectsUsed, halCompositionSize, halPwleSize, adaptiveScale, + halPerformVendorCount); } private static int[] filteredKeys(SparseBooleanArray supportArray, boolean supported) { diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 5c096ecbba04..a76f1b62fe02 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -1834,33 +1834,36 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } - mLetterboxUiController.onMovedToDisplay(mDisplayContent.getDisplayId()); + mAppCompatController.getAppCompatLetterboxPolicy() + .onMovedToDisplay(mDisplayContent.getDisplayId()); } void layoutLetterboxIfNeeded(WindowState winHint) { - mLetterboxUiController.layoutLetterboxIfNeeded(winHint); + mAppCompatController.getAppCompatLetterboxPolicy().start(winHint); } boolean hasWallpaperBackgroundForLetterbox() { - return mLetterboxUiController.hasWallpaperBackgroundForLetterbox(); + return mAppCompatController.getAppCompatLetterboxOverrides() + .hasWallpaperBackgroundForLetterbox(); } void updateLetterboxSurfaceIfNeeded(WindowState winHint, Transaction t) { - mLetterboxUiController.updateLetterboxSurfaceIfNeeded(winHint, t, getPendingTransaction()); + mAppCompatController.getAppCompatLetterboxPolicy() + .updateLetterboxSurfaceIfNeeded(winHint, t, getPendingTransaction()); } void updateLetterboxSurfaceIfNeeded(WindowState winHint) { - mLetterboxUiController.updateLetterboxSurfaceIfNeeded(winHint); + mAppCompatController.getAppCompatLetterboxPolicy().updateLetterboxSurfaceIfNeeded(winHint); } /** Gets the letterbox insets. The insets will be empty if there is no letterbox. */ Rect getLetterboxInsets() { - return mLetterboxUiController.getLetterboxInsets(); + return mAppCompatController.getAppCompatLetterboxPolicy().getLetterboxInsets(); } /** Gets the inner bounds of letterbox. The bounds will be empty if there is no letterbox. */ void getLetterboxInnerBounds(Rect outBounds) { - mLetterboxUiController.getLetterboxInnerBounds(outBounds); + mAppCompatController.getAppCompatLetterboxPolicy().getLetterboxInnerBounds(outBounds); } /** @@ -1868,7 +1871,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * when the current activity is displayed. */ boolean isFullyTransparentBarAllowed(Rect rect) { - return mLetterboxUiController.isFullyTransparentBarAllowed(rect); + return mAppCompatController.getAppCompatLetterboxPolicy() + .isFullyTransparentBarAllowed(rect); } private static class Token extends Binder { @@ -4368,7 +4372,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mTaskSupervisor.getActivityMetricsLogger().notifyActivityRemoved(this); mTaskSupervisor.mStoppingActivities.remove(this); - mLetterboxUiController.destroy(); + + mAppCompatController.getAppCompatLetterboxPolicy().stop(); mAppCompatController.getTransparentPolicy().stop(); // Defer removal of this activity when either a child is animating, or app transition is on @@ -7688,7 +7693,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A t.setLayer(mAnimationBoundsLayer, getLastLayer()); if (mNeedsLetterboxedAnimation) { - final int cornerRadius = mLetterboxUiController + final int cornerRadius = mAppCompatController.getAppCompatLetterboxPolicy() .getRoundedCornersRadius(findMainWindow()); final Rect letterboxInnerBounds = new Rect(); @@ -8574,7 +8579,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * Returns whether activity bounds are letterboxed. * * <p>Note that letterbox UI may not be shown even when this returns {@code true}. See {@link - * LetterboxUiController#shouldShowLetterboxUi} for more context. + * AppCompatLetterboxOverrides#shouldShowLetterboxUi} for more context. */ boolean areBoundsLetterboxed() { return getAppCompatState(/* ignoreVisibility= */ true) diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index e4cae58e0b81..f23211b98b85 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -3144,9 +3144,23 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { null, PENDING_ASSIST_EXTRAS_LONG_TIMEOUT, 0) != null; } + /** + * Requests assist data for a particular Task. + * + * <p>This is used by the system components to request assist data for a Task. + * + * @param receiver The receiver to send the assist data to. + * @param taskId The Task to request assist data for. + * @param callingPackageName The package name of the caller. + * @param callingAttributionTag The attribution tag of the caller. + * @param fetchStructure Whether to fetch the assist structure. Note that this is slow and + * should be avoided if possible. + * @return Whether the request was successful. + */ @Override public boolean requestAssistDataForTask(IAssistDataReceiver receiver, int taskId, - String callingPackageName, @Nullable String callingAttributionTag) { + String callingPackageName, @Nullable String callingAttributionTag, + boolean fetchStructure) { mAmInternal.enforceCallingPermission(android.Manifest.permission.GET_TOP_ACTIVITY_INFO, "requestAssistDataForTask()"); final long callingId = Binder.clearCallingIdentity(); @@ -3171,7 +3185,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { List<IBinder> topActivityToken = new ArrayList<>(); topActivityToken.add(tokens.getActivityToken()); requester.requestAssistData(topActivityToken, true /* fetchData */, - false /* fetchScreenshot */, false /* fetchStructure */, true /* allowFetchData */, + false /* fetchScreenshot */, fetchStructure, true /* allowFetchData */, false /* allowFetchScreenshot*/, true /* ignoreFocusCheck */, Binder.getCallingUid(), callingPackageName, callingAttributionTag); diff --git a/services/core/java/com/android/server/wm/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java index d38edfc39a8a..42900512de5d 100644 --- a/services/core/java/com/android/server/wm/AppCompatController.java +++ b/services/core/java/com/android/server/wm/AppCompatController.java @@ -40,6 +40,8 @@ class AppCompatController { private final AppCompatOverrides mAppCompatOverrides; @NonNull private final AppCompatDeviceStateQuery mAppCompatDeviceStateQuery; + @NonNull + private final AppCompatLetterboxPolicy mAppCompatLetterboxPolicy; AppCompatController(@NonNull WindowManagerService wmService, @NonNull ActivityRecord activityRecord) { @@ -57,6 +59,7 @@ class AppCompatController { mTransparentPolicy, mAppCompatOverrides); mAppCompatReachabilityPolicy = new AppCompatReachabilityPolicy(mActivityRecord, wmService.mAppCompatConfiguration); + mAppCompatLetterboxPolicy = new AppCompatLetterboxPolicy(mActivityRecord); } @NonNull @@ -113,6 +116,11 @@ class AppCompatController { } @NonNull + AppCompatLetterboxPolicy getAppCompatLetterboxPolicy() { + return mAppCompatLetterboxPolicy; + } + + @NonNull AppCompatFocusOverrides getAppCompatFocusOverrides() { return mAppCompatOverrides.getAppCompatFocusOverrides(); } @@ -127,4 +135,9 @@ class AppCompatController { return mAppCompatDeviceStateQuery; } + @NonNull + AppCompatLetterboxOverrides getAppCompatLetterboxOverrides() { + return mAppCompatOverrides.getAppCompatLetterboxOverrides(); + } + } diff --git a/services/core/java/com/android/server/wm/AppCompatLetterboxOverrides.java b/services/core/java/com/android/server/wm/AppCompatLetterboxOverrides.java new file mode 100644 index 000000000000..24ed14c5398f --- /dev/null +++ b/services/core/java/com/android/server/wm/AppCompatLetterboxOverrides.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; +import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME; +import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND; +import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING; +import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR; +import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_WALLPAPER; + +import android.annotation.NonNull; +import android.app.ActivityManager; +import android.graphics.Color; +import android.util.Slog; +import android.view.WindowManager; + +import com.android.server.wm.AppCompatConfiguration.LetterboxBackgroundType; + +/** + * Encapsulates overrides and configuration related to the Letterboxing policy. + */ +class AppCompatLetterboxOverrides { + + private static final String TAG = TAG_WITH_CLASS_NAME ? "AppCompatLetterboxOverrides" : TAG_ATM; + + @NonNull + private final ActivityRecord mActivityRecord; + @NonNull + private final AppCompatConfiguration mAppCompatConfiguration; + + private boolean mShowWallpaperForLetterboxBackground; + + AppCompatLetterboxOverrides(@NonNull ActivityRecord activityRecord, + @NonNull AppCompatConfiguration appCompatConfiguration) { + mActivityRecord = activityRecord; + mAppCompatConfiguration = appCompatConfiguration; + } + + boolean shouldLetterboxHaveRoundedCorners() { + // TODO(b/214030873): remove once background is drawn for transparent activities + // Letterbox shouldn't have rounded corners if the activity is transparent + return mAppCompatConfiguration.isLetterboxActivityCornersRounded() + && mActivityRecord.fillsParent(); + } + + boolean isLetterboxEducationEnabled() { + return mAppCompatConfiguration.getIsEducationEnabled(); + } + + boolean hasWallpaperBackgroundForLetterbox() { + return mShowWallpaperForLetterboxBackground; + } + + boolean checkWallpaperBackgroundForLetterbox(boolean wallpaperShouldBeShown) { + if (mShowWallpaperForLetterboxBackground != wallpaperShouldBeShown) { + mShowWallpaperForLetterboxBackground = wallpaperShouldBeShown; + return true; + } + return false; + } + + @NonNull + Color getLetterboxBackgroundColor() { + final WindowState w = mActivityRecord.findMainWindow(); + if (w == null || w.isLetterboxedForDisplayCutout()) { + return Color.valueOf(Color.BLACK); + } + final @LetterboxBackgroundType int letterboxBackgroundType = + mAppCompatConfiguration.getLetterboxBackgroundType(); + final ActivityManager.TaskDescription taskDescription = mActivityRecord.taskDescription; + switch (letterboxBackgroundType) { + case LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING: + if (taskDescription != null && taskDescription.getBackgroundColorFloating() != 0) { + return Color.valueOf(taskDescription.getBackgroundColorFloating()); + } + break; + case LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND: + if (taskDescription != null && taskDescription.getBackgroundColor() != 0) { + return Color.valueOf(taskDescription.getBackgroundColor()); + } + break; + case LETTERBOX_BACKGROUND_WALLPAPER: + if (hasWallpaperBackgroundForLetterbox()) { + // Color is used for translucent scrim that dims wallpaper. + return mAppCompatConfiguration.getLetterboxBackgroundColor(); + } + Slog.w(TAG, "Wallpaper option is selected for letterbox background but " + + "blur is not supported by a device or not supported in the current " + + "window configuration or both alpha scrim and blur radius aren't " + + "provided so using solid color background"); + break; + case LETTERBOX_BACKGROUND_SOLID_COLOR: + return mAppCompatConfiguration.getLetterboxBackgroundColor(); + default: + throw new AssertionError( + "Unexpected letterbox background type: " + letterboxBackgroundType); + } + // If picked option configured incorrectly or not supported then default to a solid color + // background. + return mAppCompatConfiguration.getLetterboxBackgroundColor(); + } + + int getLetterboxActivityCornersRadius() { + return mAppCompatConfiguration.getLetterboxActivityCornersRadius(); + } + + boolean isLetterboxActivityCornersRounded() { + return mAppCompatConfiguration.isLetterboxActivityCornersRounded(); + } + + @LetterboxBackgroundType + int getLetterboxBackgroundType() { + return mAppCompatConfiguration.getLetterboxBackgroundType(); + } + + int getLetterboxWallpaperBlurRadiusPx() { + int blurRadius = mAppCompatConfiguration.getLetterboxBackgroundWallpaperBlurRadiusPx(); + return Math.max(blurRadius, 0); + } + + float getLetterboxWallpaperDarkScrimAlpha() { + float alpha = mAppCompatConfiguration.getLetterboxBackgroundWallpaperDarkScrimAlpha(); + // No scrim by default. + return (alpha < 0 || alpha >= 1) ? 0.0f : alpha; + } + + boolean isLetterboxWallpaperBlurSupported() { + return mAppCompatConfiguration.mContext.getSystemService(WindowManager.class) + .isCrossWindowBlurEnabled(); + } + +} diff --git a/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java b/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java new file mode 100644 index 000000000000..d602c47d26c0 --- /dev/null +++ b/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java @@ -0,0 +1,372 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; +import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; + +import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_WALLPAPER; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.Point; +import android.graphics.Rect; +import android.view.SurfaceControl; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.statusbar.LetterboxDetails; +import com.android.server.wm.AppCompatConfiguration.LetterboxBackgroundType; + +/** + * Encapsulates the logic for the Letterboxing policy. + */ +class AppCompatLetterboxPolicy { + + @NonNull + private final ActivityRecord mActivityRecord; + @NonNull + private final LetterboxPolicyState mLetterboxPolicyState; + @NonNull + private final AppCompatRoundedCorners mAppCompatRoundedCorners; + + private boolean mLastShouldShowLetterboxUi; + + AppCompatLetterboxPolicy(@NonNull ActivityRecord activityRecord) { + mActivityRecord = activityRecord; + mLetterboxPolicyState = new LetterboxPolicyState(); + // TODO (b/358334569) Improve cutout logic dependency on app compat. + mAppCompatRoundedCorners = new AppCompatRoundedCorners(mActivityRecord, + this::isLetterboxedNotForDisplayCutout); + } + + /** Cleans up {@link Letterbox} if it exists.*/ + void stop() { + mLetterboxPolicyState.stop(); + } + + /** @return {@value true} if the letterbox policy is running and the activity letterboxed. */ + boolean isRunning() { + return mLetterboxPolicyState.isRunning(); + } + + void onMovedToDisplay(int displayId) { + mLetterboxPolicyState.onMovedToDisplay(displayId); + } + + /** Gets the letterbox insets. The insets will be empty if there is no letterbox. */ + @NonNull + Rect getLetterboxInsets() { + return mLetterboxPolicyState.getLetterboxInsets(); + } + + /** Gets the inner bounds of letterbox. The bounds will be empty if there is no letterbox. */ + void getLetterboxInnerBounds(@NonNull Rect outBounds) { + mLetterboxPolicyState.getLetterboxInnerBounds(outBounds); + } + + @Nullable + LetterboxDetails getLetterboxDetails() { + return mLetterboxPolicyState.getLetterboxDetails(); + } + + /** + * @return {@code true} if bar shown within a given rectangle is allowed to be fully transparent + * when the current activity is displayed. + */ + boolean isFullyTransparentBarAllowed(@NonNull Rect rect) { + return mLetterboxPolicyState.isFullyTransparentBarAllowed(rect); + } + + void updateLetterboxSurfaceIfNeeded(@NonNull WindowState winHint, + @NonNull SurfaceControl.Transaction t, + @NonNull SurfaceControl.Transaction inputT) { + mLetterboxPolicyState.updateLetterboxSurfaceIfNeeded(winHint, t, inputT); + } + + void updateLetterboxSurfaceIfNeeded(@NonNull WindowState winHint) { + mLetterboxPolicyState.updateLetterboxSurfaceIfNeeded(winHint, + mActivityRecord.getSyncTransaction(), mActivityRecord.getPendingTransaction()); + } + + void start(@NonNull WindowState w) { + if (shouldNotLayoutLetterbox(w)) { + return; + } + mAppCompatRoundedCorners.updateRoundedCornersIfNeeded(w); + updateWallpaperForLetterbox(w); + if (shouldShowLetterboxUi(w)) { + mLetterboxPolicyState.layoutLetterboxIfNeeded(w); + } else { + mLetterboxPolicyState.hide(); + } + } + + @VisibleForTesting + boolean shouldShowLetterboxUi(@NonNull WindowState mainWindow) { + if (mActivityRecord.mAppCompatController.getAppCompatOrientationOverrides() + .getIsRelaunchingAfterRequestedOrientationChanged()) { + return mLastShouldShowLetterboxUi; + } + + final boolean shouldShowLetterboxUi = + (mActivityRecord.isInLetterboxAnimation() || mActivityRecord.isVisible() + || mActivityRecord.isVisibleRequested()) + && mainWindow.areAppWindowBoundsLetterboxed() + // Check for FLAG_SHOW_WALLPAPER explicitly instead of using + // WindowContainer#showWallpaper because the later will return true when + // this activity is using blurred wallpaper for letterbox background. + && (mainWindow.getAttrs().flags & FLAG_SHOW_WALLPAPER) == 0; + + mLastShouldShowLetterboxUi = shouldShowLetterboxUi; + + return shouldShowLetterboxUi; + } + + @VisibleForTesting + @Nullable + Rect getCropBoundsIfNeeded(@NonNull final WindowState mainWindow) { + return mAppCompatRoundedCorners.getCropBoundsIfNeeded(mainWindow); + } + + /** + * Returns rounded corners radius the letterboxed activity should have based on override in + * R.integer.config_letterboxActivityCornersRadius or min device bottom corner radii. + * Device corners can be different on the right and left sides, but we use the same radius + * for all corners for consistency and pick a minimal bottom one for consistency with a + * taskbar rounded corners. + * + * @param mainWindow The {@link WindowState} to consider for the rounded corners calculation. + */ + int getRoundedCornersRadius(@NonNull final WindowState mainWindow) { + return mAppCompatRoundedCorners.getRoundedCornersRadius(mainWindow); + } + + private void updateWallpaperForLetterbox(@NonNull WindowState mainWindow) { + final AppCompatLetterboxOverrides letterboxOverrides = mActivityRecord + .mAppCompatController.getAppCompatLetterboxOverrides(); + final @LetterboxBackgroundType int letterboxBackgroundType = + letterboxOverrides.getLetterboxBackgroundType(); + boolean wallpaperShouldBeShown = + letterboxBackgroundType == LETTERBOX_BACKGROUND_WALLPAPER + // Don't use wallpaper as a background if letterboxed for display cutout. + && isLetterboxedNotForDisplayCutout(mainWindow) + // Check that dark scrim alpha or blur radius are provided + && (letterboxOverrides.getLetterboxWallpaperBlurRadiusPx() > 0 + || letterboxOverrides.getLetterboxWallpaperDarkScrimAlpha() > 0) + // Check that blur is supported by a device if blur radius is provided. + && (letterboxOverrides.getLetterboxWallpaperBlurRadiusPx() <= 0 + || letterboxOverrides.isLetterboxWallpaperBlurSupported()); + if (letterboxOverrides.checkWallpaperBackgroundForLetterbox(wallpaperShouldBeShown)) { + mActivityRecord.requestUpdateWallpaperIfNeeded(); + } + } + + private boolean isLetterboxedNotForDisplayCutout(@NonNull WindowState mainWindow) { + return shouldShowLetterboxUi(mainWindow) + && !mainWindow.isLetterboxedForDisplayCutout(); + } + + private static boolean shouldNotLayoutLetterbox(@Nullable WindowState w) { + if (w == null) { + return true; + } + final int type = w.mAttrs.type; + // Allow letterbox to be displayed early for base application or application starting + // windows even if it is not on the top z order to prevent flickering when the + // letterboxed window is brought to the top + return (type != TYPE_BASE_APPLICATION && type != TYPE_APPLICATION_STARTING) + || w.mAnimatingExit; + } + + private class LetterboxPolicyState { + + @Nullable + private Letterbox mLetterbox; + + void layoutLetterboxIfNeeded(@NonNull WindowState w) { + if (!isRunning()) { + final AppCompatLetterboxOverrides letterboxOverrides = mActivityRecord + .mAppCompatController.getAppCompatLetterboxOverrides(); + final AppCompatReachabilityPolicy reachabilityPolicy = mActivityRecord + .mAppCompatController.getAppCompatReachabilityPolicy(); + mLetterbox = new Letterbox(() -> mActivityRecord.makeChildSurface(null), + mActivityRecord.mWmService.mTransactionFactory, + reachabilityPolicy, letterboxOverrides, + this::getLetterboxParentSurface); + mLetterbox.attachInput(w); + mActivityRecord.mAppCompatController.getAppCompatReachabilityPolicy() + .setLetterboxInnerBoundsSupplier(mLetterbox::getInnerFrame); + } + final Point letterboxPosition = new Point(); + if (mActivityRecord.isInLetterboxAnimation()) { + // In this case we attach the letterbox to the task instead of the activity. + mActivityRecord.getTask().getPosition(letterboxPosition); + } else { + mActivityRecord.getPosition(letterboxPosition); + } + + // Get the bounds of the "space-to-fill". The transformed bounds have the highest + // priority because the activity is launched in a rotated environment. In multi-window + // mode, the taskFragment-level represents this for both split-screen + // and activity-embedding. In fullscreen-mode, the task container does + // (since the orientation letterbox is also applied to the task). + final Rect transformedBounds = mActivityRecord.getFixedRotationTransformDisplayBounds(); + final Rect spaceToFill = transformedBounds != null + ? transformedBounds + : mActivityRecord.inMultiWindowMode() + ? mActivityRecord.getTaskFragment().getBounds() + : mActivityRecord.getRootTask().getParent().getBounds(); + // In case of translucent activities an option is to use the WindowState#getFrame() of + // the first opaque activity beneath. In some cases (e.g. an opaque activity is using + // non MATCH_PARENT layouts or a Dialog theme) this might not provide the correct + // information and in particular it might provide a value for a smaller area making + // the letterbox overlap with the translucent activity's frame. + // If we use WindowState#getFrame() for the translucent activity's letterbox inner + // frame, the letterbox will then be overlapped with the translucent activity's frame. + // Because the surface layer of letterbox is lower than an activity window, this + // won't crop the content, but it may affect other features that rely on values stored + // in mLetterbox, e.g. transitions, a status bar scrim and recents preview in Launcher + // For this reason we use ActivityRecord#getBounds() that the translucent activity + // inherits from the first opaque activity beneath and also takes care of the scaling + // in case of activities in size compat mode. + final TransparentPolicy transparentPolicy = + mActivityRecord.mAppCompatController.getTransparentPolicy(); + final Rect innerFrame = + transparentPolicy.isRunning() ? mActivityRecord.getBounds() : w.getFrame(); + mLetterbox.layout(spaceToFill, innerFrame, letterboxPosition); + if (mActivityRecord.mAppCompatController.getAppCompatReachabilityOverrides() + .isDoubleTapEvent()) { + // We need to notify Shell that letterbox position has changed. + mActivityRecord.getTask().dispatchTaskInfoChangedIfNeeded(true /* force */); + } + } + + /** + * @return {@code true} if the policy is running and so if the current activity is + * letterboxed. + */ + boolean isRunning() { + return mLetterbox != null; + } + + void onMovedToDisplay(int displayId) { + if (isRunning()) { + mLetterbox.onMovedToDisplay(displayId); + } + } + + /** Cleans up {@link Letterbox} if it exists.*/ + void stop() { + if (isRunning()) { + mLetterbox.destroy(); + mLetterbox = null; + } + mActivityRecord.mAppCompatController.getAppCompatReachabilityPolicy() + .setLetterboxInnerBoundsSupplier(null); + } + + void updateLetterboxSurfaceIfNeeded(@NonNull WindowState winHint, + @NonNull SurfaceControl.Transaction t, + @NonNull SurfaceControl.Transaction inputT) { + if (shouldNotLayoutLetterbox(winHint)) { + return; + } + start(winHint); + if (isRunning() && mLetterbox.needsApplySurfaceChanges()) { + mLetterbox.applySurfaceChanges(t, inputT); + } + } + + void hide() { + if (isRunning()) { + mLetterbox.hide(); + } + } + + /** Gets the letterbox insets. The insets will be empty if there is no letterbox. */ + @NonNull + Rect getLetterboxInsets() { + if (isRunning()) { + return mLetterbox.getInsets(); + } else { + return new Rect(); + } + } + + /** Gets the inner bounds of letterbox. The bounds will be empty with no letterbox. */ + void getLetterboxInnerBounds(@NonNull Rect outBounds) { + if (isRunning()) { + outBounds.set(mLetterbox.getInnerFrame()); + final WindowState w = mActivityRecord.findMainWindow(); + if (w != null) { + AppCompatUtils.adjustBoundsForTaskbar(w, outBounds); + } + } else { + outBounds.setEmpty(); + } + } + + /** Gets the outer bounds of letterbox. The bounds will be empty with no letterbox. */ + private void getLetterboxOuterBounds(@NonNull Rect outBounds) { + if (isRunning()) { + outBounds.set(mLetterbox.getOuterFrame()); + } else { + outBounds.setEmpty(); + } + } + + /** + * @return {@code true} if bar shown within a given rectangle is allowed to be fully + * transparent when the current activity is displayed. + */ + boolean isFullyTransparentBarAllowed(@NonNull Rect rect) { + return !isRunning() || mLetterbox.notIntersectsOrFullyContains(rect); + } + + @Nullable + LetterboxDetails getLetterboxDetails() { + final WindowState w = mActivityRecord.findMainWindow(); + if (!isRunning() || w == null || w.isLetterboxedForDisplayCutout()) { + return null; + } + final Rect letterboxInnerBounds = new Rect(); + final Rect letterboxOuterBounds = new Rect(); + getLetterboxInnerBounds(letterboxInnerBounds); + getLetterboxOuterBounds(letterboxOuterBounds); + + if (letterboxInnerBounds.isEmpty() || letterboxOuterBounds.isEmpty()) { + return null; + } + + return new LetterboxDetails( + letterboxInnerBounds, + letterboxOuterBounds, + w.mAttrs.insetsFlags.appearance + ); + } + + @Nullable + private SurfaceControl getLetterboxParentSurface() { + if (mActivityRecord.isInLetterboxAnimation()) { + return mActivityRecord.getTask().getSurfaceControl(); + } + return mActivityRecord.getSurfaceControl(); + } + + } +} diff --git a/services/core/java/com/android/server/wm/AppCompatOverrides.java b/services/core/java/com/android/server/wm/AppCompatOverrides.java index 80bbee3dd78d..2f03105846bd 100644 --- a/services/core/java/com/android/server/wm/AppCompatOverrides.java +++ b/services/core/java/com/android/server/wm/AppCompatOverrides.java @@ -37,6 +37,8 @@ public class AppCompatOverrides { private final AppCompatResizeOverrides mAppCompatResizeOverrides; @NonNull private final AppCompatReachabilityOverrides mAppCompatReachabilityOverrides; + @NonNull + private final AppCompatLetterboxOverrides mAppCompatLetterboxOverrides; AppCompatOverrides(@NonNull ActivityRecord activityRecord, @NonNull AppCompatConfiguration appCompatConfiguration, @@ -54,6 +56,8 @@ public class AppCompatOverrides { mAppCompatFocusOverrides = new AppCompatFocusOverrides(activityRecord, appCompatConfiguration, optPropBuilder); mAppCompatResizeOverrides = new AppCompatResizeOverrides(activityRecord, optPropBuilder); + mAppCompatLetterboxOverrides = new AppCompatLetterboxOverrides(activityRecord, + appCompatConfiguration); } @NonNull @@ -85,4 +89,9 @@ public class AppCompatOverrides { AppCompatReachabilityOverrides getAppCompatReachabilityOverrides() { return mAppCompatReachabilityOverrides; } + + @NonNull + AppCompatLetterboxOverrides getAppCompatLetterboxOverrides() { + return mAppCompatLetterboxOverrides; + } } diff --git a/services/core/java/com/android/server/wm/AppCompatRoundedCorners.java b/services/core/java/com/android/server/wm/AppCompatRoundedCorners.java new file mode 100644 index 000000000000..077ce00c6033 --- /dev/null +++ b/services/core/java/com/android/server/wm/AppCompatRoundedCorners.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.Rect; +import android.view.InsetsState; +import android.view.RoundedCorner; +import android.view.SurfaceControl; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.function.Predicate; + +/** + * Utility to unify rounded corners in app compat. + */ +class AppCompatRoundedCorners { + + @NonNull + private final ActivityRecord mActivityRecord; + @NonNull + private final Predicate<WindowState> mIsLetterboxedNotForDisplayCutout; + + AppCompatRoundedCorners(@NonNull ActivityRecord activityRecord, + @NonNull Predicate<WindowState> isLetterboxedNotForDisplayCutout) { + mActivityRecord = activityRecord; + mIsLetterboxedNotForDisplayCutout = isLetterboxedNotForDisplayCutout; + } + + void updateRoundedCornersIfNeeded(@NonNull final WindowState mainWindow) { + final SurfaceControl windowSurface = mainWindow.getSurfaceControl(); + if (windowSurface == null || !windowSurface.isValid()) { + return; + } + + // cropBounds must be non-null for the cornerRadius to be ever applied. + mActivityRecord.getSyncTransaction() + .setCrop(windowSurface, getCropBoundsIfNeeded(mainWindow)) + .setCornerRadius(windowSurface, getRoundedCornersRadius(mainWindow)); + } + + @VisibleForTesting + @Nullable + Rect getCropBoundsIfNeeded(@NonNull final WindowState mainWindow) { + if (!requiresRoundedCorners(mainWindow) || mActivityRecord.isInLetterboxAnimation()) { + // We don't want corner radius on the window. + // In the case the ActivityRecord requires a letterboxed animation we never want + // rounded corners on the window because rounded corners are applied at the + // animation-bounds surface level and rounded corners on the window would interfere + // with that leading to unexpected rounded corner positioning during the animation. + return null; + } + + final Rect cropBounds = new Rect(mActivityRecord.getBounds()); + + // In case of translucent activities we check if the requested size is different from + // the size provided using inherited bounds. In that case we decide to not apply rounded + // corners because we assume the specific layout would. This is the case when the layout + // of the translucent activity uses only a part of all the bounds because of the use of + // LayoutParams.WRAP_CONTENT. + final TransparentPolicy transparentPolicy = mActivityRecord.mAppCompatController + .getTransparentPolicy(); + if (transparentPolicy.isRunning() && (cropBounds.width() != mainWindow.mRequestedWidth + || cropBounds.height() != mainWindow.mRequestedHeight)) { + return null; + } + + // It is important to call {@link #adjustBoundsIfNeeded} before {@link cropBounds.offsetTo} + // because taskbar bounds used in {@link #adjustBoundsIfNeeded} + // are in screen coordinates + AppCompatUtils.adjustBoundsForTaskbar(mainWindow, cropBounds); + + final float scale = mainWindow.mInvGlobalScale; + if (scale != 1f && scale > 0f) { + cropBounds.scale(scale); + } + + // ActivityRecord bounds are in screen coordinates while (0,0) for activity's surface + // control is in the top left corner of an app window so offsetting bounds + // accordingly. + cropBounds.offsetTo(0, 0); + return cropBounds; + } + + /** + * Returns rounded corners radius the letterboxed activity should have based on override in + * R.integer.config_letterboxActivityCornersRadius or min device bottom corner radii. + * Device corners can be different on the right and left sides, but we use the same radius + * for all corners for consistency and pick a minimal bottom one for consistency with a + * taskbar rounded corners. + * + * @param mainWindow The {@link WindowState} to consider for rounded corners calculation. + */ + int getRoundedCornersRadius(@NonNull final WindowState mainWindow) { + if (!requiresRoundedCorners(mainWindow)) { + return 0; + } + final AppCompatLetterboxOverrides letterboxOverrides = mActivityRecord + .mAppCompatController.getAppCompatLetterboxOverrides(); + final int radius; + if (letterboxOverrides.getLetterboxActivityCornersRadius() >= 0) { + radius = letterboxOverrides.getLetterboxActivityCornersRadius(); + } else { + final InsetsState insetsState = mainWindow.getInsetsState(); + radius = Math.min( + getInsetsStateCornerRadius(insetsState, RoundedCorner.POSITION_BOTTOM_LEFT), + getInsetsStateCornerRadius(insetsState, RoundedCorner.POSITION_BOTTOM_RIGHT)); + } + + final float scale = mainWindow.mInvGlobalScale; + return (scale != 1f && scale > 0f) ? (int) (scale * radius) : radius; + } + + private static int getInsetsStateCornerRadius(@NonNull InsetsState insetsState, + @RoundedCorner.Position int position) { + final RoundedCorner corner = insetsState.getRoundedCorners().getRoundedCorner(position); + return corner == null ? 0 : corner.getRadius(); + } + + private boolean requiresRoundedCorners(@NonNull final WindowState mainWindow) { + final AppCompatLetterboxOverrides letterboxOverrides = mActivityRecord + .mAppCompatController.getAppCompatLetterboxOverrides(); + return mIsLetterboxedNotForDisplayCutout.test(mainWindow) + && letterboxOverrides.isLetterboxActivityCornersRounded(); + } + +} diff --git a/services/core/java/com/android/server/wm/AppCompatUtils.java b/services/core/java/com/android/server/wm/AppCompatUtils.java index 0244d27f4363..94ad61f3f4de 100644 --- a/services/core/java/com/android/server/wm/AppCompatUtils.java +++ b/services/core/java/com/android/server/wm/AppCompatUtils.java @@ -28,13 +28,16 @@ import android.app.CameraCompatTaskInfo; import android.app.TaskInfo; import android.content.res.Configuration; import android.graphics.Rect; +import android.view.InsetsSource; +import android.view.InsetsState; +import android.view.WindowInsets; import java.util.function.BooleanSupplier; /** * Utilities for App Compat policies and overrides. */ -class AppCompatUtils { +final class AppCompatUtils { /** * Lazy version of a {@link BooleanSupplier} which access an existing BooleanSupplier and @@ -135,8 +138,8 @@ class AppCompatUtils { // Whether the direct top activity is eligible for letterbox education. appCompatTaskInfo.setEligibleForLetterboxEducation( isTopActivityResumed && top.isEligibleForLetterboxEducation()); - appCompatTaskInfo.setLetterboxEducationEnabled(top.mLetterboxUiController - .isLetterboxEducationEnabled()); + appCompatTaskInfo.setLetterboxEducationEnabled(top.mAppCompatController + .getAppCompatLetterboxOverrides().isLetterboxEducationEnabled()); final AppCompatAspectRatioOverrides aspectRatioOverrides = top.mAppCompatController.getAppCompatAspectRatioOverrides(); @@ -212,6 +215,40 @@ class AppCompatUtils { return "UNKNOWN_REASON"; } + /** + * Returns the taskbar in case it is visible and expanded in height, otherwise returns null. + */ + @Nullable + static InsetsSource getExpandedTaskbarOrNull(@NonNull final WindowState mainWindow) { + final InsetsState state = mainWindow.getInsetsState(); + for (int i = state.sourceSize() - 1; i >= 0; i--) { + final InsetsSource source = state.sourceAt(i); + if (source.getType() == WindowInsets.Type.navigationBars() + && source.hasFlags(InsetsSource.FLAG_INSETS_ROUNDED_CORNER) + && source.isVisible()) { + return source; + } + } + return null; + } + + static void adjustBoundsForTaskbar(@NonNull final WindowState mainWindow, + @NonNull final Rect bounds) { + // Rounded corners should be displayed above the taskbar. When taskbar is hidden, + // an insets frame is equal to a navigation bar which shouldn't affect position of + // rounded corners since apps are expected to handle navigation bar inset. + // This condition checks whether the taskbar is visible. + // Do not crop the taskbar inset if the window is in immersive mode - the user can + // swipe to show/hide the taskbar as an overlay. + // Adjust the bounds only in case there is an expanded taskbar, + // otherwise the rounded corners will be shown behind the navbar. + final InsetsSource expandedTaskbarOrNull = getExpandedTaskbarOrNull(mainWindow); + if (expandedTaskbarOrNull != null) { + // Rounded corners should be displayed above the expanded taskbar. + bounds.bottom = Math.min(bounds.bottom, expandedTaskbarOrNull.getFrame().top); + } + } + private static void clearAppCompatTaskInfo(@NonNull AppCompatTaskInfo info) { info.topActivityLetterboxVerticalPosition = TaskInfo.PROPERTY_VALUE_UNSET; info.topActivityLetterboxHorizontalPosition = TaskInfo.PROPERTY_VALUE_UNSET; diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java index 06e665e88b65..89e10d36caed 100644 --- a/services/core/java/com/android/server/wm/BackNavigationController.java +++ b/services/core/java/com/android/server/wm/BackNavigationController.java @@ -273,8 +273,9 @@ class BackNavigationController { customAppTransition.mBackgroundColor); } } - infoBuilder.setLetterboxColor(currentActivity.mLetterboxUiController - .getLetterboxBackgroundColor().toArgb()); + infoBuilder.setLetterboxColor(currentActivity.mAppCompatController + .getAppCompatLetterboxOverrides() + .getLetterboxBackgroundColor().toArgb()); removedWindowContainer = currentActivity; prevTask = prevActivities.get(0).getTask(); backType = BackNavigationInfo.TYPE_CROSS_ACTIVITY; @@ -761,7 +762,7 @@ class BackNavigationController { if (isMonitorForRemote()) { mObserver.sendResult(null /* result */); } - if (isMonitorAnimationOrTransition()) { + if (isMonitorAnimationOrTransition() && canCancelAnimations()) { clearBackAnimations(true /* cancel */); } cancelPendingAnimation(); @@ -2046,11 +2047,22 @@ class BackNavigationController { } } + /** If the open transition is playing, wait for transition to clear the animation */ + private boolean canCancelAnimations() { + if (!Flags.migratePredictiveBackTransition()) { + return true; + } + return mAnimationHandler.mOpenAnimAdaptor == null + || mAnimationHandler.mOpenAnimAdaptor.mPreparedOpenTransition == null; + } + void startAnimation() { if (!mBackAnimationInProgress) { // gesture is already finished, do not start animation if (mPendingAnimation != null) { - clearBackAnimations(true /* cancel */); + if (canCancelAnimations()) { + clearBackAnimations(true /* cancel */); + } mPendingAnimation = null; } return; diff --git a/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java b/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java index 2755a80ea705..9b142f280b31 100644 --- a/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java +++ b/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java @@ -109,10 +109,10 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa } @Override - public boolean onCameraOpened(@NonNull ActivityRecord cameraActivity, + public void onCameraOpened(@NonNull ActivityRecord cameraActivity, @NonNull String cameraId) { if (!isTreatmentEnabledForActivity(cameraActivity)) { - return false; + return; } final int existingCameraCompatMode = cameraActivity.mAppCompatController .getAppCompatCameraOverrides() @@ -124,11 +124,9 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa cameraActivity.mAppCompatController.getAppCompatCameraOverrides() .setFreeformCameraCompatMode(newCameraCompatMode); forceUpdateActivityAndTask(cameraActivity); - return true; } else { mIsCameraCompatTreatmentPending = false; } - return false; } @Override diff --git a/services/core/java/com/android/server/wm/CameraStateMonitor.java b/services/core/java/com/android/server/wm/CameraStateMonitor.java index 068fc001ae2c..63c90ff93224 100644 --- a/services/core/java/com/android/server/wm/CameraStateMonitor.java +++ b/services/core/java/com/android/server/wm/CameraStateMonitor.java @@ -73,16 +73,6 @@ class CameraStateMonitor { private final ArrayList<CameraCompatStateListener> mCameraStateListeners = new ArrayList<>(); - /** - * {@link CameraCompatStateListener} which returned {@code true} on the last {@link - * CameraCompatStateListener#onCameraOpened(ActivityRecord, String)}, if any. - * - * <p>This allows the {@link CameraStateMonitor} to notify a particular listener when camera - * closes, so they can revert any changes. - */ - @Nullable - private CameraCompatStateListener mCurrentListenerForCameraActivity; - private final CameraManager.AvailabilityCallback mAvailabilityCallback = new CameraManager.AvailabilityCallback() { @Override @@ -167,12 +157,7 @@ class CameraStateMonitor { @NonNull String cameraId) { for (int i = 0; i < mCameraStateListeners.size(); i++) { CameraCompatStateListener listener = mCameraStateListeners.get(i); - boolean activeCameraTreatment = listener.onCameraOpened( - cameraActivity, cameraId); - if (activeCameraTreatment) { - mCurrentListenerForCameraActivity = listener; - break; - } + listener.onCameraOpened(cameraActivity, cameraId); } } @@ -226,19 +211,30 @@ class CameraStateMonitor { return; } - if (mCurrentListenerForCameraActivity != null) { - boolean closeSuccessful = - mCurrentListenerForCameraActivity.onCameraClosed(cameraId); - if (closeSuccessful) { - mCameraIdPackageBiMapping.removeCameraId(cameraId); - mCurrentListenerForCameraActivity = null; - } else { - rescheduleRemoveCameraActivity(cameraId); - } + final boolean closeSuccessfulForAllListeners = notifyListenersCameraClosed(cameraId); + if (closeSuccessfulForAllListeners) { + // Finish cleaning up. + mCameraIdPackageBiMapping.removeCameraId(cameraId); + } else { + // Not ready to process closure yet - the camera activity might be refreshing. + // Try again later. + rescheduleRemoveCameraActivity(cameraId); } } } + /** + * @return {@code false} if any listeners have reported issues processing the close. + */ + private boolean notifyListenersCameraClosed(@NonNull String cameraId) { + boolean closeSuccessfulForAllListeners = true; + for (int i = 0; i < mCameraStateListeners.size(); i++) { + closeSuccessfulForAllListeners &= mCameraStateListeners.get(i).onCameraClosed(cameraId); + } + + return closeSuccessfulForAllListeners; + } + // TODO(b/335165310): verify that this works in multi instance and permission dialogs. /** * Finds a visible activity with the given package name. @@ -286,11 +282,9 @@ class CameraStateMonitor { interface CameraCompatStateListener { /** * Notifies the compat listener that an activity has opened camera. - * - * @return true if the treatment has been applied. */ // TODO(b/336474959): try to decouple `cameraId` from the listeners. - boolean onCameraOpened(@NonNull ActivityRecord cameraActivity, @NonNull String cameraId); + void onCameraOpened(@NonNull ActivityRecord cameraActivity, @NonNull String cameraId); /** * Notifies the compat listener that camera is closed. * diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index c3339cde6828..745b79209546 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -1596,7 +1596,7 @@ public class DisplayPolicy { final ActivityRecord currentActivity = win.getActivityRecord(); if (currentActivity != null) { final LetterboxDetails currentLetterboxDetails = currentActivity - .mLetterboxUiController.getLetterboxDetails(); + .mAppCompatController.getAppCompatLetterboxPolicy().getLetterboxDetails(); if (currentLetterboxDetails != null) { mLetterboxDetails.add(currentLetterboxDetails); } diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java index e50a089a4d5e..762180b1b778 100644 --- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java @@ -298,7 +298,7 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp } @Override - public boolean onCameraOpened(@NonNull ActivityRecord cameraActivity, + public void onCameraOpened(@NonNull ActivityRecord cameraActivity, @NonNull String cameraId) { mCameraTask = cameraActivity.getTask(); // Checking whether an activity in fullscreen rather than the task as this camera @@ -306,7 +306,7 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp if (cameraActivity.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) { recomputeConfigurationForCameraCompatIfNeeded(cameraActivity); mDisplayContent.updateOrientation(); - return true; + return; } // Checking that the whole app is in multi-window mode as we shouldn't show toast // for the activity embedding case. @@ -320,7 +320,6 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp (String) packageManager.getApplicationLabel( packageManager.getApplicationInfo(cameraActivity.packageName, /* flags */ 0))); - return true; } catch (PackageManager.NameNotFoundException e) { ProtoLog.e(WM_DEBUG_ORIENTATION, "DisplayRotationCompatPolicy: Multi-window toast not shown as " @@ -328,7 +327,6 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp cameraActivity.packageName); } } - return false; } @VisibleForTesting diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettings.java b/services/core/java/com/android/server/wm/DisplayWindowSettings.java index 7a95c2d6d934..2f0ee171b5ba 100644 --- a/services/core/java/com/android/server/wm/DisplayWindowSettings.java +++ b/services/core/java/com/android/server/wm/DisplayWindowSettings.java @@ -129,21 +129,33 @@ class DisplayWindowSettings { @WindowConfiguration.WindowingMode private int getWindowingModeLocked(@NonNull SettingsProvider.SettingsEntry settings, @NonNull DisplayContent dc) { - int windowingMode = settings.mWindowingMode; + final int windowingModeFromDisplaySettings = settings.mWindowingMode; // This display used to be in freeform, but we don't support freeform anymore, so fall // back to fullscreen. - if (windowingMode == WindowConfiguration.WINDOWING_MODE_FREEFORM + if (windowingModeFromDisplaySettings == WindowConfiguration.WINDOWING_MODE_FREEFORM && !mService.mAtmService.mSupportsFreeformWindowManagement) { return WindowConfiguration.WINDOWING_MODE_FULLSCREEN; } + if (windowingModeFromDisplaySettings != WindowConfiguration.WINDOWING_MODE_UNDEFINED) { + return windowingModeFromDisplaySettings; + } // No record is present so use default windowing mode policy. - if (windowingMode == WindowConfiguration.WINDOWING_MODE_UNDEFINED) { - windowingMode = mService.mAtmService.mSupportsFreeformWindowManagement - && (mService.mIsPc || dc.forceDesktopMode()) - ? WindowConfiguration.WINDOWING_MODE_FREEFORM - : WindowConfiguration.WINDOWING_MODE_FULLSCREEN; + final boolean forceFreeForm = mService.mAtmService.mSupportsFreeformWindowManagement + && (mService.mIsPc || dc.forceDesktopMode()); + if (forceFreeForm) { + return WindowConfiguration.WINDOWING_MODE_FREEFORM; + } + final int currentWindowingMode = dc.getDefaultTaskDisplayArea().getWindowingMode(); + if (currentWindowingMode == WindowConfiguration.WINDOWING_MODE_UNDEFINED) { + // No record preset in settings + no mode set via the display area policy. + // Move to fullscreen as a fallback. + return WindowConfiguration.WINDOWING_MODE_FULLSCREEN; + } + if (currentWindowingMode == WindowConfiguration.WINDOWING_MODE_FREEFORM) { + // Freeform was enabled before but disabled now, the TDA should now move to fullscreen. + return WindowConfiguration.WINDOWING_MODE_FULLSCREEN; } - return windowingMode; + return currentWindowingMode; } @WindowConfiguration.WindowingMode diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java index e18ca8552e72..5d8a96c530ef 100644 --- a/services/core/java/com/android/server/wm/KeyguardController.java +++ b/services/core/java/com/android/server/wm/KeyguardController.java @@ -51,6 +51,7 @@ import static com.android.server.wm.KeyguardControllerProto.KEYGUARD_SHOWING; import android.annotation.Nullable; import android.os.IBinder; import android.os.RemoteException; +import android.os.SystemClock; import android.os.Trace; import android.util.Slog; import android.util.SparseArray; @@ -619,7 +620,8 @@ class KeyguardController { } state.mKeyguardGoingAway = false; state.writeEventLog("goingAwayTimeout"); - mWindowManager.mPolicy.startKeyguardExitAnimation(0); + mWindowManager.mPolicy.startKeyguardExitAnimation( + SystemClock.uptimeMillis() - GOING_AWAY_TIMEOUT_MS); } }; diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java index 3fc5eafc8737..252590e0b696 100644 --- a/services/core/java/com/android/server/wm/Letterbox.java +++ b/services/core/java/com/android/server/wm/Letterbox.java @@ -38,9 +38,6 @@ import android.view.WindowManager; import com.android.server.UiThread; -import java.util.function.BooleanSupplier; -import java.util.function.DoubleSupplier; -import java.util.function.IntSupplier; import java.util.function.Supplier; /** @@ -54,12 +51,6 @@ public class Letterbox { private final Supplier<SurfaceControl.Builder> mSurfaceControlFactory; private final Supplier<SurfaceControl.Transaction> mTransactionFactory; - private final BooleanSupplier mAreCornersRounded; - private final Supplier<Color> mColorSupplier; - // Parameters for "blurred wallpaper" letterbox background. - private final BooleanSupplier mHasWallpaperBackgroundSupplier; - private final IntSupplier mBlurRadiusSupplier; - private final DoubleSupplier mDarkScrimAlphaSupplier; private final Supplier<SurfaceControl> mParentSurfaceSupplier; private final Rect mOuter = new Rect(); @@ -77,6 +68,8 @@ public class Letterbox { private final LetterboxSurface[] mSurfaces = { mLeft, mTop, mRight, mBottom }; @NonNull private final AppCompatReachabilityPolicy mAppCompatReachabilityPolicy; + @NonNull + private final AppCompatLetterboxOverrides mAppCompatLetterboxOverrides; /** * Constructs a Letterbox. @@ -85,24 +78,14 @@ public class Letterbox { */ public Letterbox(Supplier<SurfaceControl.Builder> surfaceControlFactory, Supplier<SurfaceControl.Transaction> transactionFactory, - BooleanSupplier areCornersRounded, - Supplier<Color> colorSupplier, - BooleanSupplier hasWallpaperBackgroundSupplier, - IntSupplier blurRadiusSupplier, - DoubleSupplier darkScrimAlphaSupplier, @NonNull AppCompatReachabilityPolicy appCompatReachabilityPolicy, + @NonNull AppCompatLetterboxOverrides appCompatLetterboxOverrides, Supplier<SurfaceControl> parentSurface) { mSurfaceControlFactory = surfaceControlFactory; mTransactionFactory = transactionFactory; - mAreCornersRounded = areCornersRounded; - mColorSupplier = colorSupplier; - mHasWallpaperBackgroundSupplier = hasWallpaperBackgroundSupplier; - mBlurRadiusSupplier = blurRadiusSupplier; - mDarkScrimAlphaSupplier = darkScrimAlphaSupplier; mAppCompatReachabilityPolicy = appCompatReachabilityPolicy; + mAppCompatLetterboxOverrides = appCompatLetterboxOverrides; mParentSurfaceSupplier = parentSurface; - // TODO Remove after Letterbox refactoring. - mAppCompatReachabilityPolicy.setLetterboxInnerBoundsSupplier(this::getInnerFrame); } /** @@ -252,7 +235,8 @@ public class Letterbox { * Returns {@code true} when using {@link #mFullWindowSurface} instead of {@link mSurfaces}. */ private boolean useFullWindowSurface() { - return mAreCornersRounded.getAsBoolean() || mHasWallpaperBackgroundSupplier.getAsBoolean(); + return mAppCompatLetterboxOverrides.shouldLetterboxHaveRoundedCorners() + || mAppCompatLetterboxOverrides.hasWallpaperBackgroundForLetterbox(); } private final class TapEventReceiver extends InputEventReceiver { @@ -431,7 +415,7 @@ public class Letterbox { createSurface(t); } - mColor = mColorSupplier.get(); + mColor = mAppCompatLetterboxOverrides.getLetterboxBackgroundColor(); mParentSurface = mParentSurfaceSupplier.get(); t.setColor(mSurface, getRgbColorArray()); t.setPosition(mSurface, mSurfaceFrameRelative.left, mSurfaceFrameRelative.top); @@ -439,7 +423,8 @@ public class Letterbox { mSurfaceFrameRelative.height()); t.reparent(mSurface, mParentSurface); - mHasWallpaperBackground = mHasWallpaperBackgroundSupplier.getAsBoolean(); + mHasWallpaperBackground = mAppCompatLetterboxOverrides + .hasWallpaperBackgroundForLetterbox(); updateAlphaAndBlur(t); t.show(mSurface); @@ -460,17 +445,19 @@ public class Letterbox { t.setBackgroundBlurRadius(mSurface, 0); return; } - final float alpha = (float) mDarkScrimAlphaSupplier.getAsDouble(); + final float alpha = mAppCompatLetterboxOverrides.getLetterboxWallpaperDarkScrimAlpha(); t.setAlpha(mSurface, alpha); // Translucent dark scrim can be shown without blur. - if (mBlurRadiusSupplier.getAsInt() <= 0) { + final int blurRadiusPx = mAppCompatLetterboxOverrides + .getLetterboxWallpaperBlurRadiusPx(); + if (blurRadiusPx <= 0) { // Removing pre-exesting blur t.setBackgroundBlurRadius(mSurface, 0); return; } - t.setBackgroundBlurRadius(mSurface, mBlurRadiusSupplier.getAsInt()); + t.setBackgroundBlurRadius(mSurface, blurRadiusPx); } private float[] getRgbColorArray() { @@ -487,8 +474,9 @@ public class Letterbox { // and mParentSurface may never be updated in applySurfaceChanges but this // doesn't mean that update is needed. || !mSurfaceFrameRelative.isEmpty() - && (mHasWallpaperBackgroundSupplier.getAsBoolean() != mHasWallpaperBackground - || !mColorSupplier.get().equals(mColor) + && (mAppCompatLetterboxOverrides.hasWallpaperBackgroundForLetterbox() + != mHasWallpaperBackground + || !mAppCompatLetterboxOverrides.getLetterboxBackgroundColor().equals(mColor) || mParentSurfaceSupplier.get() != mParentSurface); } } diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index 4740fc45c6ba..0e8291ee9492 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -16,36 +16,10 @@ package com.android.server.wm; -import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; -import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; -import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; - -import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; -import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME; -import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND; -import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING; -import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR; import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_WALLPAPER; import static com.android.server.wm.AppCompatConfiguration.letterboxBackgroundTypeToString; import android.annotation.NonNull; -import android.annotation.Nullable; -import android.app.ActivityManager.TaskDescription; -import android.graphics.Color; -import android.graphics.Point; -import android.graphics.Rect; -import android.util.Slog; -import android.view.InsetsSource; -import android.view.InsetsState; -import android.view.RoundedCorner; -import android.view.SurfaceControl; -import android.view.SurfaceControl.Transaction; -import android.view.WindowInsets; -import android.view.WindowManager; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.statusbar.LetterboxDetails; -import com.android.server.wm.AppCompatConfiguration.LetterboxBackgroundType; import java.io.PrintWriter; @@ -54,10 +28,6 @@ import java.io.PrintWriter; // SizeCompatTests and LetterboxTests but not all. final class LetterboxUiController { - private static final String TAG = TAG_WITH_CLASS_NAME ? "LetterboxUiController" : TAG_ATM; - - private final Point mTmpPoint = new Point(); - private final AppCompatConfiguration mAppCompatConfiguration; private final ActivityRecord mActivityRecord; @@ -66,18 +36,9 @@ final class LetterboxUiController { @NonNull private final AppCompatReachabilityOverrides mAppCompatReachabilityOverrides; @NonNull - private final AppCompatReachabilityPolicy mAppCompatReachabilityPolicy; + private final AppCompatLetterboxPolicy mAppCompatLetterboxPolicy; @NonNull - private final TransparentPolicy mTransparentPolicy; - @NonNull - private final AppCompatOrientationOverrides mAppCompatOrientationOverrides; - - private boolean mShowWallpaperForLetterboxBackground; - - @Nullable - private Letterbox mLetterbox; - - private boolean mLastShouldShowLetterboxUi; + private final AppCompatLetterboxOverrides mAppCompatLetterboxOverrides; LetterboxUiController(WindowManagerService wmService, ActivityRecord activityRecord) { mAppCompatConfiguration = wmService.mAppCompatConfiguration; @@ -88,402 +49,10 @@ final class LetterboxUiController { // TODO(b/356385137): Remove these we added to make dependencies temporarily explicit. mAppCompatReachabilityOverrides = mActivityRecord.mAppCompatController .getAppCompatReachabilityOverrides(); - mAppCompatReachabilityPolicy = mActivityRecord.mAppCompatController - .getAppCompatReachabilityPolicy(); - mTransparentPolicy = mActivityRecord.mAppCompatController.getTransparentPolicy(); - mAppCompatOrientationOverrides = mActivityRecord.mAppCompatController - .getAppCompatOrientationOverrides(); - - } - - /** Cleans up {@link Letterbox} if it exists.*/ - void destroy() { - if (mLetterbox != null) { - mLetterbox.destroy(); - mLetterbox = null; - // TODO Remove after Letterbox refactoring. - mAppCompatReachabilityPolicy.setLetterboxInnerBoundsSupplier(null); - } - } - - void onMovedToDisplay(int displayId) { - if (mLetterbox != null) { - mLetterbox.onMovedToDisplay(displayId); - } - } - - boolean hasWallpaperBackgroundForLetterbox() { - return mShowWallpaperForLetterboxBackground; - } - - /** Gets the letterbox insets. The insets will be empty if there is no letterbox. */ - Rect getLetterboxInsets() { - if (mLetterbox != null) { - return mLetterbox.getInsets(); - } else { - return new Rect(); - } - } - - /** Gets the inner bounds of letterbox. The bounds will be empty if there is no letterbox. */ - void getLetterboxInnerBounds(Rect outBounds) { - if (mLetterbox != null) { - outBounds.set(mLetterbox.getInnerFrame()); - final WindowState w = mActivityRecord.findMainWindow(); - if (w != null) { - adjustBoundsForTaskbar(w, outBounds); - } - } else { - outBounds.setEmpty(); - } - } - - /** Gets the outer bounds of letterbox. The bounds will be empty if there is no letterbox. */ - private void getLetterboxOuterBounds(Rect outBounds) { - if (mLetterbox != null) { - outBounds.set(mLetterbox.getOuterFrame()); - } else { - outBounds.setEmpty(); - } - } - - /** - * @return {@code true} if bar shown within a given rectangle is allowed to be fully transparent - * when the current activity is displayed. - */ - boolean isFullyTransparentBarAllowed(Rect rect) { - return mLetterbox == null || mLetterbox.notIntersectsOrFullyContains(rect); - } - - void updateLetterboxSurfaceIfNeeded(WindowState winHint) { - updateLetterboxSurfaceIfNeeded(winHint, mActivityRecord.getSyncTransaction(), - mActivityRecord.getPendingTransaction()); - } - - void updateLetterboxSurfaceIfNeeded(WindowState winHint, @NonNull Transaction t, - @NonNull Transaction inputT) { - if (shouldNotLayoutLetterbox(winHint)) { - return; - } - layoutLetterboxIfNeeded(winHint); - if (mLetterbox != null && mLetterbox.needsApplySurfaceChanges()) { - mLetterbox.applySurfaceChanges(t, inputT); - } - } - - void layoutLetterboxIfNeeded(WindowState w) { - if (shouldNotLayoutLetterbox(w)) { - return; - } - updateRoundedCornersIfNeeded(w); - updateWallpaperForLetterbox(w); - if (shouldShowLetterboxUi(w)) { - if (mLetterbox == null) { - mLetterbox = new Letterbox(() -> mActivityRecord.makeChildSurface(null), - mActivityRecord.mWmService.mTransactionFactory, - this::shouldLetterboxHaveRoundedCorners, - this::getLetterboxBackgroundColor, - this::hasWallpaperBackgroundForLetterbox, - this::getLetterboxWallpaperBlurRadiusPx, - this::getLetterboxWallpaperDarkScrimAlpha, - mAppCompatReachabilityPolicy, - this::getLetterboxParentSurface); - mLetterbox.attachInput(w); - } - - if (mActivityRecord.isInLetterboxAnimation()) { - // In this case we attach the letterbox to the task instead of the activity. - mActivityRecord.getTask().getPosition(mTmpPoint); - } else { - mActivityRecord.getPosition(mTmpPoint); - } - - // Get the bounds of the "space-to-fill". The transformed bounds have the highest - // priority because the activity is launched in a rotated environment. In multi-window - // mode, the taskFragment-level represents this for both split-screen - // and activity-embedding. In fullscreen-mode, the task container does - // (since the orientation letterbox is also applied to the task). - final Rect transformedBounds = mActivityRecord.getFixedRotationTransformDisplayBounds(); - final Rect spaceToFill = transformedBounds != null - ? transformedBounds - : mActivityRecord.inMultiWindowMode() - ? mActivityRecord.getTaskFragment().getBounds() - : mActivityRecord.getRootTask().getParent().getBounds(); - // In case of translucent activities an option is to use the WindowState#getFrame() of - // the first opaque activity beneath. In some cases (e.g. an opaque activity is using - // non MATCH_PARENT layouts or a Dialog theme) this might not provide the correct - // information and in particular it might provide a value for a smaller area making - // the letterbox overlap with the translucent activity's frame. - // If we use WindowState#getFrame() for the translucent activity's letterbox inner - // frame, the letterbox will then be overlapped with the translucent activity's frame. - // Because the surface layer of letterbox is lower than an activity window, this - // won't crop the content, but it may affect other features that rely on values stored - // in mLetterbox, e.g. transitions, a status bar scrim and recents preview in Launcher - // For this reason we use ActivityRecord#getBounds() that the translucent activity - // inherits from the first opaque activity beneath and also takes care of the scaling - // in case of activities in size compat mode. - final Rect innerFrame = - mTransparentPolicy.isRunning() ? mActivityRecord.getBounds() : w.getFrame(); - mLetterbox.layout(spaceToFill, innerFrame, mTmpPoint); - if (mAppCompatReachabilityOverrides.isDoubleTapEvent()) { - // We need to notify Shell that letterbox position has changed. - mActivityRecord.getTask().dispatchTaskInfoChangedIfNeeded(true /* force */); - } - } else if (mLetterbox != null) { - mLetterbox.hide(); - } - } - - SurfaceControl getLetterboxParentSurface() { - if (mActivityRecord.isInLetterboxAnimation()) { - return mActivityRecord.getTask().getSurfaceControl(); - } - return mActivityRecord.getSurfaceControl(); - } - - private static boolean shouldNotLayoutLetterbox(WindowState w) { - if (w == null) { - return true; - } - final int type = w.mAttrs.type; - // Allow letterbox to be displayed early for base application or application starting - // windows even if it is not on the top z order to prevent flickering when the - // letterboxed window is brought to the top - return (type != TYPE_BASE_APPLICATION && type != TYPE_APPLICATION_STARTING) - || w.mAnimatingExit; - } - - private boolean shouldLetterboxHaveRoundedCorners() { - // TODO(b/214030873): remove once background is drawn for transparent activities - // Letterbox shouldn't have rounded corners if the activity is transparent - return mAppCompatConfiguration.isLetterboxActivityCornersRounded() - && mActivityRecord.fillsParent(); - } - - boolean isLetterboxEducationEnabled() { - return mAppCompatConfiguration.getIsEducationEnabled(); - } - - @VisibleForTesting - boolean shouldShowLetterboxUi(WindowState mainWindow) { - if (mAppCompatOrientationOverrides.getIsRelaunchingAfterRequestedOrientationChanged()) { - return mLastShouldShowLetterboxUi; - } - - final boolean shouldShowLetterboxUi = - (mActivityRecord.isInLetterboxAnimation() || mActivityRecord.isVisible() - || mActivityRecord.isVisibleRequested()) - && mainWindow.areAppWindowBoundsLetterboxed() - // Check for FLAG_SHOW_WALLPAPER explicitly instead of using - // WindowContainer#showWallpaper because the later will return true when this - // activity is using blurred wallpaper for letterbox background. - && (mainWindow.getAttrs().flags & FLAG_SHOW_WALLPAPER) == 0; - - mLastShouldShowLetterboxUi = shouldShowLetterboxUi; - - return shouldShowLetterboxUi; - } - - Color getLetterboxBackgroundColor() { - final WindowState w = mActivityRecord.findMainWindow(); - if (w == null || w.isLetterboxedForDisplayCutout()) { - return Color.valueOf(Color.BLACK); - } - @LetterboxBackgroundType int letterboxBackgroundType = - mAppCompatConfiguration.getLetterboxBackgroundType(); - TaskDescription taskDescription = mActivityRecord.taskDescription; - switch (letterboxBackgroundType) { - case LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING: - if (taskDescription != null && taskDescription.getBackgroundColorFloating() != 0) { - return Color.valueOf(taskDescription.getBackgroundColorFloating()); - } - break; - case LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND: - if (taskDescription != null && taskDescription.getBackgroundColor() != 0) { - return Color.valueOf(taskDescription.getBackgroundColor()); - } - break; - case LETTERBOX_BACKGROUND_WALLPAPER: - if (hasWallpaperBackgroundForLetterbox()) { - // Color is used for translucent scrim that dims wallpaper. - return mAppCompatConfiguration.getLetterboxBackgroundColor(); - } - Slog.w(TAG, "Wallpaper option is selected for letterbox background but " - + "blur is not supported by a device or not supported in the current " - + "window configuration or both alpha scrim and blur radius aren't " - + "provided so using solid color background"); - break; - case LETTERBOX_BACKGROUND_SOLID_COLOR: - return mAppCompatConfiguration.getLetterboxBackgroundColor(); - default: - throw new AssertionError( - "Unexpected letterbox background type: " + letterboxBackgroundType); - } - // If picked option configured incorrectly or not supported then default to a solid color - // background. - return mAppCompatConfiguration.getLetterboxBackgroundColor(); - } - - private void updateRoundedCornersIfNeeded(final WindowState mainWindow) { - final SurfaceControl windowSurface = mainWindow.getSurfaceControl(); - if (windowSurface == null || !windowSurface.isValid()) { - return; - } - - // cropBounds must be non-null for the cornerRadius to be ever applied. - mActivityRecord.getSyncTransaction() - .setCrop(windowSurface, getCropBoundsIfNeeded(mainWindow)) - .setCornerRadius(windowSurface, getRoundedCornersRadius(mainWindow)); - } - - @VisibleForTesting - @Nullable - Rect getCropBoundsIfNeeded(final WindowState mainWindow) { - if (!requiresRoundedCorners(mainWindow) || mActivityRecord.isInLetterboxAnimation()) { - // We don't want corner radius on the window. - // In the case the ActivityRecord requires a letterboxed animation we never want - // rounded corners on the window because rounded corners are applied at the - // animation-bounds surface level and rounded corners on the window would interfere - // with that leading to unexpected rounded corner positioning during the animation. - return null; - } - - final Rect cropBounds = new Rect(mActivityRecord.getBounds()); - - // In case of translucent activities we check if the requested size is different from - // the size provided using inherited bounds. In that case we decide to not apply rounded - // corners because we assume the specific layout would. This is the case when the layout - // of the translucent activity uses only a part of all the bounds because of the use of - // LayoutParams.WRAP_CONTENT. - if (mTransparentPolicy.isRunning() && (cropBounds.width() != mainWindow.mRequestedWidth - || cropBounds.height() != mainWindow.mRequestedHeight)) { - return null; - } - - // It is important to call {@link #adjustBoundsIfNeeded} before {@link cropBounds.offsetTo} - // because taskbar bounds used in {@link #adjustBoundsIfNeeded} - // are in screen coordinates - adjustBoundsForTaskbar(mainWindow, cropBounds); - - final float scale = mainWindow.mInvGlobalScale; - if (scale != 1f && scale > 0f) { - cropBounds.scale(scale); - } - - // ActivityRecord bounds are in screen coordinates while (0,0) for activity's surface - // control is in the top left corner of an app window so offsetting bounds - // accordingly. - cropBounds.offsetTo(0, 0); - return cropBounds; - } - - private boolean requiresRoundedCorners(final WindowState mainWindow) { - return isLetterboxedNotForDisplayCutout(mainWindow) - && mAppCompatConfiguration.isLetterboxActivityCornersRounded(); - } - - // Returns rounded corners radius the letterboxed activity should have based on override in - // R.integer.config_letterboxActivityCornersRadius or min device bottom corner radii. - // Device corners can be different on the right and left sides, but we use the same radius - // for all corners for consistency and pick a minimal bottom one for consistency with a - // taskbar rounded corners. - int getRoundedCornersRadius(final WindowState mainWindow) { - if (!requiresRoundedCorners(mainWindow)) { - return 0; - } - - final int radius; - if (mAppCompatConfiguration.getLetterboxActivityCornersRadius() >= 0) { - radius = mAppCompatConfiguration.getLetterboxActivityCornersRadius(); - } else { - final InsetsState insetsState = mainWindow.getInsetsState(); - radius = Math.min( - getInsetsStateCornerRadius(insetsState, RoundedCorner.POSITION_BOTTOM_LEFT), - getInsetsStateCornerRadius(insetsState, RoundedCorner.POSITION_BOTTOM_RIGHT)); - } - - final float scale = mainWindow.mInvGlobalScale; - return (scale != 1f && scale > 0f) ? (int) (scale * radius) : radius; - } - - /** - * Returns the taskbar in case it is visible and expanded in height, otherwise returns null. - */ - @VisibleForTesting - @Nullable - InsetsSource getExpandedTaskbarOrNull(final WindowState mainWindow) { - final InsetsState state = mainWindow.getInsetsState(); - for (int i = state.sourceSize() - 1; i >= 0; i--) { - final InsetsSource source = state.sourceAt(i); - if (source.getType() == WindowInsets.Type.navigationBars() - && source.hasFlags(InsetsSource.FLAG_INSETS_ROUNDED_CORNER) - && source.isVisible()) { - return source; - } - } - return null; - } - - private void adjustBoundsForTaskbar(final WindowState mainWindow, final Rect bounds) { - // Rounded corners should be displayed above the taskbar. When taskbar is hidden, - // an insets frame is equal to a navigation bar which shouldn't affect position of - // rounded corners since apps are expected to handle navigation bar inset. - // This condition checks whether the taskbar is visible. - // Do not crop the taskbar inset if the window is in immersive mode - the user can - // swipe to show/hide the taskbar as an overlay. - // Adjust the bounds only in case there is an expanded taskbar, - // otherwise the rounded corners will be shown behind the navbar. - final InsetsSource expandedTaskbarOrNull = getExpandedTaskbarOrNull(mainWindow); - if (expandedTaskbarOrNull != null) { - // Rounded corners should be displayed above the expanded taskbar. - bounds.bottom = Math.min(bounds.bottom, expandedTaskbarOrNull.getFrame().top); - } - } - - private int getInsetsStateCornerRadius( - InsetsState insetsState, @RoundedCorner.Position int position) { - RoundedCorner corner = insetsState.getRoundedCorners().getRoundedCorner(position); - return corner == null ? 0 : corner.getRadius(); - } - - private boolean isLetterboxedNotForDisplayCutout(WindowState mainWindow) { - return shouldShowLetterboxUi(mainWindow) - && !mainWindow.isLetterboxedForDisplayCutout(); - } - - private void updateWallpaperForLetterbox(WindowState mainWindow) { - @LetterboxBackgroundType int letterboxBackgroundType = - mAppCompatConfiguration.getLetterboxBackgroundType(); - boolean wallpaperShouldBeShown = - letterboxBackgroundType == LETTERBOX_BACKGROUND_WALLPAPER - // Don't use wallpaper as a background if letterboxed for display cutout. - && isLetterboxedNotForDisplayCutout(mainWindow) - // Check that dark scrim alpha or blur radius are provided - && (getLetterboxWallpaperBlurRadiusPx() > 0 - || getLetterboxWallpaperDarkScrimAlpha() > 0) - // Check that blur is supported by a device if blur radius is provided. - && (getLetterboxWallpaperBlurRadiusPx() <= 0 - || isLetterboxWallpaperBlurSupported()); - if (mShowWallpaperForLetterboxBackground != wallpaperShouldBeShown) { - mShowWallpaperForLetterboxBackground = wallpaperShouldBeShown; - mActivityRecord.requestUpdateWallpaperIfNeeded(); - } - } - - private int getLetterboxWallpaperBlurRadiusPx() { - int blurRadius = mAppCompatConfiguration.getLetterboxBackgroundWallpaperBlurRadiusPx(); - return Math.max(blurRadius, 0); - } - - private float getLetterboxWallpaperDarkScrimAlpha() { - float alpha = mAppCompatConfiguration.getLetterboxBackgroundWallpaperDarkScrimAlpha(); - // No scrim by default. - return (alpha < 0 || alpha >= 1) ? 0.0f : alpha; - } - - private boolean isLetterboxWallpaperBlurSupported() { - return mAppCompatConfiguration.mContext.getSystemService(WindowManager.class) - .isCrossWindowBlurEnabled(); + mAppCompatLetterboxPolicy = mActivityRecord.mAppCompatController + .getAppCompatLetterboxPolicy(); + mAppCompatLetterboxOverrides = mActivityRecord.mAppCompatController + .getAppCompatLetterboxOverrides(); } void dump(PrintWriter pw, String prefix) { @@ -506,7 +75,7 @@ final class LetterboxUiController { pw.println(prefix + " activityAspectRatio=" + AppCompatUtils.computeAspectRatio(mActivityRecord.getBounds())); - boolean shouldShowLetterboxUi = shouldShowLetterboxUi(mainWin); + boolean shouldShowLetterboxUi = mAppCompatLetterboxPolicy.shouldShowLetterboxUi(mainWin); pw.println(prefix + "shouldShowLetterboxUi=" + shouldShowLetterboxUi); if (!shouldShowLetterboxUi) { @@ -517,20 +86,20 @@ final class LetterboxUiController { pw.println(prefix + " isHorizontalThinLetterboxed=" + mAppCompatReachabilityOverrides.isHorizontalThinLetterboxed()); pw.println(prefix + " letterboxBackgroundColor=" + Integer.toHexString( - getLetterboxBackgroundColor().toArgb())); + mAppCompatLetterboxOverrides.getLetterboxBackgroundColor().toArgb())); pw.println(prefix + " letterboxBackgroundType=" + letterboxBackgroundTypeToString( mAppCompatConfiguration.getLetterboxBackgroundType())); pw.println(prefix + " letterboxCornerRadius=" - + getRoundedCornersRadius(mainWin)); + + mAppCompatLetterboxPolicy.getRoundedCornersRadius(mainWin)); if (mAppCompatConfiguration.getLetterboxBackgroundType() == LETTERBOX_BACKGROUND_WALLPAPER) { pw.println(prefix + " isLetterboxWallpaperBlurSupported=" - + isLetterboxWallpaperBlurSupported()); + + mAppCompatLetterboxOverrides.isLetterboxWallpaperBlurSupported()); pw.println(prefix + " letterboxBackgroundWallpaperDarkScrimAlpha=" - + getLetterboxWallpaperDarkScrimAlpha()); + + mAppCompatLetterboxOverrides.getLetterboxWallpaperDarkScrimAlpha()); pw.println(prefix + " letterboxBackgroundWallpaperBlurRadius=" - + getLetterboxWallpaperBlurRadiusPx()); + + mAppCompatLetterboxOverrides.getLetterboxWallpaperBlurRadiusPx()); } final AppCompatReachabilityOverrides reachabilityOverrides = mActivityRecord .mAppCompatController.getAppCompatReachabilityOverrides(); @@ -560,26 +129,4 @@ final class LetterboxUiController { + mAppCompatConfiguration .getIsDisplayAspectRatioEnabledForFixedOrientationLetterbox()); } - - @Nullable - LetterboxDetails getLetterboxDetails() { - final WindowState w = mActivityRecord.findMainWindow(); - if (mLetterbox == null || w == null || w.isLetterboxedForDisplayCutout()) { - return null; - } - Rect letterboxInnerBounds = new Rect(); - Rect letterboxOuterBounds = new Rect(); - getLetterboxInnerBounds(letterboxInnerBounds); - getLetterboxOuterBounds(letterboxOuterBounds); - - if (letterboxInnerBounds.isEmpty() || letterboxOuterBounds.isEmpty()) { - return null; - } - - return new LetterboxDetails( - letterboxInnerBounds, - letterboxOuterBounds, - w.mAttrs.insetsFlags.appearance - ); - } } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index d3df5fdcc447..12e91adef9c5 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -3507,7 +3507,7 @@ class Task extends TaskFragment { | StartingWindowInfo.TYPE_PARAMETER_WINDOWLESS); if ((info.startingWindowTypeParameter & StartingWindowInfo.TYPE_PARAMETER_ACTIVITY_CREATED) != 0) { - final WindowState topMainWin = getWindow(w -> w.mAttrs.type == TYPE_BASE_APPLICATION); + final WindowState topMainWin = getTopFullscreenMainWindow(); if (topMainWin != null) { info.mainWindowLayoutParams = topMainWin.getAttrs(); info.requestedVisibleTypes = topMainWin.getRequestedVisibleTypes(); diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index ed0dc3be9465..2ee157f42615 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -478,6 +478,10 @@ class TaskFragment extends WindowContainer<WindowContainer> { mTaskFragmentOrganizerProcessName = processName; } + void onTaskFragmentOrganizerRestarted(@NonNull ITaskFragmentOrganizer organizer) { + mTaskFragmentOrganizer = organizer; + } + void onTaskFragmentOrganizerRemoved() { mTaskFragmentOrganizer = null; } @@ -2960,7 +2964,13 @@ class TaskFragment extends WindowContainer<WindowContainer> { } boolean shouldRemoveSelfOnLastChildRemoval() { - return !mCreatedByOrganizer || mIsRemovalRequested; + if (!mCreatedByOrganizer || mIsRemovalRequested) { + return true; + } + // The TaskFragmentOrganizer may be killed while the embedded TaskFragments remains in WM + // core. The embedded TaskFragment should still be removed when the child activities are + // all gone (like package force-stopped). + return mIsEmbedded && mTaskFragmentOrganizer == null; } /** diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java index 561ff7db5b9d..e4a31760e5ed 100644 --- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java @@ -86,6 +86,12 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr private final ArrayMap<IBinder, TaskFragmentOrganizerState> mTaskFragmentOrganizerState = new ArrayMap<>(); /** + * The cached {@link TaskFragmentOrganizerState} for the {@link ITaskFragmentOrganizer} that + * have no running process. + */ + private final ArrayList<TaskFragmentOrganizerState> mCachedTaskFragmentOrganizerStates = + new ArrayList<>(); + /** * Map from {@link ITaskFragmentOrganizer} to a list of related {@link PendingTaskFragmentEvent} */ private final ArrayMap<IBinder, List<PendingTaskFragmentEvent>> mPendingTaskFragmentEvents = @@ -105,11 +111,16 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr * {@link TaskFragment TaskFragments}. */ private class TaskFragmentOrganizerState implements IBinder.DeathRecipient { + @NonNull private final ArrayList<TaskFragment> mOrganizedTaskFragments = new ArrayList<>(); - private final IApplicationThread mAppThread; - private final ITaskFragmentOrganizer mOrganizer; - private final int mOrganizerPid; private final int mOrganizerUid; + @NonNull + private IApplicationThread mAppThread; + @NonNull + private ITaskFragmentOrganizer mOrganizer; + private int mOrganizerPid; + @Nullable + private Bundle mSavedState; /** * Map from {@link TaskFragment} to the last {@link TaskFragmentInfo} sent to the @@ -182,6 +193,24 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } } + void restore(@NonNull ITaskFragmentOrganizer organizer, int pid) { + mOrganizer = organizer; + mOrganizerPid = pid; + mAppThread = getAppThread(pid, mOrganizerUid); + for (int i = mOrganizedTaskFragments.size() - 1; i >= 0; i--) { + mOrganizedTaskFragments.get(i).onTaskFragmentOrganizerRestarted(organizer); + } + try { + mOrganizer.asBinder().linkToDeath(this, 0 /*flags*/); + } catch (RemoteException e) { + Slog.e(TAG, "TaskFragmentOrganizer failed to register death recipient"); + } + } + + boolean hasSavedState() { + return mSavedState != null && !mSavedState.isEmpty(); + } + /** * @return {@code true} if taskFragment is organized and not sent the appeared event before. */ @@ -226,12 +255,18 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr containsNonEmbeddedActivity ? null : task, null /* remoteTransition */, null /* displayChange */); } + // Defer to avoid unnecessary layout when there are multiple TaskFragments removal. mAtmService.deferWindowLayout(); try { - while (!mOrganizedTaskFragments.isEmpty()) { - final TaskFragment taskFragment = mOrganizedTaskFragments.remove(0); - taskFragment.removeImmediately(); + // Removes the TaskFragments if no saved state or is empty. + final boolean haveSavedState = hasSavedState(); + for (int i = mOrganizedTaskFragments.size() - 1; i >= 0; i--) { + final TaskFragment taskFragment = mOrganizedTaskFragments.get(i); + if (!haveSavedState || !taskFragment.hasChild()) { + mOrganizedTaskFragments.remove(i); + taskFragment.removeImmediately(); + } } } finally { mAtmService.continueWindowLayout(); @@ -407,8 +442,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr change.setTaskFragmentToken(lastParentTfToken); } // Only pass the activity token to the client if it belongs to the same process. - if (Flags.fixPipRestoreToOverlay() && nextFillTaskActivity != null - && nextFillTaskActivity.getPid() == mOrganizerPid) { + if (nextFillTaskActivity != null && nextFillTaskActivity.getPid() == mOrganizerPid) { change.setOtherActivityToken(nextFillTaskActivity.token); } return change; @@ -479,15 +513,15 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } @Override - public void registerOrganizer( - @NonNull ITaskFragmentOrganizer organizer, boolean isSystemOrganizer) { - registerOrganizerInternal( - organizer, - Flags.taskFragmentSystemOrganizerFlag() && isSystemOrganizer); + public void registerOrganizer(@NonNull ITaskFragmentOrganizer organizer, + boolean isSystemOrganizer, @NonNull Bundle outSavedState) { + registerOrganizerInternal(organizer, + Flags.taskFragmentSystemOrganizerFlag() && isSystemOrganizer, outSavedState); } private void registerOrganizerInternal( - @NonNull ITaskFragmentOrganizer organizer, boolean isSystemOrganizer) { + @NonNull ITaskFragmentOrganizer organizer, boolean isSystemOrganizer, + @NonNull Bundle outSavedState) { if (isSystemOrganizer) { enforceTaskPermission("registerSystemOrganizer()"); } @@ -497,16 +531,49 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Register task fragment organizer=%s uid=%d pid=%d", organizer.asBinder(), uid, pid); + if (isOrganizerRegistered(organizer)) { throw new IllegalStateException( "Replacing existing organizer currently unsupported"); } + + if (restoreFromCachedStateIfPossible(organizer, pid, uid, outSavedState)) { + return; + } + mTaskFragmentOrganizerState.put(organizer.asBinder(), new TaskFragmentOrganizerState(organizer, pid, uid, isSystemOrganizer)); mPendingTaskFragmentEvents.put(organizer.asBinder(), new ArrayList<>()); } } + private boolean restoreFromCachedStateIfPossible(@NonNull ITaskFragmentOrganizer organizer, + int pid, int uid, @NonNull Bundle outSavedState) { + if (!Flags.aeBackStackRestore()) { + return false; + } + + TaskFragmentOrganizerState cachedState = null; + int i = mCachedTaskFragmentOrganizerStates.size() - 1; + for (; i >= 0; i--) { + final TaskFragmentOrganizerState state = mCachedTaskFragmentOrganizerStates.get(i); + if (state.mOrganizerUid == uid) { + cachedState = state; + break; + } + } + if (cachedState == null) { + return false; + } + + mCachedTaskFragmentOrganizerStates.remove(cachedState); + outSavedState.putAll(cachedState.mSavedState); + cachedState.restore(organizer, pid); + mTaskFragmentOrganizerState.put(organizer.asBinder(), cachedState); + mPendingTaskFragmentEvents.put(organizer.asBinder(), new ArrayList<>()); + return true; + } + @Override public void unregisterOrganizer(@NonNull ITaskFragmentOrganizer organizer) { final int pid = Binder.getCallingPid(); @@ -525,6 +592,23 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } @Override + public void setSavedState(@NonNull ITaskFragmentOrganizer organizer, @Nullable Bundle state) { + final int pid = Binder.getCallingPid(); + final int uid = Binder.getCallingUid(); + synchronized (mGlobalLock) { + ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Set state for organizer=%s uid=%d pid=%d", + organizer.asBinder(), uid, pid); + final TaskFragmentOrganizerState organizerState = mTaskFragmentOrganizerState.get( + organizer.asBinder()); + if (organizerState == null) { + throw new IllegalStateException("The organizer hasn't been registered."); + } + + organizerState.mSavedState = state; + } + } + + @Override public void onTransactionHandled(@NonNull IBinder transactionToken, @NonNull WindowContainerTransaction wct, @WindowManager.TransitionType int transitionType, boolean shouldApplyIndependently) { @@ -769,9 +853,11 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr // Remove any pending event of this organizer first because state.dispose() may trigger // event dispatch as result of surface placement. mPendingTaskFragmentEvents.remove(organizer.asBinder()); - // remove all of the children of the organized TaskFragment state.dispose(reason); mTaskFragmentOrganizerState.remove(organizer.asBinder()); + if (state.hasSavedState()) { + mCachedTaskFragmentOrganizerStates.add(state); + } } /** diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index 2c5beda1d05a..9ee1fbe91d83 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -684,7 +684,8 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { removalInfo.playRevealAnimation = false; } else if (removalInfo.playRevealAnimation && playShiftUpAnimation) { removalInfo.roundedCornerRadius = - topActivity.mLetterboxUiController.getRoundedCornersRadius(mainWindow); + topActivity.mAppCompatController.getAppCompatLetterboxPolicy() + .getRoundedCornersRadius(mainWindow); removalInfo.windowAnimationLeash = applyStartingWindowAnimation(mainWindow); removalInfo.mainFrame = new Rect(mainWindow.getFrame()); removalInfo.mainFrame.offsetTo(mainWindow.mSurfacePosition.x, diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index 6ea1f3ab6469..db95d961f9fd 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -177,8 +177,8 @@ class WallpaperController { if (DEBUG_WALLPAPER) Slog.v(TAG, "Found recents animation wallpaper target: " + w); mFindResults.setWallpaperTarget(w); return true; - } else if (hasWallpaper && w.isOnScreen() - && (mWallpaperTarget == w || w.isDrawFinishedLw())) { + } else if (hasWallpaper + && (w.mActivityRecord != null ? w.isOnScreen() : w.isReadyForDisplay())) { if (DEBUG_WALLPAPER) Slog.v(TAG, "Found wallpaper target: " + w); mFindResults.setWallpaperTarget(w); mFindResults.setIsWallpaperTargetForLetterbox(w.hasWallpaperForLetterboxBackground()); diff --git a/services/core/java/com/android/server/wm/WindowAnimationSpec.java b/services/core/java/com/android/server/wm/WindowAnimationSpec.java index 34b9913c9738..2c58c61701cc 100644 --- a/services/core/java/com/android/server/wm/WindowAnimationSpec.java +++ b/services/core/java/com/android/server/wm/WindowAnimationSpec.java @@ -97,10 +97,10 @@ public class WindowAnimationSpec implements AnimationSpec { /** * @return If a window animation has outsets applied to it. - * @see Animation#hasExtension() + * @see Animation#getExtensionEdges() */ public boolean hasExtension() { - return mAnimation.hasExtension(); + return mAnimation.getExtensionEdges() != 0; } @Override diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java index 092a7515a8f8..06d8c370b914 100644 --- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java +++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java @@ -283,24 +283,28 @@ public class WindowManagerShellCommand extends ShellCommand { } private int runDisplayDensity(PrintWriter pw) throws RemoteException { - String densityStr = getNextArg(); + String densityStr = null; String arg = getNextArg(); int density; int displayId = Display.DEFAULT_DISPLAY; - if ("-d".equals(densityStr) && arg != null) { + if (!"-d".equals(arg) && !"-u".equals(arg)) { + densityStr = arg; + arg = getNextArg(); + } + if ("-d".equals(arg)) { + arg = getNextArg(); try { displayId = Integer.parseInt(arg); } catch (NumberFormatException e) { getErrPrintWriter().println("Error: bad number " + e); } - densityStr = getNextArg(); - } else if ("-u".equals(densityStr) && arg != null) { + } else if ("-u".equals(arg)) { + arg = getNextArg(); displayId = mInterface.getDisplayIdByUniqueId(arg); if (displayId == Display.INVALID_DISPLAY) { getErrPrintWriter().println("Error: the uniqueId is invalid "); return -1; } - densityStr = getNextArg(); } if (densityStr == null) { diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 58c48ad3e9ac..c6d0b729cd5a 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -1194,7 +1194,13 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } } } - effects |= sanitizeAndApplyHierarchyOp(wc, hop); + if (wc.asTask() != null) { + effects |= sanitizeAndApplyHierarchyOpForTask(wc.asTask(), hop); + } else if (wc.asDisplayArea() != null) { + effects |= sanitizeAndApplyHierarchyOpForDisplayArea(wc.asDisplayArea(), hop); + } else { + throw new IllegalArgumentException("Invalid container in hierarchy op"); + } break; } case HIERARCHY_OP_TYPE_ADD_TASK_FRAGMENT_OPERATION: { @@ -1850,12 +1856,22 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub return starterResult[0]; } - private int sanitizeAndApplyHierarchyOp(WindowContainer container, - WindowContainerTransaction.HierarchyOp hop) { - final Task task = container.asTask(); - if (task == null) { - throw new IllegalArgumentException("Invalid container in hierarchy op"); + private int sanitizeAndApplyHierarchyOpForDisplayArea(@NonNull DisplayArea displayArea, + @NonNull WindowContainerTransaction.HierarchyOp hop) { + if (hop.getType() != HIERARCHY_OP_TYPE_REORDER) { + throw new UnsupportedOperationException("DisplayArea only supports reordering"); + } + if (displayArea.getParent() == null) { + return TRANSACT_EFFECTS_NONE; } + displayArea.getParent().positionChildAt( + hop.getToTop() ? POSITION_TOP : POSITION_BOTTOM, + displayArea, hop.includingParents()); + return TRANSACT_EFFECTS_LIFECYCLE; + } + + private int sanitizeAndApplyHierarchyOpForTask(@NonNull Task task, + @NonNull WindowContainerTransaction.HierarchyOp hop) { final DisplayContent dc = task.getDisplayContent(); if (dc == null) { Slog.w(TAG, "Container is no longer attached: " + task); diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index ec2fd3f17556..b6e8977d2e45 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -2385,6 +2385,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mWmService.mDisplayManagerInternal.onPresentation(dc.getDisplay().getDisplayId(), /*isShown=*/ false); } + // Check if window provides non decor insets before clearing its provided insets. + final boolean windowProvidesDisplayDecorInsets = providesDisplayDecorInsets(); dc.getDisplayPolicy().removeWindowLw(this); @@ -2395,6 +2397,18 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mWmService.postWindowRemoveCleanupLocked(this); consumeInsetsChange(); + + // Update the orientation when removing a window affecting the display orientation. + // Also recompute configuration if it provides screen decor insets. + boolean needToSendNewConfiguration = dc.getLastOrientationSource() == this + && dc.updateOrientation(); + if (windowProvidesDisplayDecorInsets) { + needToSendNewConfiguration |= dc.getDisplayPolicy().updateDecorInsetsInfo(); + } + + if (needToSendNewConfiguration) { + dc.sendNewConfiguration(); + } } @Override @@ -2447,16 +2461,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mActivityRecord != null && mActivityRecord.isAnimating(PARENTS | TRANSITION), mWmService.mDisplayFrozen, Debug.getCallers(6)); - // Visibility of the removed window. Will be used later to update orientation later on. - boolean wasVisible = false; - // First, see if we need to run an animation. If we do, we have to hold off on removing the // window until the animation is done. If the display is frozen, just remove immediately, // since the animation wouldn't be seen. if (mHasSurface && mToken.okToAnimate()) { - // If we are not currently running the exit animation, we need to see about starting one - wasVisible = isVisible(); - // Remove immediately if there is display transition because the animation is // usually unnoticeable (e.g. covered by rotation animation) and the animation // bounds could be inconsistent, such as depending on when the window applies @@ -2466,7 +2474,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // look weird if its orientation is changed. && !inRelaunchingActivity(); - if (wasVisible && isDisplayed()) { + // If we are not currently running the exit animation, + // we need to see about starting one + if (isVisible() && isDisplayed()) { final int transit = (!startingWindow) ? TRANSIT_EXIT : TRANSIT_PREVIEW_DONE; // Try starting an animation. @@ -2515,21 +2525,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } } - // Check if window provides non decor insets before clearing its provided insets. - final boolean windowProvidesDisplayDecorInsets = providesDisplayDecorInsets(); - removeImmediately(); - // Removing a visible window may affect the display orientation so just update it if - // needed. Also recompute configuration if it provides screen decor insets. - boolean needToSendNewConfiguration = wasVisible && displayContent.updateOrientation(); - if (windowProvidesDisplayDecorInsets) { - needToSendNewConfiguration |= - displayContent.getDisplayPolicy().updateDecorInsetsInfo(); - } - - if (needToSendNewConfiguration) { - displayContent.sendNewConfiguration(); - } mWmService.updateFocusedWindowLocked(isFocused() ? UPDATE_FOCUS_REMOVING_FOCUS : UPDATE_FOCUS_NORMAL, @@ -3754,7 +3750,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // If the activity is scheduled to relaunch, skip sending the resized to ViewRootImpl now // since it will be destroyed anyway. This also prevents the client from receiving // windowing mode change before it is destroyed. - if (inRelaunchingActivity()) { + if (inRelaunchingActivity() && mAttrs.type != TYPE_APPLICATION_STARTING) { return; } // If this is an activity or wallpaper and is invisible or going invisible, don't report @@ -3975,7 +3971,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP * Returns {@code true} if activity bounds are letterboxed or letterboxed for display cutout. * * <p>Note that letterbox UI may not be shown even when this returns {@code true}. See {@link - * LetterboxUiController#shouldShowLetterboxUi} for more context. + * AppCompatLetterboxOverrides#shouldShowLetterboxUi} for more context. */ boolean areAppWindowBoundsLetterboxed() { return mActivityRecord != null && !isStartingWindowAssociatedToTask() diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index f1e94deb8a45..a5085fc3147c 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -211,7 +211,6 @@ cc_defaults { "android.system.suspend-V1-ndk", "server_configurable_flags", "service.incremental", - "android.companion.virtualdevice.flags-aconfig-cc", ], static_libs: [ diff --git a/services/core/jni/com_android_server_companion_virtual_InputController.cpp b/services/core/jni/com_android_server_companion_virtual_InputController.cpp index 9ec5ae5a8e51..6f7276079970 100644 --- a/services/core/jni/com_android_server_companion_virtual_InputController.cpp +++ b/services/core/jni/com_android_server_companion_virtual_InputController.cpp @@ -19,273 +19,22 @@ #include <android-base/unique_fd.h> #include <android/input.h> #include <android/keycodes.h> -#include <android_companion_virtualdevice_flags.h> -#include <errno.h> -#include <fcntl.h> #include <input/Input.h> #include <input/VirtualInputDevice.h> -#include <linux/uinput.h> -#include <math.h> #include <nativehelper/JNIHelp.h> #include <nativehelper/ScopedUtfChars.h> -#include <utils/Log.h> -#include <map> -#include <set> #include <string> using android::base::unique_fd; namespace android { -namespace vd_flags = android::companion::virtualdevice::flags; - static constexpr jlong INVALID_PTR = 0; -enum class DeviceType { - KEYBOARD, - MOUSE, - TOUCHSCREEN, - DPAD, - STYLUS, - ROTARY_ENCODER, -}; - -static unique_fd invalidFd() { - return unique_fd(-1); -} - -/** Creates a new uinput device and assigns a file descriptor. */ -static unique_fd openUinput(const char* readableName, jint vendorId, jint productId, - const char* phys, DeviceType deviceType, jint screenHeight, - jint screenWidth) { - unique_fd fd(TEMP_FAILURE_RETRY(::open("/dev/uinput", O_WRONLY | O_NONBLOCK))); - if (fd < 0) { - ALOGE("Error creating uinput device: %s", strerror(errno)); - return invalidFd(); - } - - ioctl(fd, UI_SET_PHYS, phys); - - ioctl(fd, UI_SET_EVBIT, EV_KEY); - ioctl(fd, UI_SET_EVBIT, EV_SYN); - switch (deviceType) { - case DeviceType::DPAD: - for (const auto& [_, keyCode] : VirtualDpad::DPAD_KEY_CODE_MAPPING) { - ioctl(fd, UI_SET_KEYBIT, keyCode); - } - break; - case DeviceType::KEYBOARD: - for (const auto& [_, keyCode] : VirtualKeyboard::KEY_CODE_MAPPING) { - ioctl(fd, UI_SET_KEYBIT, keyCode); - } - break; - case DeviceType::MOUSE: - ioctl(fd, UI_SET_EVBIT, EV_REL); - ioctl(fd, UI_SET_KEYBIT, BTN_LEFT); - ioctl(fd, UI_SET_KEYBIT, BTN_RIGHT); - ioctl(fd, UI_SET_KEYBIT, BTN_MIDDLE); - ioctl(fd, UI_SET_KEYBIT, BTN_BACK); - ioctl(fd, UI_SET_KEYBIT, BTN_FORWARD); - ioctl(fd, UI_SET_RELBIT, REL_X); - ioctl(fd, UI_SET_RELBIT, REL_Y); - ioctl(fd, UI_SET_RELBIT, REL_WHEEL); - ioctl(fd, UI_SET_RELBIT, REL_HWHEEL); - if (vd_flags::high_resolution_scroll()) { - ioctl(fd, UI_SET_RELBIT, REL_WHEEL_HI_RES); - ioctl(fd, UI_SET_RELBIT, REL_HWHEEL_HI_RES); - } - break; - case DeviceType::TOUCHSCREEN: - ioctl(fd, UI_SET_EVBIT, EV_ABS); - ioctl(fd, UI_SET_KEYBIT, BTN_TOUCH); - ioctl(fd, UI_SET_ABSBIT, ABS_MT_SLOT); - ioctl(fd, UI_SET_ABSBIT, ABS_MT_POSITION_X); - ioctl(fd, UI_SET_ABSBIT, ABS_MT_POSITION_Y); - ioctl(fd, UI_SET_ABSBIT, ABS_MT_TRACKING_ID); - ioctl(fd, UI_SET_ABSBIT, ABS_MT_TOOL_TYPE); - ioctl(fd, UI_SET_ABSBIT, ABS_MT_TOUCH_MAJOR); - ioctl(fd, UI_SET_ABSBIT, ABS_MT_PRESSURE); - ioctl(fd, UI_SET_PROPBIT, INPUT_PROP_DIRECT); - break; - case DeviceType::STYLUS: - ioctl(fd, UI_SET_EVBIT, EV_ABS); - ioctl(fd, UI_SET_KEYBIT, BTN_TOUCH); - ioctl(fd, UI_SET_KEYBIT, BTN_STYLUS); - ioctl(fd, UI_SET_KEYBIT, BTN_STYLUS2); - ioctl(fd, UI_SET_KEYBIT, BTN_TOOL_PEN); - ioctl(fd, UI_SET_KEYBIT, BTN_TOOL_RUBBER); - ioctl(fd, UI_SET_ABSBIT, ABS_X); - ioctl(fd, UI_SET_ABSBIT, ABS_Y); - ioctl(fd, UI_SET_ABSBIT, ABS_TILT_X); - ioctl(fd, UI_SET_ABSBIT, ABS_TILT_Y); - ioctl(fd, UI_SET_ABSBIT, ABS_PRESSURE); - ioctl(fd, UI_SET_PROPBIT, INPUT_PROP_DIRECT); - break; - case DeviceType::ROTARY_ENCODER: - ioctl(fd, UI_SET_EVBIT, EV_REL); - ioctl(fd, UI_SET_RELBIT, REL_WHEEL); - if (vd_flags::high_resolution_scroll()) { - ioctl(fd, UI_SET_RELBIT, REL_WHEEL_HI_RES); - } - break; - default: - ALOGE("Invalid input device type %d", static_cast<int32_t>(deviceType)); - return invalidFd(); - } - - int version; - if (ioctl(fd, UI_GET_VERSION, &version) == 0 && version >= 5) { - uinput_setup setup; - memset(&setup, 0, sizeof(setup)); - strlcpy(setup.name, readableName, UINPUT_MAX_NAME_SIZE); - setup.id.version = 1; - setup.id.bustype = BUS_VIRTUAL; - setup.id.vendor = vendorId; - setup.id.product = productId; - if (deviceType == DeviceType::TOUCHSCREEN) { - uinput_abs_setup xAbsSetup; - xAbsSetup.code = ABS_MT_POSITION_X; - xAbsSetup.absinfo.maximum = screenWidth - 1; - xAbsSetup.absinfo.minimum = 0; - if (ioctl(fd, UI_ABS_SETUP, &xAbsSetup) != 0) { - ALOGE("Error creating touchscreen uinput x axis: %s", strerror(errno)); - return invalidFd(); - } - uinput_abs_setup yAbsSetup; - yAbsSetup.code = ABS_MT_POSITION_Y; - yAbsSetup.absinfo.maximum = screenHeight - 1; - yAbsSetup.absinfo.minimum = 0; - if (ioctl(fd, UI_ABS_SETUP, &yAbsSetup) != 0) { - ALOGE("Error creating touchscreen uinput y axis: %s", strerror(errno)); - return invalidFd(); - } - uinput_abs_setup majorAbsSetup; - majorAbsSetup.code = ABS_MT_TOUCH_MAJOR; - majorAbsSetup.absinfo.maximum = screenWidth - 1; - majorAbsSetup.absinfo.minimum = 0; - if (ioctl(fd, UI_ABS_SETUP, &majorAbsSetup) != 0) { - ALOGE("Error creating touchscreen uinput major axis: %s", strerror(errno)); - return invalidFd(); - } - uinput_abs_setup pressureAbsSetup; - pressureAbsSetup.code = ABS_MT_PRESSURE; - pressureAbsSetup.absinfo.maximum = 255; - pressureAbsSetup.absinfo.minimum = 0; - if (ioctl(fd, UI_ABS_SETUP, &pressureAbsSetup) != 0) { - ALOGE("Error creating touchscreen uinput pressure axis: %s", strerror(errno)); - return invalidFd(); - } - uinput_abs_setup slotAbsSetup; - slotAbsSetup.code = ABS_MT_SLOT; - slotAbsSetup.absinfo.maximum = MAX_POINTERS - 1; - slotAbsSetup.absinfo.minimum = 0; - if (ioctl(fd, UI_ABS_SETUP, &slotAbsSetup) != 0) { - ALOGE("Error creating touchscreen uinput slots: %s", strerror(errno)); - return invalidFd(); - } - uinput_abs_setup trackingIdAbsSetup; - trackingIdAbsSetup.code = ABS_MT_TRACKING_ID; - trackingIdAbsSetup.absinfo.maximum = MAX_POINTERS - 1; - trackingIdAbsSetup.absinfo.minimum = 0; - if (ioctl(fd, UI_ABS_SETUP, &trackingIdAbsSetup) != 0) { - ALOGE("Error creating touchscreen uinput tracking ids: %s", strerror(errno)); - return invalidFd(); - } - } else if (deviceType == DeviceType::STYLUS) { - uinput_abs_setup xAbsSetup; - xAbsSetup.code = ABS_X; - xAbsSetup.absinfo.maximum = screenWidth - 1; - xAbsSetup.absinfo.minimum = 0; - if (ioctl(fd, UI_ABS_SETUP, &xAbsSetup) != 0) { - ALOGE("Error creating stylus uinput x axis: %s", strerror(errno)); - return invalidFd(); - } - uinput_abs_setup yAbsSetup; - yAbsSetup.code = ABS_Y; - yAbsSetup.absinfo.maximum = screenHeight - 1; - yAbsSetup.absinfo.minimum = 0; - if (ioctl(fd, UI_ABS_SETUP, &yAbsSetup) != 0) { - ALOGE("Error creating stylus uinput y axis: %s", strerror(errno)); - return invalidFd(); - } - uinput_abs_setup tiltXAbsSetup; - tiltXAbsSetup.code = ABS_TILT_X; - tiltXAbsSetup.absinfo.maximum = 90; - tiltXAbsSetup.absinfo.minimum = -90; - if (ioctl(fd, UI_ABS_SETUP, &tiltXAbsSetup) != 0) { - ALOGE("Error creating stylus uinput tilt x axis: %s", strerror(errno)); - return invalidFd(); - } - uinput_abs_setup tiltYAbsSetup; - tiltYAbsSetup.code = ABS_TILT_Y; - tiltYAbsSetup.absinfo.maximum = 90; - tiltYAbsSetup.absinfo.minimum = -90; - if (ioctl(fd, UI_ABS_SETUP, &tiltYAbsSetup) != 0) { - ALOGE("Error creating stylus uinput tilt y axis: %s", strerror(errno)); - return invalidFd(); - } - uinput_abs_setup pressureAbsSetup; - pressureAbsSetup.code = ABS_PRESSURE; - pressureAbsSetup.absinfo.maximum = 255; - pressureAbsSetup.absinfo.minimum = 0; - if (ioctl(fd, UI_ABS_SETUP, &pressureAbsSetup) != 0) { - ALOGE("Error creating touchscreen uinput pressure axis: %s", strerror(errno)); - return invalidFd(); - } - } - if (ioctl(fd, UI_DEV_SETUP, &setup) != 0) { - ALOGE("Error creating uinput device: %s", strerror(errno)); - return invalidFd(); - } - } else { - // UI_DEV_SETUP was not introduced until version 5. Try setting up manually. - ALOGI("Falling back to version %d manual setup", version); - uinput_user_dev fallback; - memset(&fallback, 0, sizeof(fallback)); - strlcpy(fallback.name, readableName, UINPUT_MAX_NAME_SIZE); - fallback.id.version = 1; - fallback.id.bustype = BUS_VIRTUAL; - fallback.id.vendor = vendorId; - fallback.id.product = productId; - if (deviceType == DeviceType::TOUCHSCREEN) { - fallback.absmin[ABS_MT_POSITION_X] = 0; - fallback.absmax[ABS_MT_POSITION_X] = screenWidth - 1; - fallback.absmin[ABS_MT_POSITION_Y] = 0; - fallback.absmax[ABS_MT_POSITION_Y] = screenHeight - 1; - fallback.absmin[ABS_MT_TOUCH_MAJOR] = 0; - fallback.absmax[ABS_MT_TOUCH_MAJOR] = screenWidth - 1; - fallback.absmin[ABS_MT_PRESSURE] = 0; - fallback.absmax[ABS_MT_PRESSURE] = 255; - } else if (deviceType == DeviceType::STYLUS) { - fallback.absmin[ABS_X] = 0; - fallback.absmax[ABS_X] = screenWidth - 1; - fallback.absmin[ABS_Y] = 0; - fallback.absmax[ABS_Y] = screenHeight - 1; - fallback.absmin[ABS_TILT_X] = -90; - fallback.absmax[ABS_TILT_X] = 90; - fallback.absmin[ABS_TILT_Y] = -90; - fallback.absmax[ABS_TILT_Y] = 90; - fallback.absmin[ABS_PRESSURE] = 0; - fallback.absmax[ABS_PRESSURE] = 255; - } - if (TEMP_FAILURE_RETRY(write(fd, &fallback, sizeof(fallback))) != sizeof(fallback)) { - ALOGE("Error creating uinput device: %s", strerror(errno)); - return invalidFd(); - } - } - - if (ioctl(fd, UI_DEV_CREATE) != 0) { - ALOGE("Error creating uinput device: %s", strerror(errno)); - return invalidFd(); - } - - return fd; -} - static unique_fd openUinputJni(JNIEnv* env, jstring name, jint vendorId, jint productId, - jstring phys, DeviceType deviceType, int screenHeight, - int screenWidth) { + jstring phys, DeviceType deviceType, jint screenHeight, + jint screenWidth) { ScopedUtfChars readableName(env, name); ScopedUtfChars readablePhys(env, phys); return openUinput(readableName.c_str(), vendorId, productId, readablePhys.c_str(), deviceType, diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java index f86d307d97bd..19a942cd2eed 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java @@ -129,9 +129,8 @@ final class PolicyDefinition<V> { */ static PolicyDefinition<Integer> PERMISSION_GRANT( @NonNull String packageName, @NonNull String permissionName) { - if (packageName == null || permissionName == null) { - return GENERIC_PERMISSION_GRANT; - } + Objects.requireNonNull(packageName, "packageName must not be null"); + Objects.requireNonNull(permissionName, "permissionName must not be null"); return GENERIC_PERMISSION_GRANT.createPolicyDefinition( new PackagePermissionPolicyKey( DevicePolicyIdentifiers.PERMISSION_GRANT_POLICY, @@ -190,10 +189,8 @@ final class PolicyDefinition<V> { * {@link #GENERIC_PERSISTENT_PREFERRED_ACTIVITY}. */ static PolicyDefinition<ComponentName> PERSISTENT_PREFERRED_ACTIVITY( - IntentFilter intentFilter) { - if (intentFilter == null) { - return GENERIC_PERSISTENT_PREFERRED_ACTIVITY; - } + @NonNull IntentFilter intentFilter) { + Objects.requireNonNull(intentFilter, "intentFilter must not be null"); return GENERIC_PERSISTENT_PREFERRED_ACTIVITY.createPolicyDefinition( new IntentFilterPolicyKey( DevicePolicyIdentifiers.PERSISTENT_PREFERRED_ACTIVITY_POLICY, @@ -216,11 +213,8 @@ final class PolicyDefinition<V> { * Passing in {@code null} for {@code packageName} will return * {@link #GENERIC_PACKAGE_UNINSTALL_BLOCKED}. */ - static PolicyDefinition<Boolean> PACKAGE_UNINSTALL_BLOCKED( - String packageName) { - if (packageName == null) { - return GENERIC_PACKAGE_UNINSTALL_BLOCKED; - } + static PolicyDefinition<Boolean> PACKAGE_UNINSTALL_BLOCKED(@NonNull String packageName) { + Objects.requireNonNull(packageName, "packageName must not be null"); return GENERIC_PACKAGE_UNINSTALL_BLOCKED.createPolicyDefinition( new PackagePolicyKey( DevicePolicyIdentifiers.PACKAGE_UNINSTALL_BLOCKED_POLICY, packageName)); @@ -247,10 +241,8 @@ final class PolicyDefinition<V> { * Passing in {@code null} for {@code packageName} will return * {@link #GENERIC_APPLICATION_RESTRICTIONS}. */ - static PolicyDefinition<Bundle> APPLICATION_RESTRICTIONS(String packageName) { - if (packageName == null) { - return GENERIC_APPLICATION_RESTRICTIONS; - } + static PolicyDefinition<Bundle> APPLICATION_RESTRICTIONS(@NonNull String packageName) { + Objects.requireNonNull(packageName, "packageName must not be null"); return GENERIC_APPLICATION_RESTRICTIONS.createPolicyDefinition( new PackagePolicyKey( DevicePolicyIdentifiers.APPLICATION_RESTRICTIONS_POLICY, packageName)); @@ -293,10 +285,8 @@ final class PolicyDefinition<V> { * Passing in {@code null} for {@code packageName} will return * {@link #GENERIC_APPLICATION_HIDDEN}. */ - static PolicyDefinition<Boolean> APPLICATION_HIDDEN(String packageName) { - if (packageName == null) { - return GENERIC_APPLICATION_HIDDEN; - } + static PolicyDefinition<Boolean> APPLICATION_HIDDEN(@NonNull String packageName) { + Objects.requireNonNull(packageName, "packageName must not be null"); return GENERIC_APPLICATION_HIDDEN.createPolicyDefinition( new PackagePolicyKey( DevicePolicyIdentifiers.APPLICATION_HIDDEN_POLICY, packageName)); @@ -319,10 +309,8 @@ final class PolicyDefinition<V> { * Passing in {@code null} for {@code accountType} will return * {@link #GENERIC_ACCOUNT_MANAGEMENT_DISABLED}. */ - static PolicyDefinition<Boolean> ACCOUNT_MANAGEMENT_DISABLED(String accountType) { - if (accountType == null) { - return GENERIC_ACCOUNT_MANAGEMENT_DISABLED; - } + static PolicyDefinition<Boolean> ACCOUNT_MANAGEMENT_DISABLED(@NonNull String accountType) { + Objects.requireNonNull(accountType, "accountType must not be null"); return GENERIC_ACCOUNT_MANAGEMENT_DISABLED.createPolicyDefinition( new AccountTypePolicyKey( DevicePolicyIdentifiers.ACCOUNT_MANAGEMENT_DISABLED_POLICY, accountType)); diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 9e8811f419a2..09c54cb40373 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -16,6 +16,7 @@ package com.android.server; +import static android.app.appfunctions.flags.Flags.enableAppFunctionManager; import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK; import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL; import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_HIGH; @@ -105,6 +106,7 @@ import com.android.internal.notification.SystemNotificationChannels; import com.android.internal.os.BinderInternal; import com.android.internal.os.RuntimeInit; import com.android.internal.policy.AttributeCache; +import com.android.internal.protolog.ProtoLogService; import com.android.internal.util.ConcurrentUtils; import com.android.internal.util.EmergencyAffordanceManager; import com.android.internal.util.FrameworkStatsLog; @@ -119,6 +121,7 @@ import com.android.server.am.ActivityManagerService; import com.android.server.ambientcontext.AmbientContextManagerService; import com.android.server.app.GameManagerService; import com.android.server.appbinding.AppBindingService; +import com.android.server.appfunctions.AppFunctionManagerService; import com.android.server.apphibernation.AppHibernationService; import com.android.server.appop.AppOpMigrationHelper; import com.android.server.appop.AppOpMigrationHelperImpl; @@ -1087,6 +1090,13 @@ public final class SystemServer implements Dumpable { SystemServerInitThreadPool.submit(SystemConfig::getInstance, TAG_SYSTEM_CONFIG); t.traceEnd(); + // Orchestrates some ProtoLogging functionality. + if (android.tracing.Flags.clientSideProtoLogging()) { + t.traceBegin("StartProtoLogService"); + ServiceManager.addService(Context.PROTOLOG_SERVICE, new ProtoLogService()); + t.traceEnd(); + } + // Platform compat service is used by ActivityManagerService, PackageManagerService, and // possibly others in the future. b/135010838. t.traceBegin("PlatformCompat"); @@ -1719,6 +1729,12 @@ public final class SystemServer implements Dumpable { mSystemServiceManager.startService(LogcatManagerService.class); t.traceEnd(); + t.traceBegin("StartAppFunctionManager"); + if (enableAppFunctionManager()) { + mSystemServiceManager.startService(AppFunctionManagerService.class); + } + t.traceEnd(); + } catch (Throwable e) { Slog.e("System", "******************************************"); Slog.e("System", "************ Failure starting core service"); diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java index aee7242d4604..770451cc838d 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java @@ -44,7 +44,6 @@ import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.android.server.inputmethod.InputMethodSubtypeSwitchingController.ControllerImpl; import com.android.server.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem; import org.junit.Rule; @@ -178,19 +177,20 @@ public final class InputMethodSubtypeSwitchingControllerTest { return items; } - private void assertNextInputMethod(@NonNull ControllerImpl controller, boolean onlyCurrentIme, - @NonNull ImeSubtypeListItem currentItem, @Nullable ImeSubtypeListItem nextItem) { + private void assertNextInputMethod(@NonNull InputMethodSubtypeSwitchingController controller, + boolean onlyCurrentIme, @NonNull ImeSubtypeListItem currentItem, + @Nullable ImeSubtypeListItem nextItem) { InputMethodSubtype subtype = null; if (currentItem.mSubtypeName != null) { subtype = createTestSubtype(currentItem.mSubtypeName.toString()); } - final ImeSubtypeListItem nextIme = controller.getNextInputMethod(onlyCurrentIme, + final ImeSubtypeListItem nextIme = controller.getNextInputMethodLocked(onlyCurrentIme, currentItem.mImi, subtype, MODE_STATIC, true /* forward */); assertEquals(nextItem, nextIme); } - private void assertRotationOrder(@NonNull ControllerImpl controller, boolean onlyCurrentIme, - ImeSubtypeListItem... expectedRotationOrderOfImeSubtypeList) { + private void assertRotationOrder(@NonNull InputMethodSubtypeSwitchingController controller, + boolean onlyCurrentIme, ImeSubtypeListItem... expectedRotationOrderOfImeSubtypeList) { final int numItems = expectedRotationOrderOfImeSubtypeList.length; for (int i = 0; i < numItems; i++) { final int nextIndex = (i + 1) % numItems; @@ -200,7 +200,7 @@ public final class InputMethodSubtypeSwitchingControllerTest { } } - private boolean onUserAction(@NonNull ControllerImpl controller, + private boolean onUserAction(@NonNull InputMethodSubtypeSwitchingController controller, @NonNull ImeSubtypeListItem subtypeListItem) { InputMethodSubtype subtype = null; if (subtypeListItem.mSubtypeName != null) { @@ -228,8 +228,8 @@ public final class InputMethodSubtypeSwitchingControllerTest { final ImeSubtypeListItem japaneseIme_ja_jp = enabledItems.get(6); final ImeSubtypeListItem switchUnawareJapaneseIme_ja_jp = enabledItems.get(7); - final ControllerImpl controller = ControllerImpl.createFrom( - null /* currentInstance */, enabledItems, new ArrayList<>()); + final var controller = new InputMethodSubtypeSwitchingController(); + controller.update(enabledItems, new ArrayList<>()); // switching-aware loop assertRotationOrder(controller, false /* onlyCurrentIme */, @@ -286,8 +286,8 @@ public final class InputMethodSubtypeSwitchingControllerTest { final ImeSubtypeListItem japaneseIme_ja_jp = enabledItems.get(6); final ImeSubtypeListItem switchUnawareJapaneseIme_ja_jp = enabledItems.get(7); - final ControllerImpl controller = ControllerImpl.createFrom( - null /* currentInstance */, enabledItems, new ArrayList<>()); + final var controller = new InputMethodSubtypeSwitchingController(); + controller.update(enabledItems, new ArrayList<>()); // === switching-aware loop === assertRotationOrder(controller, false /* onlyCurrentIme */, @@ -336,11 +336,10 @@ public final class InputMethodSubtypeSwitchingControllerTest { // Rotation order should be preserved when created with the same subtype list. final List<ImeSubtypeListItem> sameEnabledItems = createEnabledImeSubtypes(); - final ControllerImpl newController = ControllerImpl.createFrom(controller, - sameEnabledItems, new ArrayList<>()); - assertRotationOrder(newController, false /* onlyCurrentIme */, + controller.update(sameEnabledItems, new ArrayList<>()); + assertRotationOrder(controller, false /* onlyCurrentIme */, subtypeAwareIme, latinIme_fr, latinIme_en_us, japaneseIme_ja_jp); - assertRotationOrder(newController, false /* onlyCurrentIme */, + assertRotationOrder(controller, false /* onlyCurrentIme */, switchingUnawareLatinIme_en_uk, switchingUnawareLatinIme_hi, subtypeUnawareIme, switchUnawareJapaneseIme_ja_jp); @@ -348,11 +347,10 @@ public final class InputMethodSubtypeSwitchingControllerTest { final List<ImeSubtypeListItem> differentEnabledItems = List.of( latinIme_en_us, latinIme_fr, subtypeAwareIme, switchingUnawareLatinIme_en_uk, switchUnawareJapaneseIme_ja_jp, subtypeUnawareIme); - final ControllerImpl anotherController = ControllerImpl.createFrom(controller, - differentEnabledItems, new ArrayList<>()); - assertRotationOrder(anotherController, false /* onlyCurrentIme */, + controller.update(differentEnabledItems, new ArrayList<>()); + assertRotationOrder(controller, false /* onlyCurrentIme */, latinIme_en_us, latinIme_fr, subtypeAwareIme); - assertRotationOrder(anotherController, false /* onlyCurrentIme */, + assertRotationOrder(controller, false /* onlyCurrentIme */, switchingUnawareLatinIme_en_uk, switchUnawareJapaneseIme_ja_jp, subtypeUnawareIme); } @@ -520,8 +518,8 @@ public final class InputMethodSubtypeSwitchingControllerTest { final var hardwareLatinIme = List.of(hardwareEnglish, hardwareFrench, hardwareItalian); final var hardwareSimpleIme = List.of(hardwareSimple); - final var controller = ControllerImpl.createFrom(null /* currentInstance */, items, - hardwareItems); + final var controller = new InputMethodSubtypeSwitchingController(); + controller.update(items, hardwareItems); final int mode = MODE_STATIC; @@ -583,8 +581,8 @@ public final class InputMethodSubtypeSwitchingControllerTest { final var hardwareLatinIme = List.of(hardwareEnglish, hardwareFrench, hardwareItalian); final var hardwareSimpleIme = List.of(hardwareSimple); - final var controller = ControllerImpl.createFrom(null /* currentInstance */, items, - hardwareItems); + final var controller = new InputMethodSubtypeSwitchingController(); + controller.update(items, hardwareItems); final int mode = MODE_RECENT; @@ -666,8 +664,8 @@ public final class InputMethodSubtypeSwitchingControllerTest { final var hardwareLatinIme = List.of(hardwareEnglish, hardwareFrench, hardwareItalian); final var hardwareSimpleIme = List.of(hardwareSimple); - final var controller = ControllerImpl.createFrom(null /* currentInstance */, items, - hardwareItems); + final var controller = new InputMethodSubtypeSwitchingController(); + controller.update(items, hardwareItems); final int mode = MODE_AUTO; @@ -777,92 +775,73 @@ public final class InputMethodSubtypeSwitchingControllerTest { final var hardwareLatinIme = List.of(hardwareEnglish, hardwareFrench, hardwareItalian); final var hardwareSimpleIme = List.of(hardwareSimple); - final var controller = ControllerImpl.createFrom(null /* currentInstance */, items, - hardwareItems); + final var controller = new InputMethodSubtypeSwitchingController(); + controller.update(items, hardwareItems); final int mode = MODE_RECENT; // Recency order is initialized to static order. assertNextOrder(controller, false /* forHardware */, mode, items, List.of(latinIme, simpleIme)); - assertNextOrder(controller, true /* forHardware */, mode, hardwareItems, List.of(hardwareLatinIme, hardwareSimpleIme)); // User action on french IME. assertTrue("Recency updated for french IME", onUserAction(controller, french)); - final var equalItems = new ArrayList<>(items); - final var otherItems = new ArrayList<>(items); - otherItems.remove(simple); - - final var equalController = ControllerImpl.createFrom(controller, equalItems, - hardwareItems); - final var otherController = ControllerImpl.createFrom(controller, otherItems, - hardwareItems); - final var recencyItems = List.of(french, english, italian, simple); final var recencyLatinIme = List.of(french, english, italian); final var recencySimpleIme = List.of(simple); - assertNextOrder(controller, false /* forHardware */, mode, - recencyItems, List.of(recencyLatinIme, recencySimpleIme)); + final var equalItems = new ArrayList<>(items); + controller.update(equalItems, hardwareItems); - // The order of equal non-hardware items is unchanged. - assertNextOrder(equalController, false /* forHardware */, mode, + // The order of non-hardware items remains unchanged when updated with equal items. + assertNextOrder(controller, false /* forHardware */, mode, recencyItems, List.of(recencyLatinIme, recencySimpleIme)); - - // The order of other hardware items is reset. - assertNextOrder(otherController, false /* forHardware */, mode, - latinIme, List.of(latinIme)); - - // The order of hardware remains unchanged. + // The order of hardware items remains unchanged when only non-hardware items are updated. assertNextOrder(controller, true /* forHardware */, mode, hardwareItems, List.of(hardwareLatinIme, hardwareSimpleIme)); - assertNextOrder(equalController, true /* forHardware */, mode, - hardwareItems, List.of(hardwareLatinIme, hardwareSimpleIme)); + final var otherItems = new ArrayList<>(items); + otherItems.remove(simple); + controller.update(otherItems, hardwareItems); - assertNextOrder(otherController, true /* forHardware */, mode, + // The order of non-hardware items is reset when updated with other items. + assertNextOrder(controller, false /* forHardware */, mode, + latinIme, List.of(latinIme)); + // The order of hardware items remains unchanged when only non-hardware items are updated. + assertNextOrder(controller, true /* forHardware */, mode, hardwareItems, List.of(hardwareLatinIme, hardwareSimpleIme)); assertTrue("Recency updated for french hardware IME", onUserAction(controller, hardwareFrench)); - final var equalHardwareItems = new ArrayList<>(hardwareItems); - final var otherHardwareItems = new ArrayList<>(hardwareItems); - otherHardwareItems.remove(hardwareSimple); - - final var equalHardwareController = ControllerImpl.createFrom(controller, items, - equalHardwareItems); - final var otherHardwareController = ControllerImpl.createFrom(controller, items, - otherHardwareItems); - final var recencyHardwareItems = List.of(hardwareFrench, hardwareEnglish, hardwareItalian, hardwareSimple); final var recencyHardwareLatinIme = List.of(hardwareFrench, hardwareEnglish, hardwareItalian); final var recencyHardwareSimpleIme = List.of(hardwareSimple); - // The order of non-hardware items remains unchanged. - assertNextOrder(controller, false /* forHardware */, mode, - recencyItems, List.of(recencyLatinIme, recencySimpleIme)); - - assertNextOrder(equalHardwareController, false /* forHardware */, mode, - recencyItems, List.of(recencyLatinIme, recencySimpleIme)); - - assertNextOrder(otherHardwareController, false /* forHardware */, mode, - recencyItems, List.of(recencyLatinIme, recencySimpleIme)); + final var equalHardwareItems = new ArrayList<>(hardwareItems); + controller.update(otherItems, equalHardwareItems); + // The order of non-hardware items remains unchanged when only hardware items are updated. + assertNextOrder(controller, false /* forHardware */, mode, + latinIme, List.of(latinIme)); + // The order of hardware items remains unchanged when updated with equal items. assertNextOrder(controller, true /* forHardware */, mode, recencyHardwareItems, List.of(recencyHardwareLatinIme, recencyHardwareSimpleIme)); - // The order of equal hardware items is unchanged. - assertNextOrder(equalHardwareController, true /* forHardware */, mode, - recencyHardwareItems, List.of(recencyHardwareLatinIme, recencyHardwareSimpleIme)); + final var otherHardwareItems = new ArrayList<>(hardwareItems); + otherHardwareItems.remove(hardwareSimple); + controller.update(otherItems, otherHardwareItems); - // The order of other hardware items is reset. - assertNextOrder(otherHardwareController, true /* forHardware */, mode, + // The order of non-hardware items remains unchanged when only hardware items are updated. + assertNextOrder(controller, false /* forHardware */, mode, + latinIme, List.of(latinIme)); + // The order of hardware items is reset when updated with other items. + assertNextOrder(controller, true /* forHardware */, mode, hardwareLatinIme, List.of(hardwareLatinIme)); } @@ -882,8 +861,8 @@ public final class InputMethodSubtypeSwitchingControllerTest { addTestImeSubtypeListItems(hardwareItems, "hardwareSwitchUnaware", "hardwareSwitchUnaware", null, false /* supportsSwitchingToNextInputMethod*/); - final var controller = ControllerImpl.createFrom(null /* currentInstance */, items, - hardwareItems); + final var controller = new InputMethodSubtypeSwitchingController(); + controller.update(items, hardwareItems); for (int mode = MODE_STATIC; mode <= MODE_AUTO; mode++) { assertNextOrder(controller, false /* forHardware */, false /* onlyCurrentIme */, @@ -910,55 +889,102 @@ public final class InputMethodSubtypeSwitchingControllerTest { addTestImeSubtypeListItems(hardwareItems, "HardwareIme", "HardwareIme", List.of("en", "fr"), true /* supportsSwitchingToNextInputMethod */); - final var controller = ControllerImpl.createFrom(null /* currentInstance */, List.of(), - List.of()); + final var controller = new InputMethodSubtypeSwitchingController(); - assertNoAction(controller, false /* forHardware */, items); - assertNoAction(controller, true /* forHardware */, hardwareItems); + assertNextItemNoAction(controller, false /* forHardware */, items, + null /* expectedNext */); + assertNextItemNoAction(controller, true /* forHardware */, hardwareItems, + null /* expectedNext */); } - /** Verifies that a controller with a single item can't take any actions. */ + /** + * Verifies that a controller with a single item can't update the recency, and cannot switch + * away from the item, but allows switching from unknown items to the single item. + */ @RequiresFlagsEnabled(Flags.FLAG_IME_SWITCHER_REVAMP) @Test public void testSingleItemList() { final var items = new ArrayList<ImeSubtypeListItem>(); addTestImeSubtypeListItems(items, "LatinIme", "LatinIme", - List.of("en", "fr"), true /* supportsSwitchingToNextInputMethod */); + null, true /* supportsSwitchingToNextInputMethod */); + final var unknownItems = new ArrayList<ImeSubtypeListItem>(); + addTestImeSubtypeListItems(unknownItems, "UnknownIme", "UnknownIme", + List.of("en", "fr", "it"), true /* supportsSwitchingToNextInputMethod */); final var hardwareItems = new ArrayList<ImeSubtypeListItem>(); addTestImeSubtypeListItems(hardwareItems, "HardwareIme", "HardwareIme", - List.of("en", "fr"), true /* supportsSwitchingToNextInputMethod */); - - final var controller = ControllerImpl.createFrom(null /* currentInstance */, - List.of(items.get(0)), List.of(hardwareItems.get(0))); + null, true /* supportsSwitchingToNextInputMethod */); + final var unknownHardwareItems = new ArrayList<ImeSubtypeListItem>(); + addTestImeSubtypeListItems(unknownHardwareItems, "HardwareUnknownIme", "HardwareUnknownIme", + List.of("en", "fr", "it"), true /* supportsSwitchingToNextInputMethod */); - assertNoAction(controller, false /* forHardware */, items); - assertNoAction(controller, true /* forHardware */, hardwareItems); + final var controller = new InputMethodSubtypeSwitchingController(); + controller.update(items, hardwareItems); + + assertNextItemNoAction(controller, false /* forHardware */, items, + null /* expectedNext */); + assertNextItemNoAction(controller, false /* forHardware */, unknownItems, + items.get(0)); + assertNextItemNoAction(controller, true /* forHardware */, hardwareItems, + null /* expectedNext */); + assertNextItemNoAction(controller, true /* forHardware */, unknownHardwareItems, + hardwareItems.get(0)); } - /** Verifies that a controller can't take any actions for unknown items. */ + /** + * Verifies that the recency cannot be updated for unknown items, but switching from unknown + * items reaches the most recent known item. + */ @RequiresFlagsEnabled(Flags.FLAG_IME_SWITCHER_REVAMP) @Test public void testUnknownItems() { final var items = new ArrayList<ImeSubtypeListItem>(); addTestImeSubtypeListItems(items, "LatinIme", "LatinIme", - List.of("en", "fr"), true /* supportsSwitchingToNextInputMethod */); + List.of("en", "fr", "it"), true /* supportsSwitchingToNextInputMethod */); + + final var english = items.get(0); + final var french = items.get(1); + final var italian = items.get(2); + final var unknownItems = new ArrayList<ImeSubtypeListItem>(); addTestImeSubtypeListItems(unknownItems, "UnknownIme", "UnknownIme", - List.of("en", "fr"), true /* supportsSwitchingToNextInputMethod */); + List.of("en", "fr", "it"), true /* supportsSwitchingToNextInputMethod */); final var hardwareItems = new ArrayList<ImeSubtypeListItem>(); addTestImeSubtypeListItems(hardwareItems, "HardwareIme", "HardwareIme", - List.of("en", "fr"), true /* supportsSwitchingToNextInputMethod */); + List.of("en", "fr", "it"), true /* supportsSwitchingToNextInputMethod */); final var unknownHardwareItems = new ArrayList<ImeSubtypeListItem>(); addTestImeSubtypeListItems(unknownHardwareItems, "HardwareUnknownIme", "HardwareUnknownIme", - List.of("en", "fr"), true /* supportsSwitchingToNextInputMethod */); + List.of("en", "fr", "it"), true /* supportsSwitchingToNextInputMethod */); - final var controller = ControllerImpl.createFrom(null /* currentInstance */, items, - hardwareItems); + final var controller = new InputMethodSubtypeSwitchingController(); + controller.update(items, hardwareItems); - assertNoAction(controller, false /* forHardware */, unknownItems); - assertNoAction(controller, true /* forHardware */, unknownHardwareItems); + assertTrue("Recency updated for french IME", onUserAction(controller, french)); + + final var recencyItems = List.of(french, english, italian); + + assertNextItemNoAction(controller, false /* forHardware */, unknownItems, + french); + assertNextItemNoAction(controller, true /* forHardware */, unknownHardwareItems, + hardwareItems.get(0)); + + // Known items must not be able to switch to unknown items. + assertNextOrder(controller, false /* forHardware */, MODE_STATIC, items, + List.of(items)); + assertNextOrder(controller, false /* forHardware */, MODE_RECENT, recencyItems, + List.of(recencyItems)); + assertNextOrder(controller, false /* forHardware */, MODE_AUTO, true /* forward */, + recencyItems, List.of(recencyItems)); + assertNextOrder(controller, false /* forHardware */, MODE_AUTO, false /* forward */, + items.reversed(), List.of(items.reversed())); + + assertNextOrder(controller, true /* forHardware */, MODE_STATIC, hardwareItems, + List.of(hardwareItems)); + assertNextOrder(controller, true /* forHardware */, MODE_RECENT, hardwareItems, + List.of(hardwareItems)); + assertNextOrder(controller, true /* forHardware */, MODE_AUTO, hardwareItems, + List.of(hardwareItems)); } /** Verifies that the IME name does influence the comparison order. */ @@ -1070,8 +1096,9 @@ public final class InputMethodSubtypeSwitchingControllerTest { * @param allItems the list of items across all IMEs. * @param perImeItems the list of lists of items per IME. */ - private static void assertNextOrder(@NonNull ControllerImpl controller, boolean forHardware, - @SwitchMode int mode, boolean forward, @NonNull List<ImeSubtypeListItem> allItems, + private static void assertNextOrder(@NonNull InputMethodSubtypeSwitchingController controller, + boolean forHardware, @SwitchMode int mode, boolean forward, + @NonNull List<ImeSubtypeListItem> allItems, @NonNull List<List<ImeSubtypeListItem>> perImeItems) { assertNextOrder(controller, forHardware, false /* onlyCurrentIme */, mode, forward, allItems); @@ -1094,8 +1121,8 @@ public final class InputMethodSubtypeSwitchingControllerTest { * @param allItems the list of items across all IMEs. * @param perImeItems the list of lists of items per IME. */ - private static void assertNextOrder(@NonNull ControllerImpl controller, boolean forHardware, - @SwitchMode int mode, @NonNull List<ImeSubtypeListItem> allItems, + private static void assertNextOrder(@NonNull InputMethodSubtypeSwitchingController controller, + boolean forHardware, @SwitchMode int mode, @NonNull List<ImeSubtypeListItem> allItems, @NonNull List<List<ImeSubtypeListItem>> perImeItems) { assertNextOrder(controller, forHardware, false /* onlyCurrentIme */, mode, true /* forward */, allItems); @@ -1122,7 +1149,7 @@ public final class InputMethodSubtypeSwitchingControllerTest { * @param forward whether to search forwards or backwards in the list. * @param items the list of items to verify, in the expected order. */ - private static void assertNextOrder(@NonNull ControllerImpl controller, + private static void assertNextOrder(@NonNull InputMethodSubtypeSwitchingController controller, boolean forHardware, boolean onlyCurrentIme, @SwitchMode int mode, boolean forward, @NonNull List<ImeSubtypeListItem> items) { final int numItems = items.size(); @@ -1166,7 +1193,7 @@ public final class InputMethodSubtypeSwitchingControllerTest { * @param item the item to find the next value from. * @param expectedNext the expected next value. */ - private static void assertNextItem(@NonNull ControllerImpl controller, + private static void assertNextItem(@NonNull InputMethodSubtypeSwitchingController controller, boolean forHardware, boolean onlyCurrentIme, @SwitchMode int mode, boolean forward, @NonNull ImeSubtypeListItem item, @Nullable ImeSubtypeListItem expectedNext) { final var nextItem = getNextItem(controller, forHardware, onlyCurrentIme, mode, forward, @@ -1186,38 +1213,41 @@ public final class InputMethodSubtypeSwitchingControllerTest { * @return the next item found, otherwise {@code null}. */ @Nullable - private static ImeSubtypeListItem getNextItem(@NonNull ControllerImpl controller, - boolean forHardware, boolean onlyCurrentIme, @SwitchMode int mode, boolean forward, + private static ImeSubtypeListItem getNextItem( + @NonNull InputMethodSubtypeSwitchingController controller, boolean forHardware, + boolean onlyCurrentIme, @SwitchMode int mode, boolean forward, @NonNull ImeSubtypeListItem item) { final var subtype = item.mSubtypeName != null ? createTestSubtype(item.mSubtypeName.toString()) : null; return forHardware ? controller.getNextInputMethodForHardware( onlyCurrentIme, item.mImi, subtype, mode, forward) - : controller.getNextInputMethod( + : controller.getNextInputMethodLocked( onlyCurrentIme, item.mImi, subtype, mode, forward); } /** - * Verifies that no next items can be found, and the recency cannot be updated for the + * Verifies that the expected next item is returned, and the recency cannot be updated for the * given items. * - * @param controller the controller to verify the items on. - * @param forHardware whether to try finding the next hardware item, or software item. - * @param items the list of items to verify. + * @param controller the controller to verify the items on. + * @param forHardware whether to try finding the next hardware item, or software item. + * @param items the list of items to verify. + * @param expectedNext the expected next item. */ - private void assertNoAction(@NonNull ControllerImpl controller, boolean forHardware, - @NonNull List<ImeSubtypeListItem> items) { + private void assertNextItemNoAction(@NonNull InputMethodSubtypeSwitchingController controller, + boolean forHardware, @NonNull List<ImeSubtypeListItem> items, + @Nullable ImeSubtypeListItem expectedNext) { for (var item : items) { for (int mode = MODE_STATIC; mode <= MODE_AUTO; mode++) { assertNextItem(controller, forHardware, false /* onlyCurrentIme */, mode, - false /* forward */, item, null /* expectedNext */); + false /* forward */, item, expectedNext); assertNextItem(controller, forHardware, false /* onlyCurrentIme */, mode, - true /* forward */, item, null /* expectedNext */); + true /* forward */, item, expectedNext); assertNextItem(controller, forHardware, true /* onlyCurrentIme */, mode, - false /* forward */, item, null /* expectedNext */); + false /* forward */, item, expectedNext); assertNextItem(controller, forHardware, true /* onlyCurrentIme */, mode, - true /* forward */, item, null /* expectedNext */); + true /* forward */, item, expectedNext); } assertFalse("User action shouldn't have updated the recency.", diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java index b980ca05b609..30de0e8c7981 100644 --- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java @@ -512,6 +512,9 @@ public final class AlarmManagerServiceTest { when(mPermissionManagerInternal.getAppOpPermissionPackages( SCHEDULE_EXACT_ALARM)).thenReturn(EmptyArray.STRING); + // Initialize timestamps with arbitrary values of time + mNowElapsedTest = 12; + mNowRtcTest = 345; mInjector = new Injector(mMockContext); mService = new AlarmManagerService(mMockContext, mInjector); spyOn(mService); @@ -774,6 +777,61 @@ public final class AlarmManagerServiceTest { } @Test + public void timeChangeBroadcastForward() throws Exception { + final long timeDelta = 12345; + // AlarmManagerService sends the broadcast if real time clock proceeds 1000ms more than boot + // time clock. + mNowRtcTest += timeDelta + 1001; + mNowElapsedTest += timeDelta; + mTestTimer.expire(TIME_CHANGED_MASK); + + verify(mMockContext) + .sendBroadcastAsUser( + argThat((intent) -> intent.getAction() == Intent.ACTION_TIME_CHANGED), + eq(UserHandle.ALL), + isNull(), + any()); + } + + @Test + public void timeChangeBroadcastBackward() throws Exception { + final long timeDelta = 12345; + // AlarmManagerService sends the broadcast if real time clock proceeds 1000ms less than boot + // time clock. + mNowRtcTest += timeDelta - 1001; + mNowElapsedTest += timeDelta; + mTestTimer.expire(TIME_CHANGED_MASK); + + verify(mMockContext) + .sendBroadcastAsUser( + argThat((intent) -> intent.getAction() == Intent.ACTION_TIME_CHANGED), + eq(UserHandle.ALL), + isNull(), + any()); + } + + @Test + public void timeChangeFilterMinorAdjustment() throws Exception { + final long timeDelta = 12345; + // AlarmManagerService does not send the broadcast if real time clock proceeds within 1000ms + // than boot time clock. + mNowRtcTest += timeDelta + 1000; + mNowElapsedTest += timeDelta; + mTestTimer.expire(TIME_CHANGED_MASK); + + mNowRtcTest += timeDelta - 1000; + mNowElapsedTest += timeDelta; + mTestTimer.expire(TIME_CHANGED_MASK); + + verify(mMockContext, never()) + .sendBroadcastAsUser( + argThat((intent) -> intent.getAction() == Intent.ACTION_TIME_CHANGED), + any(), + any(), + any()); + } + + @Test public void testSingleAlarmExpiration() throws Exception { final long triggerTime = mNowElapsedTest + 5000; final PendingIntent alarmPi = getNewMockPendingIntent(); diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt index 9ab607de474d..0a6edf1b9831 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt @@ -314,6 +314,7 @@ class MockSystem(withSession: (StaticMockitoSessionBuilder) -> Unit = {}) { whenever(mocks.systemConfig.defaultVrComponents).thenReturn(ArraySet()) whenever(mocks.systemConfig.hiddenApiWhitelistedApps).thenReturn(ArraySet()) whenever(mocks.systemConfig.appMetadataFilePaths).thenReturn(ArrayMap()) + whenever(mocks.systemConfig.oemDefinedUids).thenReturn(ArrayMap()) wheneverStatic { SystemProperties.set(anyString(), anyString()) }.thenDoNothing() wheneverStatic { SystemProperties.getBoolean("fw.free_cache_v2", true) }.thenReturn(true) wheneverStatic { Environment.getApexDirectory() }.thenReturn(apexDirectory) diff --git a/services/tests/mockingservicestests/src/com/android/server/utils/quota/CountQuotaTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/utils/quota/CountQuotaTrackerTest.java index 0d14c9f02677..1b9f8d1f90c3 100644 --- a/services/tests/mockingservicestests/src/com/android/server/utils/quota/CountQuotaTrackerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/utils/quota/CountQuotaTrackerTest.java @@ -40,6 +40,8 @@ import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.AlarmManager; import android.content.BroadcastReceiver; import android.content.Context; @@ -224,6 +226,45 @@ public class CountQuotaTrackerTest { } } + private LongArrayQueue getEvents(int userId, String packageName, String tag) { + synchronized (mQuotaTracker.mLock) { + return mQuotaTracker.getEvents(userId, packageName, tag); + } + } + + private void updateExecutionStats(final int userId, @NonNull final String packageName, + @Nullable final String tag, @NonNull ExecutionStats stats) { + synchronized (mQuotaTracker.mLock) { + mQuotaTracker.updateExecutionStatsLocked(userId, packageName, tag, stats); + } + } + + private ExecutionStats getExecutionStats(final int userId, @NonNull final String packageName, + @Nullable final String tag) { + synchronized (mQuotaTracker.mLock) { + return mQuotaTracker.getExecutionStatsLocked(userId, packageName, tag); + } + } + + private void maybeScheduleStartAlarm(final int userId, @NonNull final String packageName, + @Nullable final String tag) { + synchronized (mQuotaTracker.mLock) { + mQuotaTracker.maybeScheduleStartAlarmLocked(userId, packageName, tag); + } + } + + private void maybeScheduleCleanupAlarm() { + synchronized (mQuotaTracker.mLock) { + mQuotaTracker.maybeScheduleCleanupAlarmLocked(); + } + } + + private void deleteObsoleteEvents() { + synchronized (mQuotaTracker.mLock) { + mQuotaTracker.deleteObsoleteEventsLocked(); + } + } + @Test public void testDeleteObsoleteEventsLocked() { // Count window size should only apply to event list. @@ -243,9 +284,9 @@ public class CountQuotaTrackerTest { expectedEvents.addLast(now - HOUR_IN_MILLIS); expectedEvents.addLast(now - 1); - mQuotaTracker.deleteObsoleteEventsLocked(); + deleteObsoleteEvents(); - LongArrayQueue remainingEvents = mQuotaTracker.getEvents(TEST_USER_ID, TEST_PACKAGE, + LongArrayQueue remainingEvents = getEvents(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); assertTrue(longArrayQueueEquals(expectedEvents, remainingEvents)); } @@ -270,15 +311,15 @@ public class CountQuotaTrackerTest { removal.putExtra(Intent.EXTRA_UID, TEST_UID); mReceiver.onReceive(mContext, removal); assertNull( - mQuotaTracker.getEvents(TEST_USER_ID, "com.android.test.remove", "tag1")); + getEvents(TEST_USER_ID, "com.android.test.remove", "tag1")); assertNull( - mQuotaTracker.getEvents(TEST_USER_ID, "com.android.test.remove", "tag2")); + getEvents(TEST_USER_ID, "com.android.test.remove", "tag2")); assertNull( - mQuotaTracker.getEvents(TEST_USER_ID, "com.android.test.remove", "tag3")); + getEvents(TEST_USER_ID, "com.android.test.remove", "tag3")); assertTrue(longArrayQueueEquals(expected1, - mQuotaTracker.getEvents(TEST_USER_ID, "com.android.test.stay", "tag1"))); + getEvents(TEST_USER_ID, "com.android.test.stay", "tag1"))); assertTrue(longArrayQueueEquals(expected2, - mQuotaTracker.getEvents(TEST_USER_ID, "com.android.test.stay", "tag2"))); + getEvents(TEST_USER_ID, "com.android.test.stay", "tag2"))); } @Test @@ -298,10 +339,10 @@ public class CountQuotaTrackerTest { Intent removal = new Intent(Intent.ACTION_USER_REMOVED); removal.putExtra(Intent.EXTRA_USER_HANDLE, TEST_USER_ID); mReceiver.onReceive(mContext, removal); - assertNull(mQuotaTracker.getEvents(TEST_USER_ID, TEST_PACKAGE, "tag1")); - assertNull(mQuotaTracker.getEvents(TEST_USER_ID, TEST_PACKAGE, "tag2")); - assertNull(mQuotaTracker.getEvents(TEST_USER_ID, TEST_PACKAGE, "tag3")); - longArrayQueueEquals(expected, mQuotaTracker.getEvents(10, TEST_PACKAGE, "tag4")); + assertNull(getEvents(TEST_USER_ID, TEST_PACKAGE, "tag1")); + assertNull(getEvents(TEST_USER_ID, TEST_PACKAGE, "tag2")); + assertNull(getEvents(TEST_USER_ID, TEST_PACKAGE, "tag3")); + longArrayQueueEquals(expected, getEvents(10, TEST_PACKAGE, "tag4")); } @Test @@ -323,7 +364,7 @@ public class CountQuotaTrackerTest { inputStats.countLimit = expectedStats.countLimit = 3; // Invalid time is now +24 hours since there are no sessions at all for the app. expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS; - mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, "com.android.test.not.run", TEST_TAG, + updateExecutionStats(TEST_USER_ID, "com.android.test.not.run", TEST_TAG, inputStats); assertEquals(expectedStats, inputStats); @@ -333,19 +374,19 @@ public class CountQuotaTrackerTest { // Invalid time is now since there was an event exactly windowSizeMs ago. expectedStats.expirationTimeElapsed = now; expectedStats.countInWindow = 1; - mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats); + updateExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats); assertEquals(expectedStats, inputStats); inputStats.windowSizeMs = expectedStats.windowSizeMs = 3 * MINUTE_IN_MILLIS; expectedStats.expirationTimeElapsed = now + 2 * MINUTE_IN_MILLIS; expectedStats.countInWindow = 1; - mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats); + updateExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats); assertEquals(expectedStats, inputStats); inputStats.windowSizeMs = expectedStats.windowSizeMs = 4 * MINUTE_IN_MILLIS; expectedStats.expirationTimeElapsed = now + 3 * MINUTE_IN_MILLIS; expectedStats.countInWindow = 1; - mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats); + updateExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats); assertEquals(expectedStats, inputStats); inputStats.windowSizeMs = expectedStats.windowSizeMs = 49 * MINUTE_IN_MILLIS; @@ -353,13 +394,13 @@ public class CountQuotaTrackerTest { // minutes. expectedStats.expirationTimeElapsed = now + 44 * MINUTE_IN_MILLIS; expectedStats.countInWindow = 2; - mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats); + updateExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats); assertEquals(expectedStats, inputStats); inputStats.windowSizeMs = expectedStats.windowSizeMs = 50 * MINUTE_IN_MILLIS; expectedStats.expirationTimeElapsed = now + 45 * MINUTE_IN_MILLIS; expectedStats.countInWindow = 2; - mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats); + updateExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats); assertEquals(expectedStats, inputStats); inputStats.windowSizeMs = expectedStats.windowSizeMs = HOUR_IN_MILLIS; @@ -370,28 +411,28 @@ public class CountQuotaTrackerTest { // App is at event count limit but the oldest session is at the edge of the window, so // in quota time is now. expectedStats.inQuotaTimeElapsed = now; - mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats); + updateExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats); assertEquals(expectedStats, inputStats); inputStats.windowSizeMs = expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS; expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS; expectedStats.countInWindow = 3; expectedStats.inQuotaTimeElapsed = now + HOUR_IN_MILLIS; - mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats); + updateExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats); assertEquals(expectedStats, inputStats); inputStats.windowSizeMs = expectedStats.windowSizeMs = 5 * HOUR_IN_MILLIS; expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS; expectedStats.countInWindow = 4; expectedStats.inQuotaTimeElapsed = now + 4 * HOUR_IN_MILLIS; - mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats); + updateExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats); assertEquals(expectedStats, inputStats); inputStats.windowSizeMs = expectedStats.windowSizeMs = 6 * HOUR_IN_MILLIS; expectedStats.expirationTimeElapsed = now + 2 * HOUR_IN_MILLIS; expectedStats.countInWindow = 4; expectedStats.inQuotaTimeElapsed = now + 5 * HOUR_IN_MILLIS; - mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats); + updateExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats); assertEquals(expectedStats, inputStats); } @@ -428,7 +469,7 @@ public class CountQuotaTrackerTest { expectedStats.countInWindow = 1; mCategorizer.mCategoryToUse = ACTIVE_BUCKET_CATEGORY; assertEquals(expectedStats, - mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG)); + getExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG)); // Working expectedStats.expirationTimeElapsed = now; @@ -437,7 +478,7 @@ public class CountQuotaTrackerTest { expectedStats.countInWindow = 2; mCategorizer.mCategoryToUse = WORKING_SET_BUCKET_CATEGORY; assertEquals(expectedStats, - mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG)); + getExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG)); // Frequent expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS; @@ -447,7 +488,7 @@ public class CountQuotaTrackerTest { expectedStats.inQuotaTimeElapsed = now + HOUR_IN_MILLIS; mCategorizer.mCategoryToUse = FREQUENT_BUCKET_CATEGORY; assertEquals(expectedStats, - mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG)); + getExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG)); // Rare expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS; @@ -457,7 +498,7 @@ public class CountQuotaTrackerTest { expectedStats.inQuotaTimeElapsed = now + 19 * HOUR_IN_MILLIS; mCategorizer.mCategoryToUse = RARE_BUCKET_CATEGORY; assertEquals(expectedStats, - mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG)); + getExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG)); } /** @@ -481,7 +522,7 @@ public class CountQuotaTrackerTest { expectedStats.countInWindow = 3; expectedStats.expirationTimeElapsed = 2 * HOUR_IN_MILLIS + 30_000; assertEquals(expectedStats, - mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG)); + getExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG)); } @Test @@ -556,20 +597,20 @@ public class CountQuotaTrackerTest { mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 5, 24 * HOUR_IN_MILLIS); // No sessions saved yet. - mQuotaTracker.maybeScheduleCleanupAlarmLocked(); + maybeScheduleCleanupAlarm(); verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_CLEANUP), any(), any()); // Test with only one timing session saved. final long now = mInjector.getElapsedRealtime(); logEventAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - 6 * HOUR_IN_MILLIS); - mQuotaTracker.maybeScheduleCleanupAlarmLocked(); + maybeScheduleCleanupAlarm(); verify(mAlarmManager, timeout(1000).times(1)) .set(anyInt(), eq(now + 18 * HOUR_IN_MILLIS), eq(TAG_CLEANUP), any(), any()); // Test with new (more recent) timing sessions saved. AlarmManger shouldn't be called again. logEventAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - 3 * HOUR_IN_MILLIS); logEventAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - HOUR_IN_MILLIS); - mQuotaTracker.maybeScheduleCleanupAlarmLocked(); + maybeScheduleCleanupAlarm(); verify(mAlarmManager, times(1)) .set(anyInt(), eq(now + 18 * HOUR_IN_MILLIS), eq(TAG_CLEANUP), any(), any()); } @@ -587,14 +628,14 @@ public class CountQuotaTrackerTest { mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 10, 8 * HOUR_IN_MILLIS); // No sessions saved yet. - mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); + maybeScheduleStartAlarm(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); verify(mAlarmManager, never()).setWindow( anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); // Test with timing sessions out of window. final long now = mInjector.getElapsedRealtime(); logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - 10 * HOUR_IN_MILLIS, 20); - mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); + maybeScheduleStartAlarm(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); verify(mAlarmManager, never()).setWindow( anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); @@ -602,26 +643,26 @@ public class CountQuotaTrackerTest { final long start = now - (6 * HOUR_IN_MILLIS); final long expectedAlarmTime = start + 8 * HOUR_IN_MILLIS; logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, start, 5); - mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); + maybeScheduleStartAlarm(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); verify(mAlarmManager, never()).setWindow( anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); // Add some more sessions, but still in quota. logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - 3 * HOUR_IN_MILLIS, 1); logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - HOUR_IN_MILLIS, 3); - mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); + maybeScheduleStartAlarm(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); verify(mAlarmManager, never()).setWindow( anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); // Test when out of quota. logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - HOUR_IN_MILLIS, 1); - mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); + maybeScheduleStartAlarm(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); verify(mAlarmManager, timeout(1000).times(1)).setWindow( anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); // Alarm already scheduled, so make sure it's not scheduled again. - mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); + maybeScheduleStartAlarm(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); verify(mAlarmManager, times(1)).setWindow( anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); @@ -656,7 +697,7 @@ public class CountQuotaTrackerTest { // Start in ACTIVE bucket. mCategorizer.mCategoryToUse = ACTIVE_BUCKET_CATEGORY; - mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); + maybeScheduleStartAlarm(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); inOrder.verify(mAlarmManager, never()) .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); @@ -665,40 +706,40 @@ public class CountQuotaTrackerTest { // And down from there. final long expectedWorkingAlarmTime = outOfQuotaTime + (2 * HOUR_IN_MILLIS); mCategorizer.mCategoryToUse = WORKING_SET_BUCKET_CATEGORY; - mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); + maybeScheduleStartAlarm(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); inOrder.verify(mAlarmManager, timeout(1000).times(1)) .setWindow(anyInt(), eq(expectedWorkingAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); final long expectedFrequentAlarmTime = outOfQuotaTime + (8 * HOUR_IN_MILLIS); mCategorizer.mCategoryToUse = FREQUENT_BUCKET_CATEGORY; - mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); + maybeScheduleStartAlarm(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); inOrder.verify(mAlarmManager, timeout(1000).times(1)) .setWindow(anyInt(), eq(expectedFrequentAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); final long expectedRareAlarmTime = outOfQuotaTime + (24 * HOUR_IN_MILLIS); mCategorizer.mCategoryToUse = RARE_BUCKET_CATEGORY; - mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); + maybeScheduleStartAlarm(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); inOrder.verify(mAlarmManager, timeout(1000).times(1)) .setWindow(anyInt(), eq(expectedRareAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); // And back up again. mCategorizer.mCategoryToUse = FREQUENT_BUCKET_CATEGORY; - mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); + maybeScheduleStartAlarm(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); inOrder.verify(mAlarmManager, timeout(1000).times(1)) .setWindow(anyInt(), eq(expectedFrequentAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); mCategorizer.mCategoryToUse = WORKING_SET_BUCKET_CATEGORY; - mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); + maybeScheduleStartAlarm(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); inOrder.verify(mAlarmManager, timeout(1000).times(1)) .setWindow(anyInt(), eq(expectedWorkingAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); mCategorizer.mCategoryToUse = ACTIVE_BUCKET_CATEGORY; - mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); + maybeScheduleStartAlarm(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); inOrder.verify(mAlarmManager, timeout(1000).times(1)) .cancel(any(AlarmManager.OnAlarmListener.class)); inOrder.verify(mAlarmManager, timeout(1000).times(0)) @@ -745,14 +786,14 @@ public class CountQuotaTrackerTest { mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 10, 2 * HOUR_IN_MILLIS); ExecutionStats stats = - mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); + getExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); assertEquals(0, stats.countInWindow); for (int i = 0; i < 10; ++i) { mQuotaTracker.noteEvent(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); advanceElapsedClock(10 * SECOND_IN_MILLIS); - mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, stats); + updateExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, stats); assertEquals(0, stats.countInWindow); } } @@ -766,14 +807,14 @@ public class CountQuotaTrackerTest { mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 10, 2 * HOUR_IN_MILLIS); ExecutionStats stats = - mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); + getExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); assertEquals(0, stats.countInWindow); for (int i = 0; i < 10; ++i) { mQuotaTracker.noteEvent(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); advanceElapsedClock(10 * SECOND_IN_MILLIS); - mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, stats); + updateExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, stats); assertEquals(i + 1, stats.countInWindow); } } @@ -785,14 +826,14 @@ public class CountQuotaTrackerTest { mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 10, 2 * HOUR_IN_MILLIS); ExecutionStats stats = - mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); + getExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); assertEquals(0, stats.countInWindow); for (int i = 0; i < 10; ++i) { mQuotaTracker.noteEvent(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); advanceElapsedClock(10 * SECOND_IN_MILLIS); - mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, stats); + updateExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, stats); assertEquals(0, stats.countInWindow); } } @@ -806,14 +847,14 @@ public class CountQuotaTrackerTest { mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 10, 2 * HOUR_IN_MILLIS); ExecutionStats stats = - mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); + getExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); assertEquals(0, stats.countInWindow); for (int i = 0; i < 10; ++i) { mQuotaTracker.noteEvent(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); advanceElapsedClock(10 * SECOND_IN_MILLIS); - mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, stats); + updateExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, stats); assertEquals(i + 1, stats.countInWindow); } } diff --git a/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java index d45e31248d0b..fc4d8d871fd5 100644 --- a/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java +++ b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java @@ -61,7 +61,6 @@ import com.android.server.statusbar.StatusBarManagerInternal; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import java.util.concurrent.Executor; @@ -264,8 +263,8 @@ public class NotifierTest { BatteryStats.WAKE_TYPE_PARTIAL, false); verifyNoMoreInteractions(mWakeLockLog, mBatteryStats); - WorkSource worksourceOld = Mockito.mock(WorkSource.class); - WorkSource worksourceNew = Mockito.mock(WorkSource.class); + WorkSource worksourceOld = new WorkSource(/*uid=*/ 1); + WorkSource worksourceNew = new WorkSource(/*uid=*/ 2); mNotifier.onWakeLockChanging(PowerManager.PARTIAL_WAKE_LOCK, "wakelockTag", "my.package.name", uid, pid, worksourceOld, /* historyTag= */ null, @@ -309,6 +308,40 @@ public class NotifierTest { verify(mWakeLockLog).onWakeLockReleased("wakelockTag", uid, -1); } + @Test + public void + test_notifierProcessesWorkSourceDeepCopy_OnWakelockChanging() throws RemoteException { + when(mPowerManagerFlags.improveWakelockLatency()).thenReturn(true); + createNotifier(); + clearInvocations(mWakeLockLog, mBatteryStats, mAppOpsManager); + IWakeLockCallback exceptingCallback = new IWakeLockCallback.Stub() { + @Override public void onStateChanged(boolean enabled) throws RemoteException { + throw new RemoteException("Just testing"); + } + }; + + final int uid = 1234; + final int pid = 5678; + mTestLooper.dispatchAll(); + WorkSource worksourceOld = new WorkSource(/*uid=*/ 1); + WorkSource worksourceNew = new WorkSource(/*uid=*/ 2); + + mNotifier.onWakeLockChanging(PowerManager.PARTIAL_WAKE_LOCK, "wakelockTag", + "my.package.name", uid, pid, worksourceOld, /* historyTag= */ null, + exceptingCallback, + PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "wakelockTag", + "my.package.name", uid, pid, worksourceNew, /* newHistoryTag= */ null, + exceptingCallback); + // The newWorksource is modified before notifier could process it. + worksourceNew.set(/*uid=*/ 3); + + mTestLooper.dispatchAll(); + verify(mBatteryStats).noteChangeWakelockFromSource(worksourceOld, pid, + "wakelockTag", null, BatteryStats.WAKE_TYPE_PARTIAL, + new WorkSource(/*uid=*/ 2), pid, "wakelockTag", null, + BatteryStats.WAKE_TYPE_FULL, false); + } + @Test public void testOnWakeLockListener_FullWakeLock_ProcessesOnHandler() throws RemoteException { diff --git a/services/tests/powerstatstests/src/com/android/server/powerstats/PowerStatsServiceTest.java b/services/tests/powerstatstests/src/com/android/server/powerstats/PowerStatsServiceTest.java index 1838fe884561..53e31438c560 100644 --- a/services/tests/powerstatstests/src/com/android/server/powerstats/PowerStatsServiceTest.java +++ b/services/tests/powerstatstests/src/com/android/server/powerstats/PowerStatsServiceTest.java @@ -19,6 +19,7 @@ package com.android.server.powerstats; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -37,6 +38,8 @@ import android.os.IPowerStatsService; import android.os.Looper; import android.os.PowerMonitor; import android.os.ResultReceiver; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; import android.provider.DeviceConfig; import android.provider.DeviceConfigInterface; @@ -58,6 +61,7 @@ import com.android.server.powerstats.nano.StateResidencyResultProto; import com.android.server.testutils.FakeDeviceConfigInterface; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import java.io.ByteArrayOutputStream; @@ -101,6 +105,8 @@ public class PowerStatsServiceTest { private static final int STATE_RESIDENCY_COUNT = 4; private static final int APP_UID = 10042; + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext(); private PowerStatsService mService; private TestPowerStatsHALWrapper mPowerStatsHALWrapper = new TestPowerStatsHALWrapper(); @@ -1198,4 +1204,22 @@ public class PowerStatsServiceTest { assertThat(Arrays.stream(supportedPowerMonitorsResult.powerMonitors) .map(PowerMonitor::getName).toList()).contains("ENERGYCONSUMER0"); } + + @EnableFlags(Flags.FLAG_VERIFY_NON_NULL_ARGUMENTS) + @Test + public void testGetSupportedPowerMonitors_withNullArguments() { + IPowerStatsService iPowerStatsService = mService.getIPowerStatsServiceForTest(); + assertThrows(NullPointerException.class, + () -> iPowerStatsService.getSupportedPowerMonitors(null)); + } + + @EnableFlags(Flags.FLAG_VERIFY_NON_NULL_ARGUMENTS) + @Test + public void testGetPowerMonitorReadings_withNullArguments() { + IPowerStatsService iPowerStatsService = mService.getIPowerStatsServiceForTest(); + assertThrows(NullPointerException.class, () -> iPowerStatsService.getPowerMonitorReadings( + null, new GetPowerMonitorsResult())); + assertThrows(NullPointerException.class, () -> iPowerStatsService.getPowerMonitorReadings( + new int[] {0}, null)); + } } 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 dbab54b76a2e..1db46bf17655 100644 --- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java @@ -19,6 +19,8 @@ package com.android.server.am; import static android.Manifest.permission.INTERACT_ACROSS_PROFILES; import static android.Manifest.permission.INTERACT_ACROSS_USERS; import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; +import static android.app.ActivityManager.STOP_USER_ON_SWITCH_TRUE; +import static android.app.ActivityManager.STOP_USER_ON_SWITCH_FALSE; import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY; import static android.app.ActivityManagerInternal.ALLOW_NON_FULL; import static android.app.ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE; @@ -103,6 +105,7 @@ import android.view.Display; import androidx.test.filters.SmallTest; import com.android.internal.widget.LockPatternUtils; +import com.android.server.AlarmManagerInternal; import com.android.server.FgThread; import com.android.server.SystemService; import com.android.server.am.UserState.KeyEvictedCallback; @@ -122,6 +125,7 @@ import org.junit.Rule; import org.junit.Test; import org.mockito.ArgumentCaptor; +import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -201,7 +205,8 @@ public class UserControllerTest { doNothing().when(mInjector).systemServiceManagerOnUserStopped(anyInt()); doNothing().when(mInjector).systemServiceManagerOnUserCompletedEvent( anyInt(), anyInt()); - doNothing().when(mInjector).activityManagerForceStopPackage(anyInt(), anyString()); + doNothing().when(mInjector).activityManagerForceStopUserPackages(anyInt(), + anyString(), anyBoolean()); doNothing().when(mInjector).activityManagerOnUserStopped(anyInt()); doNothing().when(mInjector).clearBroadcastQueueForUser(anyInt()); doNothing().when(mInjector).taskSupervisorRemoveUser(anyInt()); @@ -727,6 +732,39 @@ public class UserControllerTest { mUserController.getRunningUsersLU()); } + /** Test scheduling stopping of background users - reschedule if user with a scheduled alarm. */ + @Test + public void testScheduleStopOfBackgroundUser_rescheduleIfAlarm() throws Exception { + mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SCHEDULE_STOP_OF_BACKGROUND_USER); + + mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true, + /* maxRunningUsers= */ 10, /* delayUserDataLocking= */ false, + /* backgroundUserScheduledStopTimeSecs= */ 2); + + setUpAndStartUserInBackground(TEST_USER_ID); + assertEquals(newHashSet(SYSTEM_USER_ID, TEST_USER_ID), + new HashSet<>(mUserController.getRunningUsersLU())); + + // Initially, the background user has an alarm that will fire soon. So don't stop the user. + when(mInjector.mAlarmManagerInternal.getNextAlarmTriggerTimeForUser(eq(TEST_USER_ID))) + .thenReturn(System.currentTimeMillis() + Duration.ofMinutes(2).toMillis()); + assertAndProcessScheduledStopBackgroundUser(true, TEST_USER_ID); + assertEquals(newHashSet(SYSTEM_USER_ID, TEST_USER_ID), + new HashSet<>(mUserController.getRunningUsersLU())); + + // Now, that alarm is gone and the next alarm isn't for a long time. Do stop the user. + when(mInjector.mAlarmManagerInternal.getNextAlarmTriggerTimeForUser(eq(TEST_USER_ID))) + .thenReturn(System.currentTimeMillis() + Duration.ofDays(1).toMillis()); + assertAndProcessScheduledStopBackgroundUser(true, TEST_USER_ID); + assertEquals(newHashSet(SYSTEM_USER_ID), + new HashSet<>(mUserController.getRunningUsersLU())); + + // No-one is scheduled to stop anymore. + assertAndProcessScheduledStopBackgroundUser(false, null); + verify(mInjector.mAlarmManagerInternal, never()) + .getNextAlarmTriggerTimeForUser(eq(SYSTEM_USER_ID)); + } + /** * Process queued SCHEDULED_STOP_BACKGROUND_USER_MSG message, if expected. * @param userId the user we are checking to see whether it is scheduled. @@ -936,6 +974,61 @@ public class UserControllerTest { new HashSet<>(mUserController.getRunningUsersLU())); } + @Test + public void testEarlyPackageKillEnabledForUserSwitch_enabled() { + mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true, + /* maxRunningUsers= */ 4, /* delayUserDataLocking= */ true, + /* backgroundUserScheduledStopTimeSecs= */ -1); + + assertTrue(mUserController + .isEarlyPackageKillEnabledForUserSwitch(TEST_USER_ID, TEST_USER_ID1)); + } + + @Test + public void testEarlyPackageKillEnabledForUserSwitch_withoutDelayUserDataLocking() { + mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true, + /* maxRunningUsers= */ 4, /* delayUserDataLocking= */ false, + /* backgroundUserScheduledStopTimeSecs= */ -1); + + assertFalse(mUserController + .isEarlyPackageKillEnabledForUserSwitch(TEST_USER_ID, TEST_USER_ID1)); + } + + @Test + public void testEarlyPackageKillEnabledForUserSwitch_withPrevSystemUser() { + mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true, + /* maxRunningUsers= */ 4, /* delayUserDataLocking= */ true, + /* backgroundUserScheduledStopTimeSecs= */ -1); + + assertFalse(mUserController + .isEarlyPackageKillEnabledForUserSwitch(SYSTEM_USER_ID, TEST_USER_ID1)); + } + + @Test + public void testEarlyPackageKillEnabledForUserSwitch_stopUserOnSwitchModeOn() { + mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true, + /* maxRunningUsers= */ 4, /* delayUserDataLocking= */ false, + /* backgroundUserScheduledStopTimeSecs= */ -1); + + mUserController.setStopUserOnSwitch(STOP_USER_ON_SWITCH_TRUE); + + assertTrue(mUserController + .isEarlyPackageKillEnabledForUserSwitch(TEST_USER_ID, TEST_USER_ID1)); + } + + @Test + public void testEarlyPackageKillEnabledForUserSwitch_stopUserOnSwitchModeOff() { + mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true, + /* maxRunningUsers= */ 4, /* delayUserDataLocking= */ true, + /* backgroundUserScheduledStopTimeSecs= */ -1); + + mUserController.setStopUserOnSwitch(STOP_USER_ON_SWITCH_FALSE); + + assertFalse(mUserController + .isEarlyPackageKillEnabledForUserSwitch(TEST_USER_ID, TEST_USER_ID1)); + } + + /** * Test that, in getRunningUsersLU, parents come after their profile, even if the profile was * started afterwards. @@ -1689,6 +1782,7 @@ public class UserControllerTest { private final WindowManagerService mWindowManagerMock; private final ActivityTaskManagerInternal mActivityTaskManagerInternal; private final PowerManagerInternal mPowerManagerInternal; + private final AlarmManagerInternal mAlarmManagerInternal; private final KeyguardManager mKeyguardManagerMock; private final LockPatternUtils mLockPatternUtilsMock; @@ -1711,6 +1805,7 @@ public class UserControllerTest { mActivityTaskManagerInternal = mock(ActivityTaskManagerInternal.class); mStorageManagerMock = mock(IStorageManager.class); mPowerManagerInternal = mock(PowerManagerInternal.class); + mAlarmManagerInternal = mock(AlarmManagerInternal.class); mKeyguardManagerMock = mock(KeyguardManager.class); when(mKeyguardManagerMock.isDeviceSecure(anyInt())).thenReturn(true); mLockPatternUtilsMock = mock(LockPatternUtils.class); @@ -1781,6 +1876,11 @@ public class UserControllerTest { } @Override + AlarmManagerInternal getAlarmManagerInternal() { + return mAlarmManagerInternal; + } + + @Override KeyguardManager getKeyguardManager() { return mKeyguardManagerMock; } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java index 1074f7b4aa0a..6577e09a986e 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java @@ -250,120 +250,120 @@ public class HdmiCecMessageValidatorTest { assertMessageValidity("04:33:0C:08:10:1E:04:30:08:13:AD:06") .isEqualTo(ERROR_PARAMETER_SHORT); // Out of range Day of Month - assertMessageValidity("04:34:20:0C:16:0F:08:37:00:02:EA:60:03").isEqualTo(ERROR_PARAMETER); + assertMessageValidity("04:34:20:0C:22:15:08:55:00:02:EA:60:03").isEqualTo(ERROR_PARAMETER); // Out of range Month of Year - assertMessageValidity("04:33:0C:00:10:1E:04:30:08:00:13:AD:06").isEqualTo(ERROR_PARAMETER); + assertMessageValidity("04:33:0C:00:16:30:04:48:08:00:13:AD:06").isEqualTo(ERROR_PARAMETER); // Out of range Start Time - Hour - assertMessageValidity("04:34:04:0C:18:0F:08:37:00:02:EA:60:03").isEqualTo(ERROR_PARAMETER); + assertMessageValidity("04:34:04:0C:24:15:08:55:00:02:EA:60:03").isEqualTo(ERROR_PARAMETER); // Out of range Start Time - Minute - assertMessageValidity("04:33:0C:08:10:50:04:30:08:00:13:AD:06").isEqualTo(ERROR_PARAMETER); + assertMessageValidity("04:33:0C:08:16:60:04:48:08:00:13:AD:06").isEqualTo(ERROR_PARAMETER); // Out of range Duration - Duration Hours - assertMessageValidity("04:34:04:0C:16:0F:64:37:00:02:EA:60:03").isEqualTo(ERROR_PARAMETER); + assertMessageValidity("04:34:04:0C:22:15:9A:55:00:02:EA:60:03").isEqualTo(ERROR_PARAMETER); // Out of range Duration - Minute - assertMessageValidity("04:33:0C:08:10:1E:04:64:08:00:13:AD:06").isEqualTo(ERROR_PARAMETER); + assertMessageValidity("04:33:0C:08:16:30:04:60:08:00:13:AD:06").isEqualTo(ERROR_PARAMETER); // Invalid Recording Sequence - assertMessageValidity("04:34:04:0C:16:0F:08:37:88:02:EA:60:03").isEqualTo(ERROR_PARAMETER); + assertMessageValidity("04:34:04:0C:22:15:08:55:88:02:EA:60:03").isEqualTo(ERROR_PARAMETER); // Invalid Recording Sequence - assertMessageValidity("04:33:0C:08:10:1E:04:30:A2:00:13:AD:06").isEqualTo(ERROR_PARAMETER); + assertMessageValidity("04:33:0C:08:16:30:04:48:A2:00:13:AD:06").isEqualTo(ERROR_PARAMETER); // Out of range Analogue Broadcast Type - assertMessageValidity("04:34:04:0C:16:0F:08:37:00:03:EA:60:03").isEqualTo(ERROR_PARAMETER); + assertMessageValidity("04:34:04:0C:22:15:08:55:00:03:EA:60:03").isEqualTo(ERROR_PARAMETER); // Out of range Analogue Frequency - assertMessageValidity("04:33:0C:08:10:1E:04:30:08:00:FF:FF:06").isEqualTo(ERROR_PARAMETER); + assertMessageValidity("04:33:0C:08:16:30:04:48:08:00:FF:FF:06").isEqualTo(ERROR_PARAMETER); // Out of range Broadcast System - assertMessageValidity("04:34:04:0C:16:0F:08:37:00:02:EA:60:20").isEqualTo(ERROR_PARAMETER); + assertMessageValidity("04:34:04:0C:22:15:08:55:00:02:EA:60:20").isEqualTo(ERROR_PARAMETER); } @Test public void isValid_setDigitalTimer_clearDigitalTimer() { // Services identified by Digital IDs - ARIB Broadcast System - assertMessageValidity("04:99:0C:08:15:05:04:1E:00:00:C4:C2:11:D8:75:30").isEqualTo(OK); + assertMessageValidity("04:99:0C:08:21:05:04:30:00:00:C4:C2:11:D8:75:30").isEqualTo(OK); // Service identified by Digital IDs - ATSC Broadcast System - assertMessageValidity("04:97:1E:07:12:20:50:28:01:01:8B:5E:39:5A").isEqualTo(OK); + assertMessageValidity("04:97:1E:07:18:32:80:40:01:01:8B:5E:39:5A").isEqualTo(OK); // Service identified by Digital IDs - DVB Broadcast System - assertMessageValidity("04:99:05:0C:06:0A:19:3B:40:19:8B:44:03:11:04:FC").isEqualTo(OK); + assertMessageValidity("04:99:05:0C:06:10:25:59:40:19:8B:44:03:11:04:FC").isEqualTo(OK); // Service identified by Channel - 1 part channel number - assertMessageValidity("04:97:12:06:0C:2D:5A:19:08:91:04:00:B1").isEqualTo(OK); + assertMessageValidity("04:97:12:06:12:45:90:25:08:91:04:00:B1").isEqualTo(OK); // Service identified by Channel - 2 part channel number - assertMessageValidity("04:99:15:09:00:0F:00:2D:04:82:09:C8:72:C8").isEqualTo(OK); + assertMessageValidity("04:99:15:09:00:15:00:45:04:82:09:C8:72:C8").isEqualTo(OK); - assertMessageValidity("4F:97:0C:08:15:05:04:1E:00:00:C4:C2:11:D8:75:30") + assertMessageValidity("4F:97:0C:08:21:05:04:30:00:00:C4:C2:11:D8:75:30") .isEqualTo(ERROR_DESTINATION); - assertMessageValidity("F0:99:15:09:00:0F:00:2D:04:82:09:C8:72:C8").isEqualTo(ERROR_SOURCE); + assertMessageValidity("F0:99:15:09:00:15:00:45:04:82:09:C8:72:C8").isEqualTo(ERROR_SOURCE); assertMessageValidity("04:97:1E:12:20:58:01:01:8B:5E:39:5A") .isEqualTo(ERROR_PARAMETER_SHORT); // Out of range Day of Month - assertMessageValidity("04:99:24:0C:06:0A:19:3B:40:19:8B:44:03:11:04:FC") + assertMessageValidity("04:99:24:0C:06:10:25:59:40:19:8B:44:03:11:04:FC") .isEqualTo(ERROR_PARAMETER); // Out of range Month of Year - assertMessageValidity("04:97:12:10:0C:2D:5A:19:08:91:04:00:B1").isEqualTo(ERROR_PARAMETER); + assertMessageValidity("04:97:12:10:12:45:90:25:08:91:04:00:B1").isEqualTo(ERROR_PARAMETER); // Out of range Start Time - Hour - assertMessageValidity("04:99:0C:08:20:05:04:1E:00:00:C4:C2:11:D8:75:30") + assertMessageValidity("04:99:0C:08:24:05:04:30:00:00:C4:C2:11:D8:75:30") .isEqualTo(ERROR_PARAMETER); // Out of range Start Time - Minute - assertMessageValidity("04:97:15:09:00:4B:00:2D:04:82:09:C8:72:C8") + assertMessageValidity("04:97:15:09:00:60:00:45:04:82:09:C8:72:C8") .isEqualTo(ERROR_PARAMETER); // Out of range Duration - Duration Hours - assertMessageValidity("04:99:1E:07:12:20:78:28:01:01:8B:5E:39:5A") + assertMessageValidity("04:99:1E:07:18:32:9A:40:01:01:8B:5E:39:5A") .isEqualTo(ERROR_PARAMETER); // Out of range Duration - Minute - assertMessageValidity("04:97:05:0C:06:0A:19:48:40:19:8B:44:03:11:04:FC") + assertMessageValidity("04:97:05:0C:06:10:25:60:40:19:8B:44:03:11:04:FC") .isEqualTo(ERROR_PARAMETER); // Invalid Recording Sequence - assertMessageValidity("04:99:12:06:0C:2D:5A:19:90:91:04:00:B1").isEqualTo(ERROR_PARAMETER); + assertMessageValidity("04:99:12:06:12:45:90:25:90:91:04:00:B1").isEqualTo(ERROR_PARAMETER); // Invalid Recording Sequence assertMessageValidity("04:97:0C:08:15:05:04:1E:A1:00:C4:C2:11:D8:75:30") .isEqualTo(ERROR_PARAMETER); // Invalid Digital Broadcast System - assertMessageValidity("04:99:1E:07:12:20:50:28:01:04:8B:5E:39:5A") + assertMessageValidity("04:99:1E:07:18:32:80:40:01:04:8B:5E:39:5A") .isEqualTo(ERROR_PARAMETER); // Invalid Digital Broadcast System - assertMessageValidity("04:97:05:0C:06:0A:19:3B:40:93:8B:44:03:11:04:FC") + assertMessageValidity("04:97:05:0C:06:10:25:59:40:93:8B:44:03:11:04:FC") .isEqualTo(ERROR_PARAMETER); // Insufficient data for ARIB Broadcast system - assertMessageValidity("04:99:0C:08:15:05:04:1E:00:00:C4:C2:11:D8:75") + assertMessageValidity("04:99:0C:08:21:05:04:30:00:00:C4:C2:11:D8:75") .isEqualTo(ERROR_PARAMETER); // Insufficient data for ATSC Broadcast system - assertMessageValidity("04:97:1E:07:12:20:50:28:01:01:8B:5E:39").isEqualTo(ERROR_PARAMETER); + assertMessageValidity("04:97:1E:07:18:32:80:40:01:01:8B:5E:39").isEqualTo(ERROR_PARAMETER); // Insufficient data for DVB Broadcast system - assertMessageValidity("04:99:05:0C:06:0A:19:3B:40:19:8B:44:03:11:04") + assertMessageValidity("04:99:05:0C:06:10:25:59:40:19:8B:44:03:11:04") .isEqualTo(ERROR_PARAMETER); // Insufficient data for 2 part channel number - assertMessageValidity("04:97:15:09:00:0F:00:2D:04:82:09:C8:72").isEqualTo(ERROR_PARAMETER); + assertMessageValidity("04:97:15:09:00:15:00:45:04:82:09:C8:72").isEqualTo(ERROR_PARAMETER); // Invalid Channel Number format - assertMessageValidity("04:99:12:06:0C:2D:5A:19:08:91:0D:00:B1").isEqualTo(ERROR_PARAMETER); + assertMessageValidity("04:99:12:06:12:45:90:25:08:91:0D:00:B1").isEqualTo(ERROR_PARAMETER); } @Test public void isValid_setExternalTimer_clearExternalTimer() { - assertMessageValidity("40:A1:0C:08:15:05:04:1E:02:04:20").isEqualTo(OK); - assertMessageValidity("40:A2:14:09:12:28:4B:19:10:05:10:00").isEqualTo(OK); + assertMessageValidity("40:A1:0C:08:21:05:04:30:02:04:20").isEqualTo(OK); + assertMessageValidity("40:A2:14:09:18:40:75:25:10:05:10:00").isEqualTo(OK); - assertMessageValidity("4F:A1:0C:08:15:05:04:1E:02:04:20").isEqualTo(ERROR_DESTINATION); - assertMessageValidity("F4:A2:14:09:12:28:4B:19:10:05:10:00").isEqualTo(ERROR_SOURCE); - assertMessageValidity("40:A1:0C:08:15:05:04:1E:02:04").isEqualTo(ERROR_PARAMETER_SHORT); + assertMessageValidity("4F:A1:0C:08:21:05:04:30:02:04:20").isEqualTo(ERROR_DESTINATION); + assertMessageValidity("F4:A2:14:09:18:40:75:25:10:05:10:00").isEqualTo(ERROR_SOURCE); + assertMessageValidity("40:A1:0C:08:21:05:04:30:02:04").isEqualTo(ERROR_PARAMETER_SHORT); // Out of range Day of Month - assertMessageValidity("40:A2:28:09:12:28:4B:19:10:05:10:00").isEqualTo(ERROR_PARAMETER); + assertMessageValidity("40:A2:28:09:18:40:75:25:10:05:10:00").isEqualTo(ERROR_PARAMETER); // Out of range Month of Year - assertMessageValidity("40:A1:0C:0F:15:05:04:1E:02:04:20").isEqualTo(ERROR_PARAMETER); + assertMessageValidity("40:A1:0C:0F:21:05:04:30:02:04:20").isEqualTo(ERROR_PARAMETER); // Out of range Start Time - Hour - assertMessageValidity("40:A2:14:09:1A:28:4B:19:10:05:10:00").isEqualTo(ERROR_PARAMETER); + assertMessageValidity("40:A2:14:09:24:40:75:25:10:05:10:00").isEqualTo(ERROR_PARAMETER); // Out of range Start Time - Minute - assertMessageValidity("40:A1:0C:08:15:48:04:1E:02:04:20").isEqualTo(ERROR_PARAMETER); + assertMessageValidity("40:A1:0C:08:21:60:04:30:02:04:20").isEqualTo(ERROR_PARAMETER); // Out of range Duration - Duration Hours - assertMessageValidity("40:A2:14:09:12:28:66:19:10:05:10:00").isEqualTo(ERROR_PARAMETER); + assertMessageValidity("40:A2:14:09:18:40:9A:25:10:05:10:00").isEqualTo(ERROR_PARAMETER); // Out of range Duration - Minute - assertMessageValidity("40:A1:0C:08:15:05:04:3F:02:04:20").isEqualTo(ERROR_PARAMETER); + assertMessageValidity("40:A1:0C:08:21:05:04:60:02:04:20").isEqualTo(ERROR_PARAMETER); // Invalid Recording Sequence - assertMessageValidity("40:A2:14:09:12:28:4B:19:84:05:10:00").isEqualTo(ERROR_PARAMETER); + assertMessageValidity("40:A2:14:09:18:40:75:25:84:05:10:00").isEqualTo(ERROR_PARAMETER); // Invalid Recording Sequence assertMessageValidity("40:A1:0C:08:15:05:04:1E:94:04:20").isEqualTo(ERROR_PARAMETER); // Invalid external source specifier - assertMessageValidity("40:A2:14:09:12:28:4B:19:10:08:10:00").isEqualTo(ERROR_PARAMETER); + assertMessageValidity("40:A2:14:09:18:40:75:25:10:08:10:00").isEqualTo(ERROR_PARAMETER); // Invalid External PLug - assertMessageValidity("04:A1:0C:08:15:05:04:1E:02:04:00").isEqualTo(ERROR_PARAMETER); + assertMessageValidity("04:A1:0C:08:21:05:04:30:02:04:00").isEqualTo(ERROR_PARAMETER); // Invalid Physical Address - assertMessageValidity("40:A2:14:09:12:28:4B:19:10:05:10:10").isEqualTo(ERROR_PARAMETER); + assertMessageValidity("40:A2:14:09:18:40:75:25:10:05:10:10").isEqualTo(ERROR_PARAMETER); } @Test @@ -396,9 +396,9 @@ public class HdmiCecMessageValidatorTest { // Non programmed - Invalid not programmed error info assertMessageValidity("40:35:DE").isEqualTo(ERROR_PARAMETER); // Programmed - Might not be enough space available - Invalid duration hours - assertMessageValidity("40:35:BB:96:1C").isEqualTo(ERROR_PARAMETER); + assertMessageValidity("40:35:BB:9A:28").isEqualTo(ERROR_PARAMETER); // Not programmed - Duplicate - Invalid duration minutes - assertMessageValidity("40:35:EE:52:4A").isEqualTo(ERROR_PARAMETER); + assertMessageValidity("40:35:EE:82:60").isEqualTo(ERROR_PARAMETER); } @Test diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java index 3d6884925098..dddab657be14 100644 --- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java @@ -108,6 +108,7 @@ import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isA; import static org.mockito.Mockito.CALLS_REAL_METHODS; @@ -165,6 +166,7 @@ import android.os.PowerExemptionManager; import android.os.PowerManager; import android.os.PowerManagerInternal; import android.os.PowerSaveState; +import android.os.Process; import android.os.RemoteException; import android.os.SimpleClock; import android.os.SystemClock; @@ -197,6 +199,7 @@ import androidx.test.filters.FlakyTest; import androidx.test.filters.MediumTest; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.util.ArrayUtils; import com.android.internal.util.test.BroadcastInterceptingContext; import com.android.internal.util.test.BroadcastInterceptingContext.FutureIntent; import com.android.internal.util.test.FsUtil; @@ -2310,6 +2313,70 @@ public class NetworkPolicyManagerServiceTest { assertTrue(mService.isUidNetworkingBlocked(UID_A, false)); } + @SuppressWarnings("GuardedBy") // For not holding mUidRulesFirstLock + @Test + @RequiresFlagsEnabled(Flags.FLAG_NEVER_APPLY_RULES_TO_CORE_UIDS) + public void testRulesNeverAppliedToCoreUids() throws Exception { + clearInvocations(mNetworkManager); + + final int coreAppId = Process.FIRST_APPLICATION_UID - 102; + final int coreUid = UserHandle.getUid(USER_ID, coreAppId); + + // Enable all restrictions and add this core uid to all allowlists. + mService.mDeviceIdleMode = true; + mService.mRestrictPower = true; + setRestrictBackground(true); + expectHasUseRestrictedNetworksPermission(coreUid, true); + enableRestrictedMode(true); + final NetworkPolicyManagerInternal internal = LocalServices.getService( + NetworkPolicyManagerInternal.class); + internal.setLowPowerStandbyActive(true); + internal.setLowPowerStandbyAllowlist(new int[]{coreUid}); + internal.onTempPowerSaveWhitelistChange(coreAppId, true, REASON_OTHER, "testing"); + + when(mPowerExemptionManager.getAllowListedAppIds(anyBoolean())) + .thenReturn(new int[]{coreAppId}); + mPowerAllowlistReceiver.onReceive(mServiceContext, null); + + // A normal uid would undergo a rule change from denied to allowed on all chains, but we + // should not request any rule change for this core uid. + verify(mNetworkManager, never()).setFirewallUidRule(anyInt(), eq(coreUid), anyInt()); + verify(mNetworkManager, never()).setFirewallUidRules(anyInt(), + argThat(ar -> ArrayUtils.contains(ar, coreUid)), any(int[].class)); + } + + @SuppressWarnings("GuardedBy") // For not holding mUidRulesFirstLock + @Test + @RequiresFlagsEnabled(Flags.FLAG_NEVER_APPLY_RULES_TO_CORE_UIDS) + public void testRulesNeverAppliedToUidsWithoutInternetPermission() throws Exception { + clearInvocations(mNetworkManager); + + mService.mInternetPermissionMap.clear(); + expectHasInternetPermission(UID_A, false); + + // Enable all restrictions and add this uid to all allowlists. + mService.mDeviceIdleMode = true; + mService.mRestrictPower = true; + setRestrictBackground(true); + expectHasUseRestrictedNetworksPermission(UID_A, true); + enableRestrictedMode(true); + final NetworkPolicyManagerInternal internal = LocalServices.getService( + NetworkPolicyManagerInternal.class); + internal.setLowPowerStandbyActive(true); + internal.setLowPowerStandbyAllowlist(new int[]{UID_A}); + internal.onTempPowerSaveWhitelistChange(APP_ID_A, true, REASON_OTHER, "testing"); + + when(mPowerExemptionManager.getAllowListedAppIds(anyBoolean())) + .thenReturn(new int[]{APP_ID_A}); + mPowerAllowlistReceiver.onReceive(mServiceContext, null); + + // A normal uid would undergo a rule change from denied to allowed on all chains, but we + // should not request any rule this uid without the INTERNET permission. + verify(mNetworkManager, never()).setFirewallUidRule(anyInt(), eq(UID_A), anyInt()); + verify(mNetworkManager, never()).setFirewallUidRules(anyInt(), + argThat(ar -> ArrayUtils.contains(ar, UID_A)), any(int[].class)); + } + private boolean isUidState(int uid, int procState, int procStateSeq, int capability) { final NetworkPolicyManager.UidState uidState = mService.getUidStateForTest(uid); if (uidState == null) { diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java index d714db99f18f..791215695f57 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java @@ -121,6 +121,9 @@ public final class UserManagerTest { // Making a copy of mUsersToRemove to avoid ConcurrentModificationException mUsersToRemove.stream().toList().forEach(this::removeUser); mUserRemovalWaiter.close(); + + mUserManager.setUserRestriction(UserManager.DISALLOW_GRANT_ADMIN, false, + mContext.getUser()); } private void removeExistingUsers() { @@ -935,6 +938,35 @@ public final class UserManagerTest { @MediumTest @Test + @RequiresFlagsEnabled(android.multiuser.Flags.FLAG_UNICORN_MODE_REFACTORING_FOR_HSUM_READ_ONLY) + public void testSetUserAdminThrowsSecurityException() throws Exception { + UserInfo targetUser = createUser("SecondaryUser", /*flags=*/ 0); + assertThat(targetUser.isAdmin()).isFalse(); + + try { + // 1. Target User Restriction + mUserManager.setUserRestriction(UserManager.DISALLOW_GRANT_ADMIN, true, + targetUser.getUserHandle()); + assertThrows(SecurityException.class, () -> mUserManager.setUserAdmin(targetUser.id)); + + // 2. Current User Restriction + mUserManager.setUserRestriction(UserManager.DISALLOW_GRANT_ADMIN, false, + targetUser.getUserHandle()); + mUserManager.setUserRestriction(UserManager.DISALLOW_GRANT_ADMIN, true, + mContext.getUser()); + assertThrows(SecurityException.class, () -> mUserManager.setUserAdmin(targetUser.id)); + + } finally { + // Ensure restriction is removed even if test fails + mUserManager.setUserRestriction(UserManager.DISALLOW_GRANT_ADMIN, false, + targetUser.getUserHandle()); + mUserManager.setUserRestriction(UserManager.DISALLOW_GRANT_ADMIN, false, + mContext.getUser()); + } + } + + @MediumTest + @Test public void testRevokeUserAdmin() throws Exception { UserInfo userInfo = createUser("Admin", /*flags=*/ UserInfo.FLAG_ADMIN); assertThat(userInfo.isAdmin()).isTrue(); @@ -959,6 +991,37 @@ public final class UserManagerTest { @MediumTest @Test + @RequiresFlagsEnabled(android.multiuser.Flags.FLAG_UNICORN_MODE_REFACTORING_FOR_HSUM_READ_ONLY) + public void testRevokeUserAdminThrowsSecurityException() throws Exception { + UserInfo targetUser = createUser("SecondaryUser", /*flags=*/ 0); + assertThat(targetUser.isAdmin()).isFalse(); + + try { + // 1. Target User Restriction + mUserManager.setUserRestriction(UserManager.DISALLOW_GRANT_ADMIN, true, + targetUser.getUserHandle()); + assertThrows(SecurityException.class, () -> mUserManager + .revokeUserAdmin(targetUser.id)); + + // 2. Current User Restriction + mUserManager.setUserRestriction(UserManager.DISALLOW_GRANT_ADMIN, false, + targetUser.getUserHandle()); + mUserManager.setUserRestriction(UserManager.DISALLOW_GRANT_ADMIN, true, + mContext.getUser()); + assertThrows(SecurityException.class, () -> mUserManager + .revokeUserAdmin(targetUser.id)); + + } finally { + // Ensure restriction is removed even if test fails + mUserManager.setUserRestriction(UserManager.DISALLOW_GRANT_ADMIN, false, + targetUser.getUserHandle()); + mUserManager.setUserRestriction(UserManager.DISALLOW_GRANT_ADMIN, false, + mContext.getUser()); + } + } + + @MediumTest + @Test public void testGetProfileParent() throws Exception { assumeManagedUsersSupported(); int mainUserId = mUserManager.getMainUser().getIdentifier(); @@ -1184,6 +1247,23 @@ public final class UserManagerTest { } } + // Make sure createUser for ADMIN would fail if we have DISALLOW_GRANT_ADMIN. + @MediumTest + @Test + @RequiresFlagsEnabled(android.multiuser.Flags.FLAG_UNICORN_MODE_REFACTORING_FOR_HSUM_READ_ONLY) + public void testCreateAdminUser_disallowGrantAdmin() throws Exception { + final int creatorId = ActivityManager.getCurrentUser(); + final UserHandle creatorHandle = asHandle(creatorId); + mUserManager.setUserRestriction(UserManager.DISALLOW_GRANT_ADMIN, true, creatorHandle); + try { + UserInfo createdInfo = createUser("SecondaryUser", /*flags=*/ UserInfo.FLAG_ADMIN); + assertThat(createdInfo).isNull(); + } finally { + mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_USER, false, + creatorHandle); + } + } + // Make sure createProfile would fail if we have DISALLOW_ADD_CLONE_PROFILE. @MediumTest @Test diff --git a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java index fad10f7a7553..551808243640 100644 --- a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java +++ b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java @@ -182,7 +182,7 @@ public final class DeviceStateProviderImplTest { + " <device-state>\n" + " <identifier>1</identifier>\n" + " <properties>\n" - + " <property>PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS</property>\n" + + " <property>com.android.server.policy.PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS</property>\n" + " </properties>\n" + " <conditions/>\n" + " </device-state>\n" @@ -338,11 +338,9 @@ public final class DeviceStateProviderImplTest { + " <identifier>4</identifier>\n" + " <name>THERMAL_TEST</name>\n" + " <properties>\n" - + " <property>PROPERTY_EMULATED_ONLY</property>\n" - + " <property>PROPERTY_POLICY_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL" - + "</property>\n" - + " <property>PROPERTY_POLICY_UNSUPPORTED_WHEN_POWER_SAVE_MODE" - + "</property>\n" + + " <property>com.android.server.policy.PROPERTY_EMULATED_ONLY</property>\n" + + " <property>com.android.server.policy.PROPERTY_POLICY_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL</property>\n" + + " <property>com.android.server.policy.PROPERTY_POLICY_UNSUPPORTED_WHEN_POWER_SAVE_MODE</property>\n" + " </properties>\n" + " </device-state>\n" + "</device-state-config>\n"; diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ConditionProvidersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ConditionProvidersTest.java index f6e1162a5ada..af7f703e9c31 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ConditionProvidersTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ConditionProvidersTest.java @@ -16,7 +16,6 @@ package com.android.server.notification; -import static android.service.notification.Condition.SOURCE_USER_ACTION; import static android.service.notification.Condition.STATE_FALSE; import static android.service.notification.Condition.STATE_TRUE; @@ -31,13 +30,11 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; -import android.app.Flags; import android.content.ComponentName; import android.content.ServiceConnection; import android.content.pm.IPackageManager; import android.net.Uri; import android.os.IInterface; -import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; import android.service.notification.Condition; @@ -150,57 +147,6 @@ public class ConditionProvidersTest extends UiServiceTestCase { } @Test - @EnableFlags(Flags.FLAG_MODES_UI) - public void notifyConditions_appCannotUndoUserEnablement() { - ManagedServices.ManagedServiceInfo msi = mProviders.new ManagedServiceInfo( - mock(IInterface.class), new ComponentName("package", "cls"), 0, false, - mock(ServiceConnection.class), 33, 100); - // First, user enabled mode - Condition[] userConditions = new Condition[] { - new Condition(Uri.parse("a"), "summary", STATE_TRUE, SOURCE_USER_ACTION) - }; - mProviders.notifyConditions("package", msi, userConditions); - verify(mCallback).onConditionChanged(eq(Uri.parse("a")), eq(userConditions[0])); - - // Second, app tries to disable it, but cannot - Condition[] appConditions = new Condition[] { - new Condition(Uri.parse("a"), "summary", STATE_FALSE) - }; - mProviders.notifyConditions("package", msi, appConditions); - verify(mCallback).onConditionChanged(eq(Uri.parse("a")), eq(userConditions[0])); - } - - @Test - @EnableFlags(Flags.FLAG_MODES_UI) - public void notifyConditions_appCanTakeoverUserEnablement() { - ManagedServices.ManagedServiceInfo msi = mProviders.new ManagedServiceInfo( - mock(IInterface.class), new ComponentName("package", "cls"), 0, false, - mock(ServiceConnection.class), 33, 100); - // First, user enabled mode - Condition[] userConditions = new Condition[] { - new Condition(Uri.parse("a"), "summary", STATE_TRUE, SOURCE_USER_ACTION) - }; - mProviders.notifyConditions("package", msi, userConditions); - verify(mCallback).onConditionChanged(eq(Uri.parse("a")), eq(userConditions[0])); - - // Second, app now thinks the rule should be on due it its intelligence - Condition[] appConditions = new Condition[] { - new Condition(Uri.parse("a"), "summary", STATE_TRUE) - }; - mProviders.notifyConditions("package", msi, appConditions); - verify(mCallback).onConditionChanged(eq(Uri.parse("a")), eq(appConditions[0])); - - // Lastly, app can turn rule off when its intelligence think it should be off - appConditions = new Condition[] { - new Condition(Uri.parse("a"), "summary", STATE_FALSE) - }; - mProviders.notifyConditions("package", msi, appConditions); - verify(mCallback).onConditionChanged(eq(Uri.parse("a")), eq(appConditions[0])); - - verifyNoMoreInteractions(mCallback); - } - - @Test public void testRemoveDefaultFromConfig() { final int userId = 0; ComponentName oldDefaultComponent = ComponentName.unflattenFromString("package/Component1"); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java index 14ad15e23791..643ee4aadd80 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java @@ -2458,6 +2458,74 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { } @Test + public void testBeepVolume_politeNotif_AvalancheStrategy_mixedNotif() throws Exception { + mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS); + mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS); + mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS_ATTN_UPDATE); + TestableFlagResolver flagResolver = new TestableFlagResolver(); + flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50); + flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0); + initAttentionHelper(flagResolver); + + // Trigger avalanche trigger intent + final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); + intent.putExtra("state", false); + mAvalancheBroadcastReceiver.onReceive(getContext(), intent); + + // Regular notification: should beep at 0% volume + NotificationRecord r = getBeepyNotification(); + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + verifyBeepVolume(0.0f); + assertEquals(-1, r.getLastAudiblyAlertedMs()); + Mockito.reset(mRingtonePlayer); + + // Conversation notification + mChannel = new NotificationChannel("test2", "test2", IMPORTANCE_DEFAULT); + NotificationRecord r2 = getConversationNotificationRecord(mId, false /* insistent */, + false /* once */, true /* noisy */, false /* buzzy*/, false /* lights */, true, + true, false, null, Notification.GROUP_ALERT_ALL, false, mUser, mPkg, + "shortcut"); + + // Should beep at 100% volume + mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS); + assertNotEquals(-1, r2.getLastAudiblyAlertedMs()); + verifyBeepVolume(1.0f); + + // Conversation notification on a different channel + mChannel = new NotificationChannel("test3", "test3", IMPORTANCE_DEFAULT); + NotificationRecord r3 = getConversationNotificationRecord(mId, false /* insistent */, + false /* once */, true /* noisy */, false /* buzzy*/, false /* lights */, true, + true, false, null, Notification.GROUP_ALERT_ALL, false, mUser, mPkg, + "shortcut"); + + // Should beep at 50% volume + Mockito.reset(mRingtonePlayer); + mAttentionHelper.buzzBeepBlinkLocked(r3, DEFAULT_SIGNALS); + assertNotEquals(-1, r3.getLastAudiblyAlertedMs()); + verifyBeepVolume(0.5f); + + // 2nd update should beep at 0% volume + Mockito.reset(mRingtonePlayer); + mAttentionHelper.buzzBeepBlinkLocked(r3, DEFAULT_SIGNALS); + verifyBeepVolume(0.0f); + + // Set important conversation + mChannel.setImportantConversation(true); + r3 = getConversationNotificationRecord(mId, false /* insistent */, + false /* once */, true /* noisy */, false /* buzzy*/, false /* lights */, true, + true, false, null, Notification.GROUP_ALERT_ALL, false, mUser, mPkg, + "shortcut"); + + // important conversation should beep at 100% volume + Mockito.reset(mRingtonePlayer); + mAttentionHelper.buzzBeepBlinkLocked(r3, DEFAULT_SIGNALS); + verifyBeepVolume(1.0f); + + verify(mAccessibilityService, times(5)).sendAccessibilityEvent(any(), anyInt()); + assertNotEquals(-1, r3.getLastAudiblyAlertedMs()); + } + + @Test public void testBeepVolume_politeNotif_Avalanche_exemptEmergency() throws Exception { mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS); mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java index 60c4ac777906..e70ed5f256bf 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java @@ -33,6 +33,8 @@ import static android.service.notification.Condition.STATE_TRUE; import static android.service.notification.NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_ON; import static android.service.notification.ZenModeConfig.XML_VERSION_MODES_API; import static android.service.notification.ZenModeConfig.ZEN_TAG; +import static android.service.notification.ZenModeConfig.ZenRule.OVERRIDE_DEACTIVATE; +import static android.service.notification.ZenModeConfig.ZenRule.OVERRIDE_NONE; import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_IMPORTANT; import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_NONE; import static android.service.notification.ZenPolicy.PEOPLE_TYPE_ANYONE; @@ -524,7 +526,7 @@ public class ZenModeConfigTest extends UiServiceTestCase { rule.zenMode = INTERRUPTION_FILTER; rule.modified = true; rule.name = NAME; - rule.snoozing = true; + rule.setConditionOverride(OVERRIDE_DEACTIVATE); rule.pkg = OWNER.getPackageName(); rule.zenPolicy = POLICY; @@ -546,7 +548,7 @@ public class ZenModeConfigTest extends UiServiceTestCase { ZenModeConfig.ZenRule parceled = new ZenModeConfig.ZenRule(parcel); assertEquals(rule.pkg, parceled.pkg); - assertEquals(rule.snoozing, parceled.snoozing); + assertEquals(rule.getConditionOverride(), parceled.getConditionOverride()); assertEquals(rule.enabler, parceled.enabler); assertEquals(rule.component, parceled.component); assertEquals(rule.configurationActivity, parceled.configurationActivity); @@ -625,7 +627,7 @@ public class ZenModeConfigTest extends UiServiceTestCase { rule.zenMode = INTERRUPTION_FILTER; rule.modified = true; rule.name = NAME; - rule.snoozing = true; + rule.setConditionOverride(OVERRIDE_DEACTIVATE); rule.pkg = OWNER.getPackageName(); rule.zenPolicy = POLICY; rule.zenDeviceEffects = new ZenDeviceEffects.Builder() @@ -662,7 +664,7 @@ public class ZenModeConfigTest extends UiServiceTestCase { assertEquals(rule.pkg, fromXml.pkg); // always resets on reboot - assertFalse(fromXml.snoozing); + assertEquals(OVERRIDE_NONE, fromXml.getConditionOverride()); //should all match original assertEquals(rule.component, fromXml.component); assertEquals(rule.configurationActivity, fromXml.configurationActivity); @@ -1115,7 +1117,6 @@ public class ZenModeConfigTest extends UiServiceTestCase { rule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; rule.modified = true; rule.name = "name"; - rule.snoozing = false; rule.pkg = "b"; config.automaticRules.put("key", rule); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java index 9af00218dda4..91eb2edeee13 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java @@ -158,7 +158,10 @@ public class ZenModeDiffTest extends UiServiceTestCase { RuleDiff.FIELD_ZEN_DEVICE_EFFECTS, RuleDiff.FIELD_LEGACY_SUPPRESSED_EFFECTS)); } - if (!(Flags.modesApi() && Flags.modesUi())) { + if (Flags.modesApi() && Flags.modesUi()) { + exemptFields.add(RuleDiff.FIELD_SNOOZING); // Obsolete. + } else { + exemptFields.add(RuleDiff.FIELD_CONDITION_OVERRIDE); exemptFields.add(RuleDiff.FIELD_LEGACY_SUPPRESSED_EFFECTS); } return exemptFields; @@ -339,7 +342,7 @@ public class ZenModeDiffTest extends UiServiceTestCase { rule.zenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; rule.modified = false; rule.name = "name"; - rule.snoozing = true; + rule.setConditionOverride(ZenModeConfig.ZenRule.OVERRIDE_DEACTIVATE); rule.pkg = "a"; if (android.app.Flags.modesApi()) { rule.allowManualInvocation = true; diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java index 776a840466c8..ed8ebc842be8 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java @@ -51,6 +51,7 @@ import static android.provider.Settings.Global.ZEN_MODE_ALARMS; import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; import static android.provider.Settings.Global.ZEN_MODE_NO_INTERRUPTIONS; import static android.provider.Settings.Global.ZEN_MODE_OFF; +import static android.service.notification.Condition.SOURCE_CONTEXT; import static android.service.notification.Condition.SOURCE_SCHEDULE; import static android.service.notification.Condition.SOURCE_USER_ACTION; import static android.service.notification.Condition.STATE_FALSE; @@ -61,6 +62,9 @@ import static android.service.notification.ZenModeConfig.ORIGIN_INIT_USER; import static android.service.notification.ZenModeConfig.ORIGIN_UNKNOWN; import static android.service.notification.ZenModeConfig.ORIGIN_USER_IN_APP; import static android.service.notification.ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI; +import static android.service.notification.ZenModeConfig.ZenRule.OVERRIDE_ACTIVATE; +import static android.service.notification.ZenModeConfig.ZenRule.OVERRIDE_DEACTIVATE; +import static android.service.notification.ZenModeConfig.ZenRule.OVERRIDE_NONE; import static android.service.notification.ZenPolicy.PEOPLE_TYPE_CONTACTS; import static android.service.notification.ZenPolicy.PEOPLE_TYPE_NONE; import static android.service.notification.ZenPolicy.PEOPLE_TYPE_STARRED; @@ -2999,11 +3003,13 @@ public class ZenModeHelperTest extends UiServiceTestCase { assertWithMessage("Failure for origin " + origin.name()) .that(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF); assertWithMessage("Failure for origin " + origin.name()) - .that(mZenModeHelper.mConfig.automaticRules.get(activeRuleId).snoozing) - .isTrue(); + .that(mZenModeHelper.mConfig.automaticRules + .get(activeRuleId).getConditionOverride()) + .isEqualTo(OVERRIDE_DEACTIVATE); assertWithMessage("Failure for origin " + origin.name()) - .that(mZenModeHelper.mConfig.automaticRules.get(inactiveRuleId).snoozing) - .isFalse(); + .that(mZenModeHelper.mConfig.automaticRules + .get(inactiveRuleId).getConditionOverride()) + .isEqualTo(OVERRIDE_NONE); } } @@ -3038,16 +3044,20 @@ public class ZenModeHelperTest extends UiServiceTestCase { assertWithMessage("Failure for origin " + origin.name()).that( mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS); assertWithMessage("Failure for origin " + origin.name()).that( - config.automaticRules.get(activeRuleId).snoozing).isFalse(); + config.automaticRules.get(activeRuleId).getConditionOverride()) + .isEqualTo(OVERRIDE_NONE); assertWithMessage("Failure for origin " + origin.name()).that( - config.automaticRules.get(inactiveRuleId).snoozing).isFalse(); + config.automaticRules.get(inactiveRuleId).getConditionOverride()) + .isEqualTo(OVERRIDE_NONE); } else { assertWithMessage("Failure for origin " + origin.name()).that( mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF); assertWithMessage("Failure for origin " + origin.name()).that( - config.automaticRules.get(activeRuleId).snoozing).isTrue(); + config.automaticRules.get(activeRuleId).getConditionOverride()) + .isEqualTo(OVERRIDE_DEACTIVATE); assertWithMessage("Failure for origin " + origin.name()).that( - config.automaticRules.get(inactiveRuleId).snoozing).isFalse(); + config.automaticRules.get(inactiveRuleId).getConditionOverride()) + .isEqualTo(OVERRIDE_NONE); } } } @@ -4288,7 +4298,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { rule.zenMode = INTERRUPTION_FILTER_ZR; rule.modified = true; rule.name = NAME; - rule.snoozing = true; + rule.setConditionOverride(OVERRIDE_DEACTIVATE); rule.pkg = OWNER.getPackageName(); rule.zenPolicy = POLICY; @@ -4962,7 +4972,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.setAutomaticZenRuleState(createdId, new Condition(zenRule.getConditionId(), "", STATE_FALSE), - ORIGIN_APP, SYSTEM_UID); + ORIGIN_APP, CUSTOM_PKG_UID); assertTrue(latch.await(500, TimeUnit.MILLISECONDS)); if (CompatChanges.isChangeEnabled(ZenModeHelper.SEND_ACTIVATION_AZR_STATUSES)) { @@ -5000,7 +5010,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, ZenModeConfig.ORIGIN_SYSTEM, "", SYSTEM_UID); - assertEquals(false, mZenModeHelper.mConfig.automaticRules.get(createdId).snoozing); + assertEquals(OVERRIDE_NONE, + mZenModeHelper.mConfig.automaticRules.get(createdId).getConditionOverride()); } @Test @@ -5713,7 +5724,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { assertThat(storedRule.isAutomaticActive()).isFalse(); assertThat(storedRule.isTrueOrUnknown()).isFalse(); assertThat(storedRule.condition).isNull(); - assertThat(storedRule.snoozing).isFalse(); + assertThat(storedRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE); assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF); } @@ -6039,15 +6050,18 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, ZEN_MODE_IMPORTANT_INTERRUPTIONS); assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(1); - assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).snoozing).isFalse(); + assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).getConditionOverride()) + .isEqualTo(OVERRIDE_NONE); mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, ORIGIN_APP, "test", "test", 0); - assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).snoozing).isTrue(); + assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).getConditionOverride()) + .isEqualTo(OVERRIDE_DEACTIVATE); mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, ZEN_MODE_ALARMS); - assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).snoozing).isFalse(); + assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).getConditionOverride()) + .isEqualTo(OVERRIDE_NONE); assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).condition.state) .isEqualTo(STATE_TRUE); } @@ -6451,6 +6465,213 @@ public class ZenModeHelperTest extends UiServiceTestCase { ORIGIN_UNKNOWN); } + @Test + @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + public void setAutomaticZenRuleState_manualActivation_appliesOverride() { + AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond")) + .setPackage(mPkg) + .build(); + String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "adding", + CUSTOM_PKG_UID); + + mZenModeHelper.setAutomaticZenRuleState(ruleId, + new Condition(rule.getConditionId(), "manual-on", STATE_TRUE, SOURCE_USER_ACTION), + ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID); + + ZenRule zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_ACTIVATE); + assertThat(zenRule.condition).isNull(); + } + + @Test + @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + public void setAutomaticZenRuleState_manualActivationAndThenDeactivation_removesOverride() { + AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond")) + .setPackage(mPkg) + .build(); + String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "adding", + CUSTOM_PKG_UID); + Condition autoOn = new Condition(rule.getConditionId(), "auto-on", STATE_TRUE, + SOURCE_CONTEXT); + ZenRule zenRule; + + mZenModeHelper.setAutomaticZenRuleState(ruleId, + new Condition(rule.getConditionId(), "manual-on", STATE_TRUE, SOURCE_USER_ACTION), + ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID); + zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(zenRule.isAutomaticActive()).isTrue(); + assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_ACTIVATE); + assertThat(zenRule.condition).isNull(); + + mZenModeHelper.setAutomaticZenRuleState(ruleId, + new Condition(rule.getConditionId(), "manual-off", STATE_FALSE, SOURCE_USER_ACTION), + ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID); + zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(zenRule.isAutomaticActive()).isFalse(); + assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE); + assertThat(zenRule.condition).isNull(); + + // Bonus check: app has resumed control over the rule and can now turn it on. + mZenModeHelper.setAutomaticZenRuleState(ruleId, autoOn, ORIGIN_APP, CUSTOM_PKG_UID); + zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(zenRule.isAutomaticActive()).isTrue(); + assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE); + assertThat(zenRule.condition).isEqualTo(autoOn); + } + + @Test + @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + public void setAutomaticZenRuleState_manualDeactivationAndThenReactivation_removesOverride() { + AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond")) + .setPackage(mPkg) + .build(); + String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "adding", + CUSTOM_PKG_UID); + Condition autoOn = new Condition(rule.getConditionId(), "auto-on", STATE_TRUE, + SOURCE_CONTEXT); + Condition autoOff = new Condition(rule.getConditionId(), "auto-off", STATE_FALSE, + SOURCE_CONTEXT); + ZenRule zenRule; + + mZenModeHelper.setAutomaticZenRuleState(ruleId, autoOn, ORIGIN_APP, CUSTOM_PKG_UID); + zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(zenRule.isAutomaticActive()).isTrue(); + assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE); + assertThat(zenRule.condition).isEqualTo(autoOn); + + mZenModeHelper.setAutomaticZenRuleState(ruleId, + new Condition(rule.getConditionId(), "manual-off", STATE_FALSE, SOURCE_USER_ACTION), + ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID); + zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(zenRule.isAutomaticActive()).isFalse(); + assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_DEACTIVATE); + assertThat(zenRule.condition).isEqualTo(autoOn); + + mZenModeHelper.setAutomaticZenRuleState(ruleId, + new Condition(rule.getConditionId(), "manual-on", STATE_TRUE, SOURCE_USER_ACTION), + ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID); + zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(zenRule.isAutomaticActive()).isTrue(); + assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE); + assertThat(zenRule.condition).isEqualTo(autoOn); + + // Bonus check: app has resumed control over the rule and can now turn it off. + mZenModeHelper.setAutomaticZenRuleState(ruleId, autoOff, ORIGIN_APP, CUSTOM_PKG_UID); + zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(zenRule.isAutomaticActive()).isFalse(); + assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE); + assertThat(zenRule.condition).isEqualTo(autoOff); + } + + @Test + @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + public void setAutomaticZenRuleState_manualDeactivation_appliesOverride() { + AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond")) + .setPackage(mPkg) + .build(); + String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "adding", + CUSTOM_PKG_UID); + + mZenModeHelper.setAutomaticZenRuleState(ruleId, + new Condition(rule.getConditionId(), "auto-on", STATE_TRUE, SOURCE_CONTEXT), + ORIGIN_APP, CUSTOM_PKG_UID); + ZenRule zenRuleOn = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(zenRuleOn.isAutomaticActive()).isTrue(); + assertThat(zenRuleOn.getConditionOverride()).isEqualTo(OVERRIDE_NONE); + assertThat(zenRuleOn.condition).isNotNull(); + + mZenModeHelper.setAutomaticZenRuleState(ruleId, + new Condition(rule.getConditionId(), "manual-off", STATE_FALSE, SOURCE_USER_ACTION), + ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID); + ZenRule zenRuleOff = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(zenRuleOff.isAutomaticActive()).isFalse(); + assertThat(zenRuleOff.getConditionOverride()).isEqualTo(OVERRIDE_DEACTIVATE); + assertThat(zenRuleOff.condition).isNotNull(); + } + + @Test + @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + public void setAutomaticZenRuleState_ifManualActive_appCannotDeactivateBeforeActivating() { + AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond")) + .setPackage(mPkg) + .build(); + String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "adding", + CUSTOM_PKG_UID); + ZenRule zenRule; + + mZenModeHelper.setAutomaticZenRuleState(ruleId, + new Condition(rule.getConditionId(), "manual-on", STATE_TRUE, SOURCE_USER_ACTION), + ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID); + zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(zenRule.isAutomaticActive()).isTrue(); + assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_ACTIVATE); + + mZenModeHelper.setAutomaticZenRuleState(ruleId, + new Condition(rule.getConditionId(), "auto-off", STATE_FALSE, SOURCE_CONTEXT), + ORIGIN_APP, CUSTOM_PKG_UID); + zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(zenRule.isAutomaticActive()).isTrue(); + assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_ACTIVATE); + + mZenModeHelper.setAutomaticZenRuleState(ruleId, + new Condition(rule.getConditionId(), "auto-on", STATE_TRUE, SOURCE_CONTEXT), + ORIGIN_APP, CUSTOM_PKG_UID); + zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(zenRule.isAutomaticActive()).isTrue(); + assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE); + mZenModeHelper.setAutomaticZenRuleState(ruleId, + new Condition(rule.getConditionId(), "auto-off", STATE_FALSE, SOURCE_CONTEXT), + ORIGIN_APP, CUSTOM_PKG_UID); + zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(zenRule.isAutomaticActive()).isFalse(); + } + + @Test + @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + public void setAutomaticZenRuleState_ifManualInactive_appCannotReactivateBeforeDeactivating() { + AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond")) + .setPackage(mPkg) + .build(); + String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "adding", + CUSTOM_PKG_UID); + ZenRule zenRule; + + mZenModeHelper.setAutomaticZenRuleState(ruleId, + new Condition(rule.getConditionId(), "auto-on", STATE_TRUE, SOURCE_CONTEXT), + ORIGIN_APP, CUSTOM_PKG_UID); + zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(zenRule.isAutomaticActive()).isTrue(); + assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE); + + mZenModeHelper.setAutomaticZenRuleState(ruleId, + new Condition(rule.getConditionId(), "manual-off", STATE_FALSE, SOURCE_USER_ACTION), + ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID); + zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(zenRule.isAutomaticActive()).isFalse(); + assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_DEACTIVATE); + + mZenModeHelper.setAutomaticZenRuleState(ruleId, + new Condition(rule.getConditionId(), "auto-on", STATE_TRUE, SOURCE_CONTEXT), + ORIGIN_APP, CUSTOM_PKG_UID); + zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(zenRule.isAutomaticActive()).isFalse(); + assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_DEACTIVATE); + + mZenModeHelper.setAutomaticZenRuleState(ruleId, + new Condition(rule.getConditionId(), "auto-off", STATE_FALSE, SOURCE_CONTEXT), + ORIGIN_APP, CUSTOM_PKG_UID); + zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(zenRule.isAutomaticActive()).isFalse(); + assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE); + + mZenModeHelper.setAutomaticZenRuleState(ruleId, + new Condition(rule.getConditionId(), "auto-on", STATE_TRUE, SOURCE_CONTEXT), + ORIGIN_APP, CUSTOM_PKG_UID); + zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(zenRule.isAutomaticActive()).isTrue(); + assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE); + } + private static void addZenRule(ZenModeConfig config, String id, String ownerPkg, int zenMode, @Nullable ZenPolicy zenPolicy) { ZenRule rule = new ZenRule(); diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java index 72ef888aa061..6f06050f55ff 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java @@ -119,6 +119,7 @@ public class VibrationSettingsTest { USAGE_PHYSICAL_EMULATION, USAGE_RINGTONE, USAGE_TOUCH, + USAGE_IME_FEEDBACK }; @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule(); @@ -525,7 +526,7 @@ public class VibrationSettingsTest { setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF); for (int usage : ALL_USAGES) { - if (usage == USAGE_TOUCH) { + if (usage == USAGE_TOUCH || usage == USAGE_IME_FEEDBACK) { assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_FOR_SETTINGS); } else { assertVibrationNotIgnoredForUsage(usage); @@ -601,14 +602,14 @@ public class VibrationSettingsTest { @Test public void shouldIgnoreVibration_withKeyboardSettingsOff_shouldIgnoreKeyboardVibration() { + setKeyboardVibrationSettingsSupported(true); setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_MEDIUM); setUserSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, 0 /* OFF*/); - setKeyboardVibrationSettingsSupported(true); // Keyboard touch ignored. assertVibrationIgnoredForAttributes( new VibrationAttributes.Builder() - .setUsage(USAGE_TOUCH) + .setUsage(USAGE_IME_FEEDBACK) .setCategory(VibrationAttributes.CATEGORY_KEYBOARD) .build(), Vibration.Status.IGNORED_FOR_SETTINGS); @@ -617,7 +618,7 @@ public class VibrationSettingsTest { assertVibrationNotIgnoredForUsage(USAGE_TOUCH); assertVibrationNotIgnoredForAttributes( new VibrationAttributes.Builder() - .setUsage(USAGE_TOUCH) + .setUsage(USAGE_IME_FEEDBACK) .setCategory(VibrationAttributes.CATEGORY_KEYBOARD) .setFlags(VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF) .build()); @@ -625,9 +626,9 @@ public class VibrationSettingsTest { @Test public void shouldIgnoreVibration_withKeyboardSettingsOn_shouldNotIgnoreKeyboardVibration() { + setKeyboardVibrationSettingsSupported(true); setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF); setUserSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, 1 /* ON */); - setKeyboardVibrationSettingsSupported(true); // General touch ignored. assertVibrationIgnoredForUsage(USAGE_TOUCH, Vibration.Status.IGNORED_FOR_SETTINGS); @@ -635,16 +636,16 @@ public class VibrationSettingsTest { // Keyboard touch not ignored. assertVibrationNotIgnoredForAttributes( new VibrationAttributes.Builder() - .setUsage(USAGE_TOUCH) + .setUsage(USAGE_IME_FEEDBACK) .setCategory(VibrationAttributes.CATEGORY_KEYBOARD) .build()); } @Test - public void shouldIgnoreVibration_notSupportKeyboardVibration_ignoresKeyboardTouchVibration() { + public void shouldIgnoreVibration_notSupportKeyboardVibration_followsTouchFeedbackSettings() { + setKeyboardVibrationSettingsSupported(false); setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF); setUserSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, 1 /* ON */); - setKeyboardVibrationSettingsSupported(false); // General touch ignored. assertVibrationIgnoredForUsage(USAGE_TOUCH, Vibration.Status.IGNORED_FOR_SETTINGS); @@ -652,7 +653,7 @@ public class VibrationSettingsTest { // Keyboard touch ignored. assertVibrationIgnoredForAttributes( new VibrationAttributes.Builder() - .setUsage(USAGE_TOUCH) + .setUsage(USAGE_IME_FEEDBACK) .setCategory(VibrationAttributes.CATEGORY_KEYBOARD) .build(), Vibration.Status.IGNORED_FOR_SETTINGS); diff --git a/services/tests/wmtests/src/com/android/server/wm/CameraStateMonitorTests.java b/services/tests/wmtests/src/com/android/server/wm/CameraStateMonitorTests.java index 12f5714f91c7..d2232729f271 100644 --- a/services/tests/wmtests/src/com/android/server/wm/CameraStateMonitorTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/CameraStateMonitorTests.java @@ -43,10 +43,10 @@ import org.junit.runner.RunWith; import java.util.concurrent.Executor; /** - * Tests for {@link DisplayRotationCompatPolicy}. + * Tests for {@link CameraStateMonitor}. * * Build/Install/Run: - * atest WmTests:DisplayRotationCompatPolicyTests + * atest WmTests:CameraStateMonitorTests */ @SmallTest @Presubmit @@ -68,23 +68,14 @@ public final class CameraStateMonitorTests extends WindowTestsBase { private ActivityRecord mActivity; private Task mTask; - // Simulates a listener which will not react to the change on a particular activity. - private final FakeCameraCompatStateListener mNotInterestedListener = - new FakeCameraCompatStateListener( - /*onCameraOpenedReturnValue=*/ false, - /*simulateUnsuccessfulCloseOnce=*/ false); // Simulates a listener which will react to the change on a particular activity - for example // put the activity in a camera compat mode. - private final FakeCameraCompatStateListener mInterestedListener = - new FakeCameraCompatStateListener( - /*onCameraOpenedReturnValue=*/ true, - /*simulateUnsuccessfulCloseOnce=*/ false); + private final FakeCameraCompatStateListener mListener = + new FakeCameraCompatStateListener(/* simulateUnsuccessfulCloseOnce= */ false); // Simulates a listener which for some reason cannot process `onCameraClosed` event once it // first arrives - this means that the update needs to be postponed. private final FakeCameraCompatStateListener mListenerCannotClose = - new FakeCameraCompatStateListener( - /*onCameraOpenedReturnValue=*/ true, - /*simulateUnsuccessfulCloseOnce=*/ true); + new FakeCameraCompatStateListener(/* simulateUnsuccessfulCloseOnce= */ true); @Before public void setUp() throws Exception { @@ -129,44 +120,31 @@ public final class CameraStateMonitorTests extends WindowTestsBase { @After public void tearDown() { // Remove all listeners. - mCameraStateMonitor.removeCameraStateListener(mNotInterestedListener); - mCameraStateMonitor.removeCameraStateListener(mInterestedListener); + mCameraStateMonitor.removeCameraStateListener(mListener); mCameraStateMonitor.removeCameraStateListener(mListenerCannotClose); // Reset the listener's state. - mNotInterestedListener.resetCounters(); - mInterestedListener.resetCounters(); + mListener.resetCounters(); mListenerCannotClose.resetCounters(); } @Test public void testOnCameraOpened_listenerAdded_notifiesCameraOpened() { - mCameraStateMonitor.addCameraStateListener(mNotInterestedListener); + mCameraStateMonitor.addCameraStateListener(mListener); mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - assertEquals(1, mNotInterestedListener.mOnCameraOpenedCounter); + assertEquals(1, mListener.mOnCameraOpenedCounter); } @Test - public void testOnCameraOpened_listenerReturnsFalse_doesNotNotifyCameraClosed() { - mCameraStateMonitor.addCameraStateListener(mNotInterestedListener); - // Listener returns false on `onCameraOpened`. - mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - - mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1); - - assertEquals(0, mNotInterestedListener.mOnCameraClosedCounter); - } - - @Test - public void testOnCameraOpened_listenerReturnsTrue_notifyCameraClosed() { - mCameraStateMonitor.addCameraStateListener(mInterestedListener); + public void testOnCameraOpened_cameraClosed_notifyCameraClosed() { + mCameraStateMonitor.addCameraStateListener(mListener); // Listener returns true on `onCameraOpened`. mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1); - assertEquals(1, mInterestedListener.mOnCameraClosedCounter); + assertEquals(1, mListener.mOnCameraClosedCounter); } @Test @@ -182,32 +160,22 @@ public final class CameraStateMonitorTests extends WindowTestsBase { @Test public void testReconnectedToDifferentCamera_notifiesListener() { - mCameraStateMonitor.addCameraStateListener(mInterestedListener); + mCameraStateMonitor.addCameraStateListener(mListener); mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1); mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_2, TEST_PACKAGE_1); - assertEquals(2, mInterestedListener.mOnCameraOpenedCounter); + assertEquals(2, mListener.mOnCameraOpenedCounter); } @Test public void testDifferentAppConnectedToCamera_notifiesListener() { - mCameraStateMonitor.addCameraStateListener(mInterestedListener); + mCameraStateMonitor.addCameraStateListener(mListener); mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1); mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_2); - assertEquals(2, mInterestedListener.mOnCameraOpenedCounter); - } - - @Test - public void testCameraAlreadyClosed_notifiesListenerOnce() { - mCameraStateMonitor.addCameraStateListener(mInterestedListener); - mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1); - mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1); - - assertEquals(1, mInterestedListener.mOnCameraClosedCounter); + assertEquals(2, mListener.mOnCameraOpenedCounter); } private void configureActivity(@NonNull String packageName) { @@ -232,7 +200,6 @@ public final class CameraStateMonitorTests extends WindowTestsBase { int mOnCameraOpenedCounter = 0; int mOnCameraClosedCounter = 0; - boolean mOnCameraOpenedReturnValue = true; private boolean mOnCameraClosedReturnValue = true; /** @@ -242,17 +209,14 @@ public final class CameraStateMonitorTests extends WindowTestsBase { * subsequent calls. This fake implementation tests the * retry mechanism in {@link CameraStateMonitor}. */ - FakeCameraCompatStateListener(boolean onCameraOpenedReturnValue, - boolean simulateUnsuccessfulCloseOnce) { - mOnCameraOpenedReturnValue = onCameraOpenedReturnValue; + FakeCameraCompatStateListener(boolean simulateUnsuccessfulCloseOnce) { mOnCameraClosedReturnValue = !simulateUnsuccessfulCloseOnce; } @Override - public boolean onCameraOpened(@NonNull ActivityRecord cameraActivity, + public void onCameraOpened(@NonNull ActivityRecord cameraActivity, @NonNull String cameraId) { mOnCameraOpenedCounter++; - return mOnCameraOpenedReturnValue; } @Override diff --git a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java index 771e290f60fd..e57e36d36621 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java @@ -59,6 +59,7 @@ public class DimmerTests extends WindowTestsBase { TestWindowContainer(WindowManagerService wm) { super(wm); + setVisibleRequested(true); } @Override diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java index e2524a289b7d..ddadbc41a1c0 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java @@ -115,6 +115,17 @@ public class DisplayWindowSettingsTests extends WindowTestsBase { } @Test + public void testPrimaryDisplayUnchanged_whenWindowingModeAlreadySet_NoFreeformSupport() { + mPrimaryDisplay.getDefaultTaskDisplayArea().setWindowingMode( + WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW); + + mDisplayWindowSettings.applySettingsToDisplayLocked(mPrimaryDisplay); + + assertEquals(WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW, + mPrimaryDisplay.getDefaultTaskDisplayArea().getWindowingMode()); + } + + @Test public void testPrimaryDisplayDefaultToFullscreen_HasFreeformSupport_NonPc_NoDesktopMode() { mWm.mAtmService.mSupportsFreeformWindowManagement = true; diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java index ffaa2d820203..400fe8b05526 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java @@ -24,6 +24,7 @@ import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @@ -45,6 +46,12 @@ import org.mockito.invocation.InvocationOnMock; import java.util.function.Supplier; +/** + * Test class for {@link Letterbox}. + * <p> + * Build/Install/Run: + * atest WmTests:LetterboxTest + */ @SmallTest @Presubmit public class LetterboxTest { @@ -53,21 +60,21 @@ public class LetterboxTest { SurfaceControlMocker mSurfaces; SurfaceControl.Transaction mTransaction; - private boolean mAreCornersRounded = false; - private int mColor = Color.BLACK; - private boolean mHasWallpaperBackground = false; - private int mBlurRadius = 0; - private float mDarkScrimAlpha = 0.5f; private SurfaceControl mParentSurface = mock(SurfaceControl.class); + private AppCompatLetterboxOverrides mLetterboxOverrides; @Before public void setUp() throws Exception { mSurfaces = new SurfaceControlMocker(); + mLetterboxOverrides = mock(AppCompatLetterboxOverrides.class); + doReturn(false).when(mLetterboxOverrides).shouldLetterboxHaveRoundedCorners(); + doReturn(Color.valueOf(Color.BLACK)).when(mLetterboxOverrides) + .getLetterboxBackgroundColor(); + doReturn(false).when(mLetterboxOverrides).hasWallpaperBackgroundForLetterbox(); + doReturn(0).when(mLetterboxOverrides).getLetterboxWallpaperBlurRadiusPx(); + doReturn(0.5f).when(mLetterboxOverrides).getLetterboxWallpaperDarkScrimAlpha(); mLetterbox = new Letterbox(mSurfaces, StubTransaction::new, - () -> mAreCornersRounded, () -> Color.valueOf(mColor), - () -> mHasWallpaperBackground, () -> mBlurRadius, () -> mDarkScrimAlpha, - mock(AppCompatReachabilityPolicy.class), - () -> mParentSurface); + mock(AppCompatReachabilityPolicy.class), mLetterboxOverrides, () -> mParentSurface); mTransaction = spy(StubTransaction.class); } @@ -183,7 +190,8 @@ public class LetterboxTest { verify(mTransaction).setColor(mSurfaces.top, new float[]{0, 0, 0}); - mColor = Color.GREEN; + doReturn(Color.valueOf(Color.GREEN)).when(mLetterboxOverrides) + .getLetterboxBackgroundColor(); assertTrue(mLetterbox.needsApplySurfaceChanges()); @@ -200,12 +208,12 @@ public class LetterboxTest { verify(mTransaction).setAlpha(mSurfaces.top, 1.0f); assertFalse(mLetterbox.needsApplySurfaceChanges()); - mHasWallpaperBackground = true; + doReturn(true).when(mLetterboxOverrides).hasWallpaperBackgroundForLetterbox(); assertTrue(mLetterbox.needsApplySurfaceChanges()); applySurfaceChanges(); - verify(mTransaction).setAlpha(mSurfaces.fullWindowSurface, mDarkScrimAlpha); + verify(mTransaction).setAlpha(mSurfaces.fullWindowSurface, /* alpha */ 0.5f); } @Test @@ -234,7 +242,7 @@ public class LetterboxTest { @Test public void testApplySurfaceChanges_cornersRounded_surfaceFullWindowSurfaceCreated() { - mAreCornersRounded = true; + doReturn(true).when(mLetterboxOverrides).shouldLetterboxHaveRoundedCorners(); mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000)); applySurfaceChanges(); @@ -243,7 +251,7 @@ public class LetterboxTest { @Test public void testApplySurfaceChanges_wallpaperBackground_surfaceFullWindowSurfaceCreated() { - mHasWallpaperBackground = true; + doReturn(true).when(mLetterboxOverrides).hasWallpaperBackgroundForLetterbox(); mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000)); applySurfaceChanges(); @@ -252,7 +260,7 @@ public class LetterboxTest { @Test public void testNotIntersectsOrFullyContains_cornersRounded() { - mAreCornersRounded = true; + doReturn(true).when(mLetterboxOverrides).shouldLetterboxHaveRoundedCorners(); mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(0, 0)); applySurfaceChanges(); diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java index 695068a5842a..cf321ed89c89 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java @@ -107,12 +107,14 @@ public class LetterboxUiControllerTest extends WindowTestsBase { // Makes requested sizes different mainWindow.mRequestedWidth = opaqueBounds.width() - 1; mainWindow.mRequestedHeight = opaqueBounds.height() - 1; - assertNull(mActivity.mLetterboxUiController.getCropBoundsIfNeeded(mainWindow)); + final AppCompatLetterboxPolicy letterboxPolicy = + mActivity.mAppCompatController.getAppCompatLetterboxPolicy(); + assertNull(letterboxPolicy.getCropBoundsIfNeeded(mainWindow)); // Makes requested sizes equals mainWindow.mRequestedWidth = opaqueBounds.width(); mainWindow.mRequestedHeight = opaqueBounds.height(); - assertNotNull(mActivity.mLetterboxUiController.getCropBoundsIfNeeded(mainWindow)); + assertNotNull(letterboxPolicy.getCropBoundsIfNeeded(mainWindow)); } @Test @@ -123,12 +125,15 @@ public class LetterboxUiControllerTest extends WindowTestsBase { // Do not apply crop if taskbar is collapsed taskbar.setFrame(TASKBAR_COLLAPSED_BOUNDS); - assertNull(mController.getExpandedTaskbarOrNull(mainWindow)); + assertNull(AppCompatUtils.getExpandedTaskbarOrNull(mainWindow)); mLetterboxedPortraitTaskBounds.set(SCREEN_WIDTH / 4, SCREEN_HEIGHT / 4, SCREEN_WIDTH - SCREEN_WIDTH / 4, SCREEN_HEIGHT - SCREEN_HEIGHT / 4); - final Rect noCrop = mController.getCropBoundsIfNeeded(mainWindow); + final AppCompatLetterboxPolicy letterboxPolicy = + mActivity.mAppCompatController.getAppCompatLetterboxPolicy(); + + final Rect noCrop = letterboxPolicy.getCropBoundsIfNeeded(mainWindow); assertNotEquals(null, noCrop); assertEquals(0, noCrop.left); assertEquals(0, noCrop.top); @@ -145,12 +150,14 @@ public class LetterboxUiControllerTest extends WindowTestsBase { // Apply crop if taskbar is expanded taskbar.setFrame(TASKBAR_EXPANDED_BOUNDS); - assertNotNull(mController.getExpandedTaskbarOrNull(mainWindow)); + assertNotNull(AppCompatUtils.getExpandedTaskbarOrNull(mainWindow)); mLetterboxedPortraitTaskBounds.set(SCREEN_WIDTH / 4, 0, SCREEN_WIDTH - SCREEN_WIDTH / 4, SCREEN_HEIGHT); - final Rect crop = mController.getCropBoundsIfNeeded(mainWindow); + final AppCompatLetterboxPolicy letterboxPolicy = + mActivity.mAppCompatController.getAppCompatLetterboxPolicy(); + final Rect crop = letterboxPolicy.getCropBoundsIfNeeded(mainWindow); assertNotEquals(null, crop); assertEquals(0, crop.left); assertEquals(0, crop.top); @@ -169,7 +176,7 @@ public class LetterboxUiControllerTest extends WindowTestsBase { // Apply crop if taskbar is expanded taskbar.setFrame(TASKBAR_EXPANDED_BOUNDS); - assertNotNull(mController.getExpandedTaskbarOrNull(mainWindow)); + assertNotNull(AppCompatUtils.getExpandedTaskbarOrNull(mainWindow)); // With SizeCompat scaling doReturn(true).when(mActivity).inSizeCompatMode(); mainWindow.mInvGlobalScale = scaling; @@ -180,7 +187,9 @@ public class LetterboxUiControllerTest extends WindowTestsBase { final int appWidth = mLetterboxedPortraitTaskBounds.width(); final int appHeight = mLetterboxedPortraitTaskBounds.height(); - final Rect crop = mController.getCropBoundsIfNeeded(mainWindow); + final AppCompatLetterboxPolicy letterboxPolicy = + mActivity.mAppCompatController.getAppCompatLetterboxPolicy(); + final Rect crop = letterboxPolicy.getCropBoundsIfNeeded(mainWindow); assertNotEquals(null, crop); assertEquals(0, crop.left); assertEquals(0, crop.top); @@ -210,7 +219,8 @@ public class LetterboxUiControllerTest extends WindowTestsBase { insets.setRoundedCorners(roundedCorners); mAppCompatConfiguration.setLetterboxActivityCornersRadius(-1); - assertEquals(expectedRadius, mController.getRoundedCornersRadius(mainWindow)); + assertEquals(expectedRadius, mActivity.mAppCompatController.getAppCompatLetterboxPolicy() + .getRoundedCornersRadius(mainWindow)); } @Test @@ -223,18 +233,21 @@ public class LetterboxUiControllerTest extends WindowTestsBase { mainWindow.mInvGlobalScale = invGlobalScale; mAppCompatConfiguration.setLetterboxActivityCornersRadius(configurationRadius); + final AppCompatLetterboxPolicy letterboxPolicy = + mActivity.mAppCompatController.getAppCompatLetterboxPolicy(); + doReturn(true).when(mActivity).isInLetterboxAnimation(); - assertEquals(expectedRadius, mController.getRoundedCornersRadius(mainWindow)); + assertEquals(expectedRadius, letterboxPolicy.getRoundedCornersRadius(mainWindow)); doReturn(false).when(mActivity).isInLetterboxAnimation(); - assertEquals(expectedRadius, mController.getRoundedCornersRadius(mainWindow)); + assertEquals(expectedRadius, letterboxPolicy.getRoundedCornersRadius(mainWindow)); doReturn(false).when(mActivity).isVisibleRequested(); doReturn(false).when(mActivity).isVisible(); - assertEquals(0, mController.getRoundedCornersRadius(mainWindow)); + assertEquals(0, letterboxPolicy.getRoundedCornersRadius(mainWindow)); doReturn(true).when(mActivity).isInLetterboxAnimation(); - assertEquals(expectedRadius, mController.getRoundedCornersRadius(mainWindow)); + assertEquals(expectedRadius, letterboxPolicy.getRoundedCornersRadius(mainWindow)); } @Test @@ -244,14 +257,17 @@ public class LetterboxUiControllerTest extends WindowTestsBase { final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(/*taskbar=*/ null); mAppCompatConfiguration.setLetterboxActivityCornersRadius(configurationRadius); + final AppCompatLetterboxPolicy letterboxPolicy = + mActivity.mAppCompatController.getAppCompatLetterboxPolicy(); + mainWindow.mInvGlobalScale = -1f; - assertEquals(configurationRadius, mController.getRoundedCornersRadius(mainWindow)); + assertEquals(configurationRadius, letterboxPolicy.getRoundedCornersRadius(mainWindow)); mainWindow.mInvGlobalScale = 0f; - assertEquals(configurationRadius, mController.getRoundedCornersRadius(mainWindow)); + assertEquals(configurationRadius, letterboxPolicy.getRoundedCornersRadius(mainWindow)); mainWindow.mInvGlobalScale = 1f; - assertEquals(configurationRadius, mController.getRoundedCornersRadius(mainWindow)); + assertEquals(configurationRadius, letterboxPolicy.getRoundedCornersRadius(mainWindow)); } private WindowState mockForGetCropBoundsAndRoundedCorners(@Nullable InsetsSource taskbar) { @@ -292,7 +308,8 @@ public class LetterboxUiControllerTest extends WindowTestsBase { @Test public void testIsLetterboxEducationEnabled() { - mController.isLetterboxEducationEnabled(); + mActivity.mAppCompatController.getAppCompatLetterboxOverrides() + .isLetterboxEducationEnabled(); verify(mAppCompatConfiguration).getIsEducationEnabled(); } diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index aa997ac42c66..36696a16fa39 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -247,9 +247,9 @@ public class SizeCompatTests extends WindowTestsBase { .build(); mTask.addChild(translucentActivity); - spyOn(translucentActivity.mLetterboxUiController); - doReturn(true).when(translucentActivity.mLetterboxUiController) - .shouldShowLetterboxUi(any()); + spyOn(translucentActivity.mAppCompatController.getAppCompatLetterboxPolicy()); + doReturn(true).when(translucentActivity.mAppCompatController + .getAppCompatLetterboxPolicy()).shouldShowLetterboxUi(any()); addWindowToActivity(translucentActivity); translucentActivity.mRootWindowContainer.performSurfacePlacement(); @@ -257,7 +257,7 @@ public class SizeCompatTests extends WindowTestsBase { final Function<ActivityRecord, Rect> innerBoundsOf = (ActivityRecord a) -> { final Rect bounds = new Rect(); - a.mLetterboxUiController.getLetterboxInnerBounds(bounds); + a.getLetterboxInnerBounds(bounds); return bounds; }; final Runnable checkLetterboxPositions = () -> assertEquals(innerBoundsOf.apply(mActivity), @@ -369,7 +369,7 @@ public class SizeCompatTests extends WindowTestsBase { final Function<ActivityRecord, Rect> innerBoundsOf = (ActivityRecord a) -> { final Rect bounds = new Rect(); - a.mLetterboxUiController.getLetterboxInnerBounds(bounds); + a.getLetterboxInnerBounds(bounds); return bounds; }; @@ -632,16 +632,15 @@ public class SizeCompatTests extends WindowTestsBase { assertEquals(window, mActivity.findMainWindow()); - spyOn(mActivity.mLetterboxUiController); doReturn(true).when(mActivity).isVisibleRequested(); - assertTrue(mActivity.mLetterboxUiController.shouldShowLetterboxUi( - mActivity.findMainWindow())); + assertTrue(mActivity.mAppCompatController.getAppCompatLetterboxPolicy() + .shouldShowLetterboxUi(mActivity.findMainWindow())); window.mAttrs.flags |= FLAG_SHOW_WALLPAPER; - assertFalse(mActivity.mLetterboxUiController.shouldShowLetterboxUi( - mActivity.findMainWindow())); + assertFalse(mActivity.mAppCompatController.getAppCompatLetterboxPolicy() + .shouldShowLetterboxUi(mActivity.findMainWindow())); } @Test @@ -1757,7 +1756,8 @@ public class SizeCompatTests extends WindowTestsBase { // Compute the frames of the window and invoke {@link ActivityRecord#layoutLetterbox}. mActivity.mRootWindowContainer.performSurfacePlacement(); - LetterboxDetails letterboxDetails = mActivity.mLetterboxUiController.getLetterboxDetails(); + LetterboxDetails letterboxDetails = mActivity.mAppCompatController + .getAppCompatLetterboxPolicy().getLetterboxDetails(); assertEquals(dh / scale, letterboxDetails.getLetterboxInnerBounds().width()); assertEquals(dw / scale, letterboxDetails.getLetterboxInnerBounds().height()); @@ -3825,7 +3825,8 @@ public class SizeCompatTests extends WindowTestsBase { mActivity.mRootWindowContainer.performSurfacePlacement(); - LetterboxDetails letterboxDetails = mActivity.mLetterboxUiController.getLetterboxDetails(); + LetterboxDetails letterboxDetails = mActivity.mAppCompatController + .getAppCompatLetterboxPolicy().getLetterboxDetails(); // Letterboxed activity at bottom assertEquals(new Rect(0, 2100, 1400, 2800), mActivity.getBounds()); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java index 2b611b754edd..18255b8d82f8 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java @@ -1488,7 +1488,7 @@ public class WindowOrganizerTests extends WindowTestsBase { @Test public void testReorderWithParents() { /* - root + default TDA ____|______ | | firstTda secondTda @@ -1496,10 +1496,12 @@ public class WindowOrganizerTests extends WindowTestsBase { firstRootTask secondRootTask */ - final TaskDisplayArea firstTaskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea(); - final TaskDisplayArea secondTaskDisplayArea = createTaskDisplayArea( - mDisplayContent, mRootWindowContainer.mWmService, "TestTaskDisplayArea", + final TaskDisplayArea firstTaskDisplayArea = createTaskDisplayArea( + mDisplayContent, mRootWindowContainer.mWmService, "FirstTaskDisplayArea", FEATURE_VENDOR_FIRST); + final TaskDisplayArea secondTaskDisplayArea = createTaskDisplayArea( + mDisplayContent, mRootWindowContainer.mWmService, "SecondTaskDisplayArea", + FEATURE_VENDOR_FIRST + 1); final Task firstRootTask = firstTaskDisplayArea.createRootTask( WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */); final Task secondRootTask = secondTaskDisplayArea.createRootTask( @@ -1508,9 +1510,6 @@ public class WindowOrganizerTests extends WindowTestsBase { .setTask(firstRootTask).build(); final ActivityRecord secondActivity = new ActivityBuilder(mAtm) .setTask(secondRootTask).build(); - // This assertion is just a defense to ensure that firstRootTask is not the top most - // by default - assertThat(mDisplayContent.getTopRootTask()).isEqualTo(secondRootTask); WindowContainerTransaction wct = new WindowContainerTransaction(); // Reorder to top @@ -1533,6 +1532,67 @@ public class WindowOrganizerTests extends WindowTestsBase { } @Test + public void testReorderDisplayArea() { + /* + defaultTda + ____|______ + | | + firstTda secondTda + | | + firstRootTask secondRootTask + + */ + final TaskDisplayArea firstTaskDisplayArea = createTaskDisplayArea( + mDisplayContent, mRootWindowContainer.mWmService, "FirstTaskDisplayArea", + FEATURE_VENDOR_FIRST); + final TaskDisplayArea secondTaskDisplayArea = createTaskDisplayArea( + mDisplayContent, mRootWindowContainer.mWmService, "SecondTaskDisplayArea", + FEATURE_VENDOR_FIRST + 1); + final Task firstRootTask = firstTaskDisplayArea.createRootTask( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */); + final Task secondRootTask = secondTaskDisplayArea.createRootTask( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */); + final ActivityRecord firstActivity = new ActivityBuilder(mAtm) + .setTask(firstRootTask).build(); + final ActivityRecord secondActivity = new ActivityBuilder(mAtm) + .setTask(secondRootTask).build(); + + WindowContainerTransaction wct = new WindowContainerTransaction(); + + // Reorder to top + wct.reorder(firstTaskDisplayArea.mRemoteToken.toWindowContainerToken(), true /* onTop */, + true /* includingParents */); + mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct); + assertThat(mDisplayContent.getTopRootTask()).isEqualTo(firstRootTask); + + // Reorder to bottom + wct.reorder(firstTaskDisplayArea.mRemoteToken.toWindowContainerToken(), false /* onTop */, + true /* includingParents */); + mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct); + assertThat(mDisplayContent.getBottomMostTask()).isEqualTo(firstRootTask); + } + + @Test + public void testReparentDisplayAreaUnsupported() { + final TaskDisplayArea firstTaskDisplayArea = createTaskDisplayArea( + mDisplayContent, mRootWindowContainer.mWmService, "FirstTaskDisplayArea", + FEATURE_VENDOR_FIRST); + final TaskDisplayArea secondTaskDisplayArea = createTaskDisplayArea( + mDisplayContent, mRootWindowContainer.mWmService, "SecondTaskDisplayArea", + FEATURE_VENDOR_FIRST + 1); + + WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.reparent(firstTaskDisplayArea.mRemoteToken.toWindowContainerToken(), + secondTaskDisplayArea.mRemoteToken.toWindowContainerToken(), + true /* onTop */ + ); + + assertThrows(UnsupportedOperationException.class, () -> + mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct) + ); + } + + @Test public void testAppearDeferThenVanish() { final ITaskOrganizer organizer = registerMockOrganizer(); final Task rootTask = createRootTask(); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index ea2abf7ddcb8..4a8a2e600826 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -924,7 +924,8 @@ public class WindowTestsBase extends SystemServiceTestsBase { mSystemServicesTestRule.addProcess("pkgName", "procName", WindowManagerService.MY_PID, WindowManagerService.MY_UID); } - mAtm.mTaskFragmentOrganizerController.registerOrganizer(organizer, isSystemOrganizer); + mAtm.mTaskFragmentOrganizerController.registerOrganizer(organizer, isSystemOrganizer, + new Bundle()); } /** Creates a {@link DisplayContent} that supports IME and adds it to the system. */ diff --git a/services/usage/java/com/android/server/usage/TEST_MAPPING b/services/usage/java/com/android/server/usage/TEST_MAPPING index 6e845433492b..6ceb7635830f 100644 --- a/services/usage/java/com/android/server/usage/TEST_MAPPING +++ b/services/usage/java/com/android/server/usage/TEST_MAPPING @@ -1,12 +1,7 @@ { "presubmit": [ { - "name": "FrameworksCoreTests", - "options": [ - { - "include-filter": "android.app.usage" - } - ] + "name": "FrameworksCoreTests_usage" }, { "name": "FrameworksServicesTests", diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index b9a001d1f2b6..afd5720d9264 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -9992,6 +9992,16 @@ public class CarrierConfigManager { @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN) public static final String KEY_SATELLITE_ESOS_SUPPORTED_BOOL = "satellite_esos_supported_bool"; + /** + * Defines the NIDD (Non-IP Data Delivery) APN to be used for carrier roaming to satellite + * attachment. For more on NIDD, see 3GPP TS 29.542. + * Note this config is the only source of truth regarding the definition of the APN. + * + * @hide + */ + public static final String KEY_SATELLITE_NIDD_APN_NAME_STRING = + "satellite_nidd_apn_name_string"; + /** @hide */ @IntDef({ CARRIER_ROAMING_NTN_CONNECT_AUTOMATIC, @@ -11224,6 +11234,7 @@ public class CarrierConfigManager { sDefaults.putInt(KEY_EMERGENCY_CALL_TO_SATELLITE_T911_HANDOVER_TIMEOUT_MILLIS_INT, (int) TimeUnit.SECONDS.toMillis(30)); sDefaults.putBoolean(KEY_SATELLITE_ESOS_SUPPORTED_BOOL, false); + sDefaults.putString(KEY_SATELLITE_NIDD_APN_NAME_STRING, ""); sDefaults.putInt(KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT, 0); sDefaults.putInt(KEY_CARRIER_ROAMING_NTN_EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_INT, SatelliteManager.EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911); diff --git a/telephony/java/android/telephony/PcoData.java b/telephony/java/android/telephony/PcoData.java index 39e4f2f799d8..3cc32c657fd7 100644 --- a/telephony/java/android/telephony/PcoData.java +++ b/telephony/java/android/telephony/PcoData.java @@ -19,6 +19,8 @@ package android.telephony; import android.os.Parcel; import android.os.Parcelable; +import com.android.internal.telephony.uicc.IccUtils; + import java.util.Arrays; import java.util.Objects; @@ -84,8 +86,8 @@ public class PcoData implements Parcelable { @Override public String toString() { - return "PcoData(" + cid + ", " + bearerProto + ", " + pcoId + ", contents[" + - contents.length + "])"; + return "PcoData(" + cid + ", " + bearerProto + ", " + pcoId + " " + + IccUtils.bytesToHexString(contents) + ")"; } @Override diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java index db167c0592df..127bbff01575 100644 --- a/telephony/java/android/telephony/ServiceState.java +++ b/telephony/java/android/telephony/ServiceState.java @@ -1211,6 +1211,8 @@ public class ServiceState implements Parcelable { .append(", mIsDataRoamingFromRegistration=") .append(mIsDataRoamingFromRegistration) .append(", mIsIwlanPreferred=").append(mIsIwlanPreferred) + .append(", mIsUsingNonTerrestrialNetwork=") + .append(isUsingNonTerrestrialNetwork()) .append("}").toString(); } } diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java index ad6db2d07336..e657d7faad15 100644 --- a/telephony/java/android/telephony/satellite/SatelliteManager.java +++ b/telephony/java/android/telephony/satellite/SatelliteManager.java @@ -254,7 +254,6 @@ public final class SatelliteManager { */ public static final String KEY_PROVISION_SATELLITE_TOKENS = "provision_satellite"; - /** * The request was successfully processed. */ @@ -2643,7 +2642,7 @@ public final class SatelliteManager { @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN) public void requestProvisionSubscriberIds(@NonNull @CallbackExecutor Executor executor, - @NonNull OutcomeReceiver<List<ProvisionSubscriberId>, SatelliteException> callback) { + @NonNull OutcomeReceiver<List<SatelliteSubscriberInfo>, SatelliteException> callback) { Objects.requireNonNull(executor); Objects.requireNonNull(callback); @@ -2655,10 +2654,10 @@ public final class SatelliteManager { protected void onReceiveResult(int resultCode, Bundle resultData) { if (resultCode == SATELLITE_RESULT_SUCCESS) { if (resultData.containsKey(KEY_REQUEST_PROVISION_SUBSCRIBER_ID_TOKEN)) { - List<ProvisionSubscriberId> list = + List<SatelliteSubscriberInfo> list = resultData.getParcelableArrayList( KEY_REQUEST_PROVISION_SUBSCRIBER_ID_TOKEN, - ProvisionSubscriberId.class); + SatelliteSubscriberInfo.class); executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onResult(list))); } else { @@ -2743,9 +2742,9 @@ public final class SatelliteManager { } /** - * Deliver the list of provisioned satellite subscriber ids. + * Deliver the list of provisioned satellite subscriber infos. * - * @param list List of ProvisionSubscriberId. + * @param list The list of provisioned satellite subscriber infos. * @param executor The executor on which the callback will be called. * @param callback The callback object to which the result will be delivered. * @@ -2754,7 +2753,7 @@ public final class SatelliteManager { */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN) - public void provisionSatellite(@NonNull List<ProvisionSubscriberId> list, + public void provisionSatellite(@NonNull List<SatelliteSubscriberInfo> list, @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) { Objects.requireNonNull(executor); diff --git a/telephony/java/android/telephony/satellite/ProvisionSubscriberId.aidl b/telephony/java/android/telephony/satellite/SatelliteSubscriberInfo.aidl index fe46db878909..992c9aeefb40 100644 --- a/telephony/java/android/telephony/satellite/ProvisionSubscriberId.aidl +++ b/telephony/java/android/telephony/satellite/SatelliteSubscriberInfo.aidl @@ -16,4 +16,4 @@ package android.telephony.satellite; -parcelable ProvisionSubscriberId; +parcelable SatelliteSubscriberInfo; diff --git a/telephony/java/android/telephony/satellite/ProvisionSubscriberId.java b/telephony/java/android/telephony/satellite/SatelliteSubscriberInfo.java index 3e6f743e8a93..f26219bd0885 100644 --- a/telephony/java/android/telephony/satellite/ProvisionSubscriberId.java +++ b/telephony/java/android/telephony/satellite/SatelliteSubscriberInfo.java @@ -26,7 +26,7 @@ import com.android.internal.telephony.flags.Flags; import java.util.Objects; /** - * ProvisionSubscriberId + * SatelliteSubscriberInfo * * Satellite Gateway client will use these subscriber ids to register with satellite gateway service * which identify user subscription with unique subscriber ids. These subscriber ids can be any @@ -35,7 +35,7 @@ import java.util.Objects; * @hide */ @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN) -public final class ProvisionSubscriberId implements Parcelable { +public final class SatelliteSubscriberInfo implements Parcelable { /** provision subscriberId */ @NonNull private String mSubscriberId; @@ -49,14 +49,14 @@ public final class ProvisionSubscriberId implements Parcelable { /** * @hide */ - public ProvisionSubscriberId(@NonNull String subscriberId, @NonNull int carrierId, + public SatelliteSubscriberInfo(@NonNull String subscriberId, @NonNull int carrierId, @NonNull String niddApn) { this.mCarrierId = carrierId; this.mSubscriberId = subscriberId; this.mNiddApn = niddApn; } - private ProvisionSubscriberId(Parcel in) { + private SatelliteSubscriberInfo(Parcel in) { readFromParcel(in); } @@ -72,16 +72,16 @@ public final class ProvisionSubscriberId implements Parcelable { } @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN) - public static final @android.annotation.NonNull Creator<ProvisionSubscriberId> CREATOR = - new Creator<ProvisionSubscriberId>() { + public static final @android.annotation.NonNull Creator<SatelliteSubscriberInfo> CREATOR = + new Creator<SatelliteSubscriberInfo>() { @Override - public ProvisionSubscriberId createFromParcel(Parcel in) { - return new ProvisionSubscriberId(in); + public SatelliteSubscriberInfo createFromParcel(Parcel in) { + return new SatelliteSubscriberInfo(in); } @Override - public ProvisionSubscriberId[] newArray(int size) { - return new ProvisionSubscriberId[size]; + public SatelliteSubscriberInfo[] newArray(int size) { + return new SatelliteSubscriberInfo[size]; } }; @@ -148,7 +148,7 @@ public final class ProvisionSubscriberId implements Parcelable { public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - ProvisionSubscriberId that = (ProvisionSubscriberId) o; + SatelliteSubscriberInfo that = (SatelliteSubscriberInfo) o; return mSubscriberId.equals(that.mSubscriberId) && mCarrierId == that.mCarrierId && mNiddApn.equals(that.mNiddApn); } diff --git a/telephony/java/android/telephony/satellite/stub/ProvisionSubscriberId.aidl b/telephony/java/android/telephony/satellite/stub/SatelliteSubscriberInfo.aidl index 460de8c8113d..fb44f87ee1ee 100644 --- a/telephony/java/android/telephony/satellite/stub/ProvisionSubscriberId.aidl +++ b/telephony/java/android/telephony/satellite/stub/SatelliteSubscriberInfo.aidl @@ -19,7 +19,7 @@ package android.telephony.satellite.stub; /** * {@hide} */ -parcelable ProvisionSubscriberId { +parcelable SatelliteSubscriberInfo { /** provision subscriberId */ String subscriberId; diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 2f8e95713eba..89197032dcef 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -78,7 +78,7 @@ import android.telephony.satellite.ISatelliteModemStateCallback; import android.telephony.satellite.NtnSignalStrength; import android.telephony.satellite.SatelliteCapabilities; import android.telephony.satellite.SatelliteDatagram; -import android.telephony.satellite.ProvisionSubscriberId; +import android.telephony.satellite.SatelliteSubscriberInfo; import com.android.ims.internal.IImsServiceFeatureCallback; import com.android.internal.telephony.CellNetworkScanResult; import com.android.internal.telephony.IBooleanConsumer; @@ -3428,13 +3428,13 @@ interface ITelephony { void requestIsProvisioned(in String satelliteSubscriberId, in ResultReceiver result); /** - * Deliver the list of provisioned satellite subscriber ids. + * Deliver the list of provisioned satellite subscriber infos. * - * @param list List of provisioned satellite subscriber ids. + * @param list The list of provisioned satellite subscriber infos. * @param result The result receiver that returns whether deliver success or fail. * @hide */ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(" + "android.Manifest.permission.SATELLITE_COMMUNICATION)") - void provisionSatellite(in List<ProvisionSubscriberId> list, in ResultReceiver result); + void provisionSatellite(in List<SatelliteSubscriberInfo> list, in ResultReceiver result); } diff --git a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java index f518d53fa517..9676bd7cb718 100644 --- a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java +++ b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java @@ -967,7 +967,7 @@ public class IccUtils { byte[] serializedFplmns = new byte[dataLength]; int offset = 0; for (String fplmn : fplmns) { - if (offset >= dataLength) break; + if (offset + FPLMN_BYTE_SIZE > dataLength) break; stringToBcdPlmn(fplmn, serializedFplmns, offset); offset += FPLMN_BYTE_SIZE; } diff --git a/tests/FlickerTests/ActivityEmbedding/Android.bp b/tests/FlickerTests/ActivityEmbedding/Android.bp index e09fbf6adc02..c681ce96a269 100644 --- a/tests/FlickerTests/ActivityEmbedding/Android.bp +++ b/tests/FlickerTests/ActivityEmbedding/Android.bp @@ -24,6 +24,9 @@ package { default_applicable_licenses: ["frameworks_base_license"], } +//////////////////////////////////////////////////////////////////////////////// +// Begin to cleanup after CL merges + filegroup { name: "FlickerTestsOtherCommon-src", srcs: ["src/**/ActivityEmbeddingTestBase.kt"], @@ -82,3 +85,123 @@ android_test { ":FlickerTestsOtherCommon-src", ], } + +// End to cleanup after CL merges +//////////////////////////////////////////////////////////////////////////////// + +android_test { + name: "FlickerTestsActivityEmbedding", + defaults: ["FlickerTestsDefault"], + manifest: "AndroidManifest.xml", + package_name: "com.android.server.wm.flicker", + instrumentation_target_package: "com.android.server.wm.flicker", + test_config_template: "AndroidTestTemplate.xml", + srcs: ["src/**/*"], + static_libs: [ + "FlickerTestsBase", + "FlickerTestsOtherCommon", + ], + data: ["trace_config/*"], +} + +//////////////////////////////////////////////////////////////////////////////// +// Begin breakdowns for FlickerTestsActivityEmbedding module + +test_module_config { + name: "FlickerTestsActivityEmbedding-CatchAll", + base: "FlickerTestsActivityEmbedding", + exclude_filters: [ + "com.android.server.wm.flicker.activityembedding.close.CloseSecondaryActivityInSplitTest", + "com.android.server.wm.flicker.activityembedding.layoutchange.HorizontalSplitChangeRatioTest", + "com.android.server.wm.flicker.activityembedding.open.MainActivityStartsSecondaryWithAlwaysExpandTest", + "com.android.server.wm.flicker.activityembedding.open.OpenActivityEmbeddingPlaceholderSplitTest", + "com.android.server.wm.flicker.activityembedding.open.OpenActivityEmbeddingSecondaryToSplitTest", + "com.android.server.wm.flicker.activityembedding.open.OpenThirdActivityOverSplitTest", + "com.android.server.wm.flicker.activityembedding.open.OpenTrampolineActivityTest", + "com.android.server.wm.flicker.activityembedding.pip.SecondaryActivityEnterPipTest", + "com.android.server.wm.flicker.activityembedding.rotation.RotateSplitNoChangeTest", + "com.android.server.wm.flicker.activityembedding.rtl.RTLStartSecondaryWithPlaceholderTest", + "com.android.server.wm.flicker.activityembedding.splitscreen.EnterSystemSplitTest", + ], + test_suites: ["device-tests"], +} + +test_module_config { + name: "FlickerTestsActivityEmbedding-Close-CloseSecondaryActivityInSplitTest", + base: "FlickerTestsActivityEmbedding", + include_filters: ["com.android.server.wm.flicker.activityembedding.close.CloseSecondaryActivityInSplitTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "FlickerTestsActivityEmbedding-LayoutChange-HorizontalSplitChangeRatioTest", + base: "FlickerTestsActivityEmbedding", + include_filters: ["com.android.server.wm.flicker.activityembedding.layoutchange.HorizontalSplitChangeRatioTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "FlickerTestsActivityEmbedding-Open-MainActivityStartsSecondaryWithAlwaysExpandTest", + base: "FlickerTestsActivityEmbedding", + include_filters: ["com.android.server.wm.flicker.activityembedding.open.MainActivityStartsSecondaryWithAlwaysExpandTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "FlickerTestsActivityEmbedding-Open-OpenActivityEmbeddingPlaceholderSplitTest", + base: "FlickerTestsActivityEmbedding", + include_filters: ["com.android.server.wm.flicker.activityembedding.open.OpenActivityEmbeddingPlaceholderSplitTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "FlickerTestsActivityEmbedding-Open-OpenActivityEmbeddingSecondaryToSplitTest", + base: "FlickerTestsActivityEmbedding", + include_filters: ["com.android.server.wm.flicker.activityembedding.open.OpenActivityEmbeddingSecondaryToSplitTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "FlickerTestsActivityEmbedding-Open-OpenThirdActivityOverSplitTest", + base: "FlickerTestsActivityEmbedding", + include_filters: ["com.android.server.wm.flicker.activityembedding.open.OpenThirdActivityOverSplitTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "FlickerTestsActivityEmbedding-Open-OpenTrampolineActivityTest", + base: "FlickerTestsActivityEmbedding", + include_filters: ["com.android.server.wm.flicker.activityembedding.open.OpenTrampolineActivityTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "FlickerTestsActivityEmbedding-Pip-SecondaryActivityEnterPipTest", + base: "FlickerTestsActivityEmbedding", + include_filters: ["com.android.server.wm.flicker.activityembedding.pip.SecondaryActivityEnterPipTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "FlickerTestsActivityEmbedding-Rotation-RotateSplitNoChangeTest", + base: "FlickerTestsActivityEmbedding", + include_filters: ["com.android.server.wm.flicker.activityembedding.rotation.RotateSplitNoChangeTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "FlickerTestsActivityEmbedding-Rtl-RTLStartSecondaryWithPlaceholderTest", + base: "FlickerTestsActivityEmbedding", + include_filters: ["com.android.server.wm.flicker.activityembedding.rtl.RTLStartSecondaryWithPlaceholderTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "FlickerTestsActivityEmbedding-SplitScreen-EnterSystemSplitTest", + base: "FlickerTestsActivityEmbedding", + include_filters: ["com.android.server.wm.flicker.activityembedding.splitscreen.EnterSystemSplitTest"], + test_suites: ["device-tests"], +} + +// End breakdowns for FlickerTestsActivityEmbedding module +//////////////////////////////////////////////////////////////////////////////// diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotationTransition.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotationTransition.kt index ee2c05e82d51..06326f8cc8d2 100644 --- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotationTransition.kt +++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotationTransition.kt @@ -36,7 +36,7 @@ abstract class RotationTransition(flicker: LegacyFlickerTest) : ActivityEmbeddin teardown { testApp.exit(wmHelper) } transitions { this.setRotation(flicker.scenario.endRotation) - if (!flicker.scenario.isTablet) { + if (!usesTaskbar) { wmHelper.StateSyncBuilder() .add(navBarInPosition(flicker.scenario.isGesturalNavigation)) .waitForAndVerify() diff --git a/tests/FlickerTests/AppClose/Android.bp b/tests/FlickerTests/AppClose/Android.bp index d14a178fe316..8b45740aad7b 100644 --- a/tests/FlickerTests/AppClose/Android.bp +++ b/tests/FlickerTests/AppClose/Android.bp @@ -33,3 +33,34 @@ android_test { static_libs: ["FlickerTestsBase"], data: ["trace_config/*"], } + +//////////////////////////////////////////////////////////////////////////////// +// Begin breakdowns for FlickerTestsAppClose module + +test_module_config { + name: "FlickerTestsAppClose-CatchAll", + base: "FlickerTestsAppClose", + exclude_filters: [ + "com.android.server.wm.flicker.close.CloseAppBackButtonTest", + "com.android.server.wm.flicker.close.CloseAppHomeButtonTest", + "com.android.server.wm.flicker.close.", + ], + test_suites: ["device-tests"], +} + +test_module_config { + name: "FlickerTestsAppClose-CloseAppBackButtonTest", + base: "FlickerTestsAppClose", + include_filters: ["com.android.server.wm.flicker.close.CloseAppBackButtonTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "FlickerTestsAppClose-CloseAppHomeButtonTest", + base: "FlickerTestsAppClose", + include_filters: ["com.android.server.wm.flicker.close.CloseAppHomeButtonTest"], + test_suites: ["device-tests"], +} + +// End breakdowns for FlickerTestsAppClose module +//////////////////////////////////////////////////////////////////////////////// diff --git a/tests/FlickerTests/AppLaunch/Android.bp b/tests/FlickerTests/AppLaunch/Android.bp index 72a90650927f..b61739f100ab 100644 --- a/tests/FlickerTests/AppLaunch/Android.bp +++ b/tests/FlickerTests/AppLaunch/Android.bp @@ -15,6 +15,7 @@ // package { + default_team: "trendy_team_windowing_animations_transitions", // See: http://go/android-license-faq // A large-scale-change added 'default_applicable_licenses' to import // all of the 'license_kinds' from "frameworks_base_license" @@ -23,6 +24,9 @@ package { default_applicable_licenses: ["frameworks_base_license"], } +//////////////////////////////////////////////////////////////////////////////// +// Begin to cleanup after CL merges + filegroup { name: "FlickerTestsAppLaunchCommon-src", srcs: ["src/**/common/*"], @@ -69,3 +73,122 @@ android_test { ], data: ["trace_config/*"], } + +// End to cleanup after CL merges +//////////////////////////////////////////////////////////////////////////////// + +android_test { + name: "FlickerTestsAppLaunch", + defaults: ["FlickerTestsDefault"], + manifest: "AndroidManifest.xml", + test_config_template: "AndroidTestTemplate.xml", + srcs: ["src/**/*"], + static_libs: [ + "FlickerTestsBase", + "FlickerTestsAppLaunchCommon", + ], + data: ["trace_config/*"], +} + +//////////////////////////////////////////////////////////////////////////////// +// Begin breakdowns for FlickerTestsAppLaunch module + +test_module_config { + name: "FlickerTestsAppLaunch-CatchAll", + base: "FlickerTestsAppLaunch", + exclude_filters: [ + "com.android.server.wm.flicker.launch.TaskTransitionTest", + "com.android.server.wm.flicker.launch.ActivityTransitionTest", + "com.android.server.wm.flicker.launch.OpenAppFromIconColdTest", + "com.android.server.wm.flicker.launch.OpenAppFromIntentColdAfterCameraTest", + "com.android.server.wm.flicker.launch.OpenAppFromIntentColdTest", + "com.android.server.wm.flicker.launch.OpenAppFromIntentWarmTest", + "com.android.server.wm.flicker.launch.OpenAppFromLockscreenViaIntentTest", + "com.android.server.wm.flicker.launch.OpenAppFromOverviewTest", + "com.android.server.wm.flicker.launch.OpenCameraFromHomeOnDoubleClickPowerButtonTest", + "com.android.server.wm.flicker.launch.OpenTransferSplashscreenAppFromLauncherTransition", + "com.android.server.wm.flicker.launch.OverrideTaskTransitionTest", + "com.android.server.wm.flicker.launch.TaskTransitionTest", + ], + test_suites: ["device-tests"], +} + +test_module_config { + name: "FlickerTestsAppLaunch-ActivityTransitionTest", + base: "FlickerTestsAppLaunch", + include_filters: ["com.android.server.wm.flicker.launch.ActivityTransitionTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "FlickerTestsAppLaunch-OpenAppFromIconColdTest", + base: "FlickerTestsAppLaunch", + include_filters: ["com.android.server.wm.flicker.launch.OpenAppFromIconColdTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "FlickerTestsAppLaunch-OpenAppFromIntentColdAfterCameraTest", + base: "FlickerTestsAppLaunch", + include_filters: ["com.android.server.wm.flicker.launch.OpenAppFromIntentColdAfterCameraTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "FlickerTestsAppLaunch-OpenAppFromIntentColdTest", + base: "FlickerTestsAppLaunch", + include_filters: ["com.android.server.wm.flicker.launch.OpenAppFromIntentColdTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "FlickerTestsAppLaunch-OpenAppFromIntentWarmTest", + base: "FlickerTestsAppLaunch", + include_filters: ["com.android.server.wm.flicker.launch.OpenAppFromIntentWarmTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "FlickerTestsAppLaunch-OpenAppFromLockscreenViaIntentTest", + base: "FlickerTestsAppLaunch", + include_filters: ["com.android.server.wm.flicker.launch.OpenAppFromLockscreenViaIntentTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "FlickerTestsAppLaunch-OpenAppFromOverviewTest", + base: "FlickerTestsAppLaunch", + include_filters: ["com.android.server.wm.flicker.launch.OpenAppFromOverviewTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "FlickerTestsAppLaunch-OpenCameraFromHomeOnDoubleClickPowerButtonTest", + base: "FlickerTestsAppLaunch", + include_filters: ["com.android.server.wm.flicker.launch.OpenCameraFromHomeOnDoubleClickPowerButtonTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "FlickerTestsAppLaunch-OpenTransferSplashscreenAppFromLauncherTransition", + base: "FlickerTestsAppLaunch", + include_filters: ["com.android.server.wm.flicker.launch.OpenTransferSplashscreenAppFromLauncherTransition"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "FlickerTestsAppLaunch-OverrideTaskTransitionTest", + base: "FlickerTestsAppLaunch", + include_filters: ["com.android.server.wm.flicker.launch.OverrideTaskTransitionTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "FlickerTestsAppLaunch-TaskTransitionTest", + base: "FlickerTestsAppLaunch", + include_filters: ["com.android.server.wm.flicker.launch.TaskTransitionTest"], + test_suites: ["device-tests"], +} + +// End breakdowns for FlickerTestsAppLaunch module +//////////////////////////////////////////////////////////////////////////////// diff --git a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt index 44ae27c2ee4b..adeba72c9c96 100644 --- a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt +++ b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt @@ -75,7 +75,7 @@ open class OpenAppFromLockscreenViaIntentTest(flicker: LegacyFlickerTest) : @FlakyTest(bugId = 288341660) @Test fun navBarLayerVisibilityChanges() { - Assume.assumeFalse(flicker.scenario.isTablet) + Assume.assumeFalse(usesTaskbar) flicker.assertLayers { this.isInvisible(ComponentNameMatcher.NAV_BAR) .then() @@ -97,7 +97,7 @@ open class OpenAppFromLockscreenViaIntentTest(flicker: LegacyFlickerTest) : @FlakyTest(bugId = 293581770) @Test fun navBarWindowsVisibilityChanges() { - Assume.assumeFalse(flicker.scenario.isTablet) + Assume.assumeFalse(usesTaskbar) flicker.assertWm { this.isNonAppWindowInvisible(ComponentNameMatcher.NAV_BAR) .then() @@ -112,7 +112,7 @@ open class OpenAppFromLockscreenViaIntentTest(flicker: LegacyFlickerTest) : @Presubmit @Test fun taskBarLayerIsVisibleAtEnd() { - Assume.assumeTrue(flicker.scenario.isTablet) + Assume.assumeTrue(usesTaskbar) flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.TASK_BAR) } } @@ -170,7 +170,7 @@ open class OpenAppFromLockscreenViaIntentTest(flicker: LegacyFlickerTest) : @Presubmit @Test fun navBarLayerIsVisibleAtEnd() { - Assume.assumeFalse(flicker.scenario.isTablet) + Assume.assumeFalse(usesTaskbar) flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.NAV_BAR) } } @@ -184,7 +184,7 @@ open class OpenAppFromLockscreenViaIntentTest(flicker: LegacyFlickerTest) : @Presubmit @Test override fun appLayerBecomesVisible() { - Assume.assumeFalse(flicker.scenario.isTablet) + Assume.assumeFalse(usesTaskbar) super.appLayerBecomesVisible() } @@ -192,7 +192,7 @@ open class OpenAppFromLockscreenViaIntentTest(flicker: LegacyFlickerTest) : @FlakyTest(bugId = 227143265) @Test fun appLayerBecomesVisibleTablet() { - Assume.assumeTrue(flicker.scenario.isTablet) + Assume.assumeTrue(usesTaskbar) super.appLayerBecomesVisible() } diff --git a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/common/OpenAppFromIconTransition.kt b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/common/OpenAppFromIconTransition.kt index 8a3304b0343d..b497e3048759 100644 --- a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/common/OpenAppFromIconTransition.kt +++ b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/common/OpenAppFromIconTransition.kt @@ -28,6 +28,7 @@ abstract class OpenAppFromIconTransition(flicker: LegacyFlickerTest) : get() = { super.transition(this) setup { + // By default, launcher doesn't rotate on phones, but rotates on tablets if (flicker.scenario.isTablet) { tapl.setExpectedRotation(flicker.scenario.startRotation.value) } else { diff --git a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/common/OpenAppFromLockscreenTransition.kt b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/common/OpenAppFromLockscreenTransition.kt index f8fd35860f6f..a6e31d49a0e8 100644 --- a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/common/OpenAppFromLockscreenTransition.kt +++ b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/common/OpenAppFromLockscreenTransition.kt @@ -103,7 +103,7 @@ abstract class OpenAppFromLockscreenTransition(flicker: LegacyFlickerTest) : @Presubmit @Test open fun navBarLayerPositionAtEnd() { - Assume.assumeFalse(flicker.scenario.isTablet) + Assume.assumeFalse(usesTaskbar) flicker.navBarLayerPositionAtEnd() } diff --git a/tests/FlickerTests/IME/Android.bp b/tests/FlickerTests/IME/Android.bp index 78d93e1cb32a..f80e6b4b2f5e 100644 --- a/tests/FlickerTests/IME/Android.bp +++ b/tests/FlickerTests/IME/Android.bp @@ -24,6 +24,9 @@ package { default_applicable_licenses: ["frameworks_base_license"], } +//////////////////////////////////////////////////////////////////////////////// +// Begin to cleanup after CL merges + filegroup { name: "FlickerTestsImeCommon-src", srcs: ["src/**/common/*"], @@ -39,6 +42,9 @@ filegroup { srcs: ["src/**/ShowImeOnAppStart*"], } +// End to cleanup after CL merges +//////////////////////////////////////////////////////////////////////////////// + android_test { name: "FlickerTestsIme", defaults: ["FlickerTestsDefault"], @@ -53,6 +59,9 @@ android_test { data: ["trace_config/*"], } +//////////////////////////////////////////////////////////////////////////////// +// Begin to cleanup after CL merges + java_library { name: "FlickerTestsImeCommon", defaults: ["FlickerTestsDefault"], @@ -107,3 +116,140 @@ android_test { ], data: ["trace_config/*"], } + +// End to cleanup after CL merges +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +// Begin breakdowns for FlickerTestsIme module + +test_module_config { + name: "FlickerTestsIme-CatchAll", + base: "FlickerTestsIme", + exclude_filters: [ + "com.android.server.wm.flicker.ime.CloseImeOnDismissPopupDialogTest", + "com.android.server.wm.flicker.ime.CloseImeOnGoHomeTest", + "com.android.server.wm.flicker.ime.CloseImeShownOnAppStartOnGoHomeTest", + "com.android.server.wm.flicker.ime.CloseImeShownOnAppStartToAppOnPressBackTest", + "com.android.server.wm.flicker.ime.CloseImeToAppOnPressBackTest", + "com.android.server.wm.flicker.ime.CloseImeToHomeOnFinishActivityTest", + "com.android.server.wm.flicker.ime.OpenImeWindowToFixedPortraitAppTest", + "com.android.server.wm.flicker.ime.ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest", + "com.android.server.wm.flicker.ime.ShowImeOnAppStartWhenLaunchingAppFromOverviewTest", + "com.android.server.wm.flicker.ime.ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest", + "com.android.server.wm.flicker.ime.ShowImeOnAppStartWhenLaunchingAppTest", + "com.android.server.wm.flicker.ime.ShowImeOnUnlockScreenTest", + "com.android.server.wm.flicker.ime.ShowImeWhenFocusingOnInputFieldTest", + "com.android.server.wm.flicker.ime.ShowImeWhileDismissingThemedPopupDialogTest", + "com.android.server.wm.flicker.ime.ShowImeWhileEnteringOverviewTest", + ], + test_suites: ["device-tests"], +} + +test_module_config { + name: "FlickerTestsIme-CloseImeOnDismissPopupDialogTest", + base: "FlickerTestsIme", + include_filters: ["com.android.server.wm.flicker.ime.CloseImeOnDismissPopupDialogTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "FlickerTestsIme-CloseImeOnGoHomeTest", + base: "FlickerTestsIme", + include_filters: ["com.android.server.wm.flicker.ime.CloseImeOnGoHomeTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "FlickerTestsIme-CloseImeShownOnAppStartOnGoHomeTest", + base: "FlickerTestsIme", + include_filters: ["com.android.server.wm.flicker.ime.CloseImeShownOnAppStartOnGoHomeTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "FlickerTestsIme-CloseImeShownOnAppStartToAppOnPressBackTest", + base: "FlickerTestsIme", + include_filters: ["com.android.server.wm.flicker.ime.CloseImeShownOnAppStartToAppOnPressBackTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "FlickerTestsIme-CloseImeToAppOnPressBackTest", + base: "FlickerTestsIme", + include_filters: ["com.android.server.wm.flicker.ime.CloseImeToAppOnPressBackTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "FlickerTestsIme-CloseImeToHomeOnFinishActivityTest", + base: "FlickerTestsIme", + include_filters: ["com.android.server.wm.flicker.ime.CloseImeToHomeOnFinishActivityTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "FlickerTestsIme-OpenImeWindowToFixedPortraitAppTest", + base: "FlickerTestsIme", + include_filters: ["com.android.server.wm.flicker.ime.OpenImeWindowToFixedPortraitAppTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "FlickerTestsIme-ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest", + base: "FlickerTestsIme", + include_filters: ["com.android.server.wm.flicker.ime.ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "FlickerTestsIme-ShowImeOnAppStartWhenLaunchingAppFromOverviewTest", + base: "FlickerTestsIme", + include_filters: ["com.android.server.wm.flicker.ime.ShowImeOnAppStartWhenLaunchingAppFromOverviewTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "FlickerTestsIme-ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest", + base: "FlickerTestsIme", + include_filters: ["com.android.server.wm.flicker.ime.ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "FlickerTestsIme-ShowImeOnAppStartWhenLaunchingAppTest", + base: "FlickerTestsIme", + include_filters: ["com.android.server.wm.flicker.ime.ShowImeOnAppStartWhenLaunchingAppTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "FlickerTestsIme-ShowImeOnUnlockScreenTest", + base: "FlickerTestsIme", + include_filters: ["com.android.server.wm.flicker.ime.ShowImeOnUnlockScreenTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "FlickerTestsIme-ShowImeWhenFocusingOnInputFieldTest", + base: "FlickerTestsIme", + include_filters: ["com.android.server.wm.flicker.ime.ShowImeWhenFocusingOnInputFieldTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "FlickerTestsIme-ShowImeWhileDismissingThemedPopupDialogTest", + base: "FlickerTestsIme", + include_filters: ["com.android.server.wm.flicker.ime.ShowImeWhileDismissingThemedPopupDialogTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "FlickerTestsIme-ShowImeWhileEnteringOverviewTest", + base: "FlickerTestsIme", + include_filters: ["com.android.server.wm.flicker.ime.ShowImeWhileEnteringOverviewTest"], + test_suites: ["device-tests"], +} + +// End breakdowns for FlickerTestsIme module +//////////////////////////////////////////////////////////////////////////////// diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt index dc2bd1bc9996..522c68bba0d1 100644 --- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt +++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt @@ -72,7 +72,7 @@ class CloseImeToAppOnPressBackTest(flicker: LegacyFlickerTest) : BaseTest(flicke @Presubmit @Test override fun navBarLayerPositionAtStartAndEnd() { - Assume.assumeFalse(flicker.scenario.isTablet) + Assume.assumeFalse(usesTaskbar) Assume.assumeFalse(flicker.scenario.isLandscapeOrSeascapeAtStart) flicker.navBarLayerPositionAtStartAndEnd() } @@ -80,7 +80,7 @@ class CloseImeToAppOnPressBackTest(flicker: LegacyFlickerTest) : BaseTest(flicke @Presubmit @Test fun navBarLayerPositionAtStartAndEndLandscapeOrSeascapeAtStart() { - Assume.assumeFalse(flicker.scenario.isTablet) + Assume.assumeFalse(usesTaskbar) Assume.assumeTrue(flicker.scenario.isLandscapeOrSeascapeAtStart) flicker.navBarLayerPositionAtStartAndEnd() } diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt index c96c760e2d7b..638d594b0a48 100644 --- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt +++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt @@ -93,7 +93,7 @@ class ShowImeWhileEnteringOverviewTest(flicker: LegacyFlickerTest) : BaseTest(fl @Presubmit @Test fun navBarLayerIsVisibleAtStartAndEnd3Button() { - Assume.assumeFalse(flicker.scenario.isTablet) + Assume.assumeFalse(usesTaskbar) Assume.assumeFalse(flicker.scenario.isGesturalNavigation) flicker.navBarLayerIsVisibleAtStartAndEnd() } @@ -105,7 +105,7 @@ class ShowImeWhileEnteringOverviewTest(flicker: LegacyFlickerTest) : BaseTest(fl @Presubmit @Test fun navBarLayerIsInvisibleInLandscapeGestural() { - Assume.assumeFalse(flicker.scenario.isTablet) + Assume.assumeFalse(usesTaskbar) Assume.assumeTrue(flicker.scenario.isLandscapeOrSeascapeAtStart) Assume.assumeTrue(flicker.scenario.isGesturalNavigation) flicker.assertLayersStart { this.isVisible(ComponentNameMatcher.NAV_BAR) } @@ -121,7 +121,7 @@ class ShowImeWhileEnteringOverviewTest(flicker: LegacyFlickerTest) : BaseTest(fl fun statusBarLayerIsInvisibleInLandscapePhone() { Assume.assumeTrue(flicker.scenario.isLandscapeOrSeascapeAtStart) Assume.assumeTrue(flicker.scenario.isGesturalNavigation) - Assume.assumeFalse(flicker.scenario.isTablet) + Assume.assumeFalse(usesTaskbar) flicker.assertLayersStart { this.isVisible(ComponentNameMatcher.STATUS_BAR) } flicker.assertLayersEnd { this.isInvisible(ComponentNameMatcher.STATUS_BAR) } } @@ -135,7 +135,7 @@ class ShowImeWhileEnteringOverviewTest(flicker: LegacyFlickerTest) : BaseTest(fl fun statusBarLayerIsInvisibleInLandscapeTablet() { Assume.assumeTrue(flicker.scenario.isLandscapeOrSeascapeAtStart) Assume.assumeTrue(flicker.scenario.isGesturalNavigation) - Assume.assumeTrue(flicker.scenario.isTablet) + Assume.assumeTrue(usesTaskbar) flicker.statusBarLayerIsVisibleAtStartAndEnd() } @@ -174,7 +174,7 @@ class ShowImeWhileEnteringOverviewTest(flicker: LegacyFlickerTest) : BaseTest(fl @Test fun statusBarLayerIsInvisibleInLandscape() { Assume.assumeTrue(flicker.scenario.isLandscapeOrSeascapeAtStart) - Assume.assumeFalse(flicker.scenario.isTablet) + Assume.assumeFalse(usesTaskbar) flicker.assertLayersStart { this.isVisible(ComponentNameMatcher.STATUS_BAR) } flicker.assertLayersEnd { this.isInvisible(ComponentNameMatcher.STATUS_BAR) } } diff --git a/tests/FlickerTests/Notification/Android.bp b/tests/FlickerTests/Notification/Android.bp index 4648383b2771..06daaafacbd8 100644 --- a/tests/FlickerTests/Notification/Android.bp +++ b/tests/FlickerTests/Notification/Android.bp @@ -32,3 +32,57 @@ android_test { static_libs: ["FlickerTestsBase"], data: ["trace_config/*"], } + +//////////////////////////////////////////////////////////////////////////////// +// Begin breakdowns for FlickerTestsNotification module + +test_module_config { + name: "FlickerTestsNotification-CatchAll", + base: "FlickerTestsNotification", + exclude_filters: [ + "com.android.server.wm.flicker.notification.OpenAppFromLockscreenNotificationColdTest", + "com.android.server.wm.flicker.notification.OpenAppFromLockscreenNotificationWarmTest", + "com.android.server.wm.flicker.notification.OpenAppFromLockscreenNotificationWithOverlayAppTest", + "com.android.server.wm.flicker.notification.OpenAppFromNotificationColdTest", + "com.android.server.wm.flicker.notification.OpenAppFromNotificationWarmTest", + ], + test_suites: ["device-tests"], +} + +test_module_config { + name: "FlickerTestsNotification-OpenAppFromLockscreenNotificationColdTest", + base: "FlickerTestsNotification", + include_filters: ["com.android.server.wm.flicker.notification.OpenAppFromLockscreenNotificationColdTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "FlickerTestsNotification-OpenAppFromLockscreenNotificationWarmTest", + base: "FlickerTestsNotification", + include_filters: ["com.android.server.wm.flicker.notification.OpenAppFromLockscreenNotificationWarmTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "FlickerTestsNotification-OpenAppFromLockscreenNotificationWithOverlayAppTest", + base: "FlickerTestsNotification", + include_filters: ["com.android.server.wm.flicker.notification.OpenAppFromLockscreenNotificationWithOverlayAppTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "FlickerTestsNotification-OpenAppFromNotificationColdTest", + base: "FlickerTestsNotification", + include_filters: ["com.android.server.wm.flicker.notification.OpenAppFromNotificationColdTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "FlickerTestsNotification-OpenAppFromNotificationWarmTest", + base: "FlickerTestsNotification", + include_filters: ["com.android.server.wm.flicker.notification.OpenAppFromNotificationWarmTest"], + test_suites: ["device-tests"], +} + +// End breakdowns for FlickerTestsNotification module +//////////////////////////////////////////////////////////////////////////////// diff --git a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt index 07fc2300286a..ad70757a9a4d 100644 --- a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt +++ b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt @@ -151,7 +151,7 @@ open class OpenAppFromNotificationWarmTest(flicker: LegacyFlickerTest) : @Presubmit @Test open fun taskBarWindowIsVisibleAtEnd() { - Assume.assumeTrue(flicker.scenario.isTablet) + Assume.assumeTrue(usesTaskbar) flicker.taskBarWindowIsVisibleAtEnd() } @@ -163,7 +163,7 @@ open class OpenAppFromNotificationWarmTest(flicker: LegacyFlickerTest) : @Presubmit @Test open fun taskBarLayerIsVisibleAtEnd() { - Assume.assumeTrue(flicker.scenario.isTablet) + Assume.assumeTrue(usesTaskbar) flicker.taskBarLayerIsVisibleAtEnd() } @@ -171,7 +171,7 @@ open class OpenAppFromNotificationWarmTest(flicker: LegacyFlickerTest) : @Presubmit @Test open fun navBarLayerPositionAtEnd() { - Assume.assumeFalse(flicker.scenario.isTablet) + Assume.assumeFalse(usesTaskbar) flicker.navBarLayerPositionAtEnd() } @@ -179,14 +179,14 @@ open class OpenAppFromNotificationWarmTest(flicker: LegacyFlickerTest) : @Presubmit @Test open fun navBarLayerIsVisibleAtEnd() { - Assume.assumeFalse(flicker.scenario.isTablet) + Assume.assumeFalse(usesTaskbar) flicker.navBarLayerIsVisibleAtEnd() } @Presubmit @Test open fun navBarWindowIsVisibleAtEnd() { - Assume.assumeFalse(flicker.scenario.isTablet) + Assume.assumeFalse(usesTaskbar) flicker.navBarWindowIsVisibleAtEnd() } diff --git a/tests/FlickerTests/QuickSwitch/Android.bp b/tests/FlickerTests/QuickSwitch/Android.bp index 8755d0e3b304..4d5dba3d9221 100644 --- a/tests/FlickerTests/QuickSwitch/Android.bp +++ b/tests/FlickerTests/QuickSwitch/Android.bp @@ -32,3 +32,41 @@ android_test { static_libs: ["FlickerTestsBase"], data: ["trace_config/*"], } + +//////////////////////////////////////////////////////////////////////////////// +// Begin breakdowns for FlickerTestsQuickswitch module + +test_module_config { + name: "FlickerTestsQuickswitch-CatchAll", + base: "FlickerTestsQuickswitch", + exclude_filters: [ + "com.android.server.wm.flicker.quickswitch.QuickSwitchBetweenTwoAppsBackTest", + "com.android.server.wm.flicker.quickswitch.QuickSwitchBetweenTwoAppsForwardTest", + "com.android.server.wm.flicker.quickswitch.QuickSwitchFromLauncherTest", + ], + test_suites: ["device-tests"], +} + +test_module_config { + name: "FlickerTestsQuickswitch-QuickSwitchBetweenTwoAppsBackTest", + base: "FlickerTestsQuickswitch", + include_filters: ["com.android.server.wm.flicker.quickswitch.QuickSwitchBetweenTwoAppsBackTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "FlickerTestsQuickswitch-QuickSwitchBetweenTwoAppsForwardTest", + base: "FlickerTestsQuickswitch", + include_filters: ["com.android.server.wm.flicker.quickswitch.QuickSwitchBetweenTwoAppsForwardTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "FlickerTestsQuickswitch-QuickSwitchFromLauncherTest", + base: "FlickerTestsQuickswitch", + include_filters: ["com.android.server.wm.flicker.quickswitch.QuickSwitchFromLauncherTest"], + test_suites: ["device-tests"], +} + +// End breakdowns for FlickerTestsQuickswitch module +//////////////////////////////////////////////////////////////////////////////// diff --git a/tests/FlickerTests/Rotation/Android.bp b/tests/FlickerTests/Rotation/Android.bp index aceb8bad256f..0884ef9734b0 100644 --- a/tests/FlickerTests/Rotation/Android.bp +++ b/tests/FlickerTests/Rotation/Android.bp @@ -37,3 +37,41 @@ android_test { static_libs: ["FlickerTestsBase"], data: ["trace_config/*"], } + +//////////////////////////////////////////////////////////////////////////////// +// Begin breakdowns for FlickerTestsRotation module + +test_module_config { + name: "FlickerTestsAppRotation-CatchAll", + base: "FlickerTestsRotation", + exclude_filters: [ + "com.android.server.wm.flicker.rotation.ChangeAppRotationTest", + "com.android.server.wm.flicker.rotation.OpenShowWhenLockedSeamlessAppRotationTest", + "com.android.server.wm.flicker.rotation.SeamlessAppRotationTest", + ], + test_suites: ["device-tests"], +} + +test_module_config { + name: "FlickerTestsAppRotation-ChangeAppRotationTest", + base: "FlickerTestsRotation", + include_filters: ["com.android.server.wm.flicker.rotation.ChangeAppRotationTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "FlickerTestsAppRotation-OpenShowWhenLockedSeamlessAppRotationTest", + base: "FlickerTestsRotation", + include_filters: ["com.android.server.wm.flicker.rotation.OpenShowWhenLockedSeamlessAppRotationTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "FlickerTestsAppRotation-SeamlessAppRotationTest", + base: "FlickerTestsRotation", + include_filters: ["com.android.server.wm.flicker.rotation.SeamlessAppRotationTest"], + test_suites: ["device-tests"], +} + +// End breakdowns for FlickerTestsRotation module +//////////////////////////////////////////////////////////////////////////////// diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt index 060015bcc4b2..70d762e02af5 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt @@ -26,6 +26,7 @@ import android.tools.traces.component.ComponentNameMatcher import android.util.Log import androidx.test.platform.app.InstrumentationRegistry import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.wm.shell.Flags import org.junit.Assume import org.junit.AssumptionViolatedException import org.junit.Test @@ -48,6 +49,9 @@ constructor( private val logTag = this::class.java.simpleName + protected val usesTaskbar: Boolean + get() = flicker.scenario.isTablet || Flags.enableTaskbarOnPhones() + /** Specification of the test transition to execute */ abstract val transition: FlickerBuilder.() -> Unit @@ -87,7 +91,7 @@ constructor( @Presubmit @Test open fun navBarLayerIsVisibleAtStartAndEnd() { - Assume.assumeFalse(flicker.scenario.isTablet) + Assume.assumeFalse(usesTaskbar) flicker.navBarLayerIsVisibleAtStartAndEnd() } @@ -100,7 +104,7 @@ constructor( @Presubmit @Test open fun navBarLayerPositionAtStartAndEnd() { - Assume.assumeFalse(flicker.scenario.isTablet) + Assume.assumeFalse(usesTaskbar) flicker.navBarLayerPositionAtStartAndEnd() } @@ -112,7 +116,7 @@ constructor( @Presubmit @Test open fun navBarWindowIsAlwaysVisible() { - Assume.assumeFalse(flicker.scenario.isTablet) + Assume.assumeFalse(usesTaskbar) Assume.assumeFalse(flicker.scenario.isLandscapeOrSeascapeAtStart) flicker.navBarWindowIsAlwaysVisible() } @@ -126,7 +130,7 @@ constructor( @Presubmit @Test open fun navBarWindowIsVisibleAtStartAndEnd() { - Assume.assumeFalse(flicker.scenario.isTablet) + Assume.assumeFalse(usesTaskbar) flicker.navBarWindowIsVisibleAtStartAndEnd() } @@ -139,7 +143,7 @@ constructor( @Presubmit @Test open fun taskBarLayerIsVisibleAtStartAndEnd() { - Assume.assumeTrue(flicker.scenario.isTablet) + Assume.assumeTrue(usesTaskbar) flicker.taskBarLayerIsVisibleAtStartAndEnd() } @@ -151,7 +155,7 @@ constructor( @Presubmit @Test open fun taskBarWindowIsAlwaysVisible() { - Assume.assumeTrue(flicker.scenario.isTablet) + Assume.assumeTrue(usesTaskbar) flicker.taskBarWindowIsAlwaysVisible() } diff --git a/tests/Internal/Android.bp b/tests/Internal/Android.bp index 827ff4fbd989..ad98e47fa8f0 100644 --- a/tests/Internal/Android.bp +++ b/tests/Internal/Android.bp @@ -24,6 +24,7 @@ android_test { "flickerlib-parsers", "perfetto_trace_java_protos", "flickerlib-trace_processor_shell", + "ravenwood-junit", ], java_resource_dirs: ["res"], certificate: "platform", @@ -39,6 +40,7 @@ android_ravenwood_test { "platform-test-annotations", ], srcs: [ + "src/com/android/internal/graphics/ColorUtilsTest.java", "src/com/android/internal/util/ParcellingTests.java", ], auto_gen_config: true, diff --git a/tests/Internal/src/com/android/internal/graphics/ColorUtilsTest.java b/tests/Internal/src/com/android/internal/graphics/ColorUtilsTest.java index d0bb8e3745bc..38a22f2fc2f3 100644 --- a/tests/Internal/src/com/android/internal/graphics/ColorUtilsTest.java +++ b/tests/Internal/src/com/android/internal/graphics/ColorUtilsTest.java @@ -19,14 +19,19 @@ package com.android.internal.graphics; import static org.junit.Assert.assertTrue; import android.graphics.Color; +import android.platform.test.ravenwood.RavenwoodRule; import androidx.test.filters.SmallTest; +import org.junit.Rule; import org.junit.Test; @SmallTest public class ColorUtilsTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); + @Test public void calculateMinimumBackgroundAlpha_satisfiestContrast() { diff --git a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java b/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java index fad94d45c85d..7d0c5966b5dc 100644 --- a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java +++ b/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java @@ -126,30 +126,35 @@ public class PerfettoProtoLogImplTest { .setMessage("My Test Debug Log Message %b") .setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_DEBUG) .setGroupId(1) + .setLocation("com/test/MyTestClass.java:123") ).addMessages( Protolog.ProtoLogViewerConfig.MessageData.newBuilder() .setMessageId(2) .setMessage("My Test Verbose Log Message %b") .setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_VERBOSE) .setGroupId(1) + .setLocation("com/test/MyTestClass.java:342") ).addMessages( Protolog.ProtoLogViewerConfig.MessageData.newBuilder() .setMessageId(3) .setMessage("My Test Warn Log Message %b") .setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_WARN) .setGroupId(1) + .setLocation("com/test/MyTestClass.java:563") ).addMessages( Protolog.ProtoLogViewerConfig.MessageData.newBuilder() .setMessageId(4) .setMessage("My Test Error Log Message %b") .setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_ERROR) .setGroupId(1) + .setLocation("com/test/MyTestClass.java:156") ).addMessages( Protolog.ProtoLogViewerConfig.MessageData.newBuilder() .setMessageId(5) .setMessage("My Test WTF Log Message %b") .setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_WTF) .setGroupId(1) + .setLocation("com/test/MyTestClass.java:192") ); ViewerConfigInputStreamProvider viewerConfigInputStreamProvider = Mockito.mock( @@ -465,6 +470,26 @@ public class PerfettoProtoLogImplTest { .isEqualTo("My test message :: test, 2, 4, 6, 0.400000, true"); } + @Test + public void supportsLocationInformation() throws IOException { + PerfettoTraceMonitor traceMonitor = + PerfettoTraceMonitor.newBuilder().enableProtoLog(true).build(); + try { + traceMonitor.start(); + mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1, + LogDataType.BOOLEAN, new Object[]{true}); + } finally { + traceMonitor.stop(mWriter); + } + + final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig); + final ProtoLogTrace protolog = reader.readProtoLogTrace(); + + Truth.assertThat(protolog.messages).hasSize(1); + Truth.assertThat(protolog.messages.get(0).getLocation()) + .isEqualTo("com/test/MyTestClass.java:123"); + } + private long addMessageToConfig(ProtologCommon.ProtoLogLevel logLevel, String message) { final long messageId = new Random().nextLong(); mViewerConfigBuilder.addMessages(Protolog.ProtoLogViewerConfig.MessageData.newBuilder() diff --git a/tests/Internal/src/com/android/internal/util/ParcellingTests.java b/tests/Internal/src/com/android/internal/util/ParcellingTests.java index 65a3436a4c5e..fb63422cdf9f 100644 --- a/tests/Internal/src/com/android/internal/util/ParcellingTests.java +++ b/tests/Internal/src/com/android/internal/util/ParcellingTests.java @@ -18,6 +18,7 @@ package com.android.internal.util; import android.os.Parcel; import android.platform.test.annotations.Presubmit; +import android.platform.test.ravenwood.RavenwoodRule; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; @@ -26,6 +27,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.util.Parcelling.BuiltIn.ForInstant; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -38,6 +40,9 @@ import java.time.Instant; @RunWith(JUnit4.class) public class ParcellingTests { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); + private Parcel mParcel = Parcel.obtain(); @Test diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp index 3f9016ba4852..f43cf521edf5 100644 --- a/tools/aapt2/Android.bp +++ b/tools/aapt2/Android.bp @@ -113,6 +113,7 @@ cc_library_host_static { "io/ZipArchive.cpp", "link/AutoVersioner.cpp", "link/FeatureFlagsFilter.cpp", + "link/FlagDisabledResourceRemover.cpp", "link/ManifestFixer.cpp", "link/NoDefaultResourceRemover.cpp", "link/PrivateAttributeMover.cpp", @@ -189,6 +190,8 @@ cc_test_host { "integration-tests/CommandTests/**/*", "integration-tests/ConvertTest/**/*", "integration-tests/DumpTest/**/*", + ":resource-flagging-test-app-apk", + ":resource-flagging-test-app-r-java", ], } diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp index 9444dd968f5f..1c85e9ff231b 100644 --- a/tools/aapt2/ResourceParser.cpp +++ b/tools/aapt2/ResourceParser.cpp @@ -690,9 +690,7 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser, resource_format = item_iter->second.format; } - // Don't bother parsing the item if it is behind a disabled flag - if (out_resource->flag_status != FlagStatus::Disabled && - !ParseItem(parser, out_resource, resource_format)) { + if (!ParseItem(parser, out_resource, resource_format)) { return false; } return true; diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp index 2e6ad13d99de..b59b16574c42 100644 --- a/tools/aapt2/ResourceParser_test.cpp +++ b/tools/aapt2/ResourceParser_test.cpp @@ -69,13 +69,8 @@ class ResourceParserTest : public ::testing::Test { return TestParse(str, ConfigDescription{}); } - ::testing::AssertionResult TestParse(StringPiece str, ResourceParserOptions parserOptions) { - return TestParse(str, ConfigDescription{}, parserOptions); - } - - ::testing::AssertionResult TestParse( - StringPiece str, const ConfigDescription& config, - ResourceParserOptions parserOptions = ResourceParserOptions()) { + ::testing::AssertionResult TestParse(StringPiece str, const ConfigDescription& config) { + ResourceParserOptions parserOptions; ResourceParser parser(context_->GetDiagnostics(), &table_, android::Source{"test"}, config, parserOptions); @@ -247,19 +242,6 @@ TEST_F(ResourceParserTest, ParseStringTranslatableAttribute) { EXPECT_FALSE(TestParse(R"(<string name="foo4" translatable="yes">Translate</string>)")); } -TEST_F(ResourceParserTest, ParseStringBehindDisabledFlag) { - FeatureFlagProperties flag_properties(true, false); - ResourceParserOptions options; - options.feature_flag_values = {{"falseFlag", flag_properties}}; - ASSERT_TRUE(TestParse( - R"(<string name="foo" android:featureFlag="falseFlag" - xmlns:android="http://schemas.android.com/apk/res/android">foo</string>)", - options)); - - String* str = test::GetValue<String>(&table_, "string/foo"); - ASSERT_THAT(str, IsNull()); -} - TEST_F(ResourceParserTest, IgnoreXliffTagsOtherThanG) { std::string input = R"( <string name="foo" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> diff --git a/tools/aapt2/cmd/Convert.cpp b/tools/aapt2/cmd/Convert.cpp index c132792d374b..6c3eae11eab9 100644 --- a/tools/aapt2/cmd/Convert.cpp +++ b/tools/aapt2/cmd/Convert.cpp @@ -244,6 +244,10 @@ class Context : public IAaptContext { return verbose_; } + void SetVerbose(bool verbose) { + verbose_ = verbose; + } + int GetMinSdkVersion() override { return min_sdk_; } @@ -388,6 +392,8 @@ int ConvertCommand::Action(const std::vector<std::string>& args) { } Context context; + context.SetVerbose(verbose_); + StringPiece path = args[0]; unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(path, context.GetDiagnostics()); if (apk == nullptr) { diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp index 642a5618b6ad..56f52885b36d 100644 --- a/tools/aapt2/cmd/Link.cpp +++ b/tools/aapt2/cmd/Link.cpp @@ -57,6 +57,7 @@ #include "java/ManifestClassGenerator.h" #include "java/ProguardRules.h" #include "link/FeatureFlagsFilter.h" +#include "link/FlagDisabledResourceRemover.h" #include "link/Linkers.h" #include "link/ManifestFixer.h" #include "link/NoDefaultResourceRemover.h" @@ -1840,11 +1841,57 @@ class Linker { return validate(attr->value); } + class FlagDisabledStringVisitor : public DescendingValueVisitor { + public: + using DescendingValueVisitor::Visit; + + explicit FlagDisabledStringVisitor(android::StringPool& string_pool) + : string_pool_(string_pool) { + } + + void Visit(RawString* value) override { + value->value = string_pool_.MakeRef(""); + } + + void Visit(String* value) override { + value->value = string_pool_.MakeRef(""); + } + + void Visit(StyledString* value) override { + value->value = string_pool_.MakeRef(android::StyleString{{""}, {}}); + } + + private: + DISALLOW_COPY_AND_ASSIGN(FlagDisabledStringVisitor); + android::StringPool& string_pool_; + }; + // Writes the AndroidManifest, ResourceTable, and all XML files referenced by the ResourceTable // to the IArchiveWriter. bool WriteApk(IArchiveWriter* writer, proguard::KeepSet* keep_set, xml::XmlResource* manifest, ResourceTable* table) { TRACE_CALL(); + + FlagDisabledStringVisitor visitor(table->string_pool); + + for (auto& package : table->packages) { + for (auto& type : package->types) { + for (auto& entry : type->entries) { + for (auto& config_value : entry->values) { + if (config_value->flag_status == FlagStatus::Disabled) { + config_value->value->Accept(&visitor); + } + } + } + } + } + + if (!FlagDisabledResourceRemover{}.Consume(context_, table)) { + context_->GetDiagnostics()->Error(android::DiagMessage() + << "failed removing resources behind disabled flags"); + return 1; + } + const bool keep_raw_values = (context_->GetPackageType() == PackageType::kStaticLib) || options_.keep_raw_values; bool result = FlattenXml(context_, *manifest, kAndroidManifestPath, keep_raw_values, @@ -2331,6 +2378,12 @@ class Linker { return 1; }; + if (options_.generate_java_class_path || options_.generate_text_symbols_path) { + if (!GenerateJavaClasses()) { + return 1; + } + } + if (!WriteApk(archive_writer.get(), &proguard_keep_set, manifest_xml.get(), &final_table_)) { return 1; } @@ -2339,12 +2392,6 @@ class Linker { return 1; } - if (options_.generate_java_class_path || options_.generate_text_symbols_path) { - if (!GenerateJavaClasses()) { - return 1; - } - } - if (!WriteProguardFile(options_.generate_proguard_rules_path, proguard_keep_set)) { return 1; } diff --git a/tools/aapt2/integration-tests/FlaggedResourcesTest/Android.bp b/tools/aapt2/integration-tests/FlaggedResourcesTest/Android.bp new file mode 100644 index 000000000000..5932271d4d28 --- /dev/null +++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/Android.bp @@ -0,0 +1,81 @@ +// Copyright (C) 2024 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], + default_team: "trendy_team_android_resources", +} + +genrule { + name: "resource-flagging-test-app-compile", + tools: ["aapt2"], + srcs: [ + "res/values/bools.xml", + "res/values/bools2.xml", + "res/values/strings.xml", + ], + out: [ + "values_bools.arsc.flat", + "values_bools2.arsc.flat", + "values_strings.arsc.flat", + ], + cmd: "$(location aapt2) compile $(in) -o $(genDir) " + + "--feature-flags test.package.falseFlag:ro=false,test.package.trueFlag:ro=true", +} + +genrule { + name: "resource-flagging-test-app-apk", + tools: ["aapt2"], + // The first input file in the list must be the manifest + srcs: [ + "AndroidManifest.xml", + ":resource-flagging-test-app-compile", + ], + out: [ + "resapp.apk", + ], + cmd: "$(location aapt2) link -o $(out) --manifest $(in)", +} + +genrule { + name: "resource-flagging-test-app-r-java", + tools: ["aapt2"], + // The first input file in the list must be the manifest + srcs: [ + "AndroidManifest.xml", + ":resource-flagging-test-app-compile", + ], + out: [ + "resource-flagging-java/com/android/intenal/flaggedresources/R.java", + ], + cmd: "$(location aapt2) link -o $(genDir)/resapp.apk --java $(genDir)/resource-flagging-java --manifest $(in)", +} + +java_genrule { + name: "resource-flagging-test-app-apk-as-resource", + srcs: [ + ":resource-flagging-test-app-apk", + ], + out: ["apks_as_resources.res.zip"], + tools: ["soong_zip"], + + cmd: "mkdir -p $(genDir)/res/raw && " + + "cp $(in) $(genDir)/res/raw/$$(basename $(in)) && " + + "$(location soong_zip) -o $(out) -C $(genDir)/res -D $(genDir)/res", +} diff --git a/core/tests/resourceflaggingtests/TestAppAndroidManifest.xml b/tools/aapt2/integration-tests/FlaggedResourcesTest/AndroidManifest.xml index d6cdeb7b5231..d6cdeb7b5231 100644 --- a/core/tests/resourceflaggingtests/TestAppAndroidManifest.xml +++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/AndroidManifest.xml diff --git a/core/tests/resourceflaggingtests/flagged_resources_res/values/bools.xml b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/bools.xml index 8d0146511d1d..3e094fbd669c 100644 --- a/core/tests/resourceflaggingtests/flagged_resources_res/values/bools.xml +++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/bools.xml @@ -7,4 +7,6 @@ <bool name="res2" android:featureFlag="test.package.trueFlag">true</bool> <bool name="res3">false</bool> + + <bool name="res4" android:featureFlag="test.package.falseFlag">true</bool> </resources>
\ No newline at end of file diff --git a/core/tests/resourceflaggingtests/flagged_resources_res/values/bools2.xml b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/bools2.xml index e7563aa0fbdd..e7563aa0fbdd 100644 --- a/core/tests/resourceflaggingtests/flagged_resources_res/values/bools2.xml +++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/bools2.xml diff --git a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/strings.xml b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/strings.xml new file mode 100644 index 000000000000..5c0fca16fe39 --- /dev/null +++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/strings.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android"> + <string name="str">plain string</string> + + <string name="str1" android:featureFlag="test.package.falseFlag">DONTFIND</string> +</resources>
\ No newline at end of file diff --git a/tools/aapt2/link/FlagDisabledResourceRemover.cpp b/tools/aapt2/link/FlagDisabledResourceRemover.cpp new file mode 100644 index 000000000000..e3289e2a173a --- /dev/null +++ b/tools/aapt2/link/FlagDisabledResourceRemover.cpp @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "link/FlagDisabledResourceRemover.h" + +#include <algorithm> + +#include "ResourceTable.h" + +using android::ConfigDescription; + +namespace aapt { + +static bool KeepResourceEntry(const std::unique_ptr<ResourceEntry>& entry) { + if (entry->values.empty()) { + return true; + } + const auto end_iter = entry->values.end(); + const auto remove_iter = + std::stable_partition(entry->values.begin(), end_iter, + [](const std::unique_ptr<ResourceConfigValue>& value) -> bool { + return value->flag_status != FlagStatus::Disabled; + }); + + bool keep = remove_iter != entry->values.begin(); + + entry->values.erase(remove_iter, end_iter); + return keep; +} + +bool FlagDisabledResourceRemover::Consume(IAaptContext* context, ResourceTable* table) { + for (auto& pkg : table->packages) { + for (auto& type : pkg->types) { + const auto end_iter = type->entries.end(); + const auto remove_iter = std::stable_partition( + type->entries.begin(), end_iter, [](const std::unique_ptr<ResourceEntry>& entry) -> bool { + return KeepResourceEntry(entry); + }); + + type->entries.erase(remove_iter, end_iter); + } + } + return true; +} + +} // namespace aapt
\ No newline at end of file diff --git a/tools/aapt2/link/FlagDisabledResourceRemover.h b/tools/aapt2/link/FlagDisabledResourceRemover.h new file mode 100644 index 000000000000..2db2cb44c4cc --- /dev/null +++ b/tools/aapt2/link/FlagDisabledResourceRemover.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "android-base/macros.h" +#include "process/IResourceTableConsumer.h" + +namespace aapt { + +// Removes any resource that are behind disabled flags. +class FlagDisabledResourceRemover : public IResourceTableConsumer { + public: + FlagDisabledResourceRemover() = default; + + bool Consume(IAaptContext* context, ResourceTable* table) override; + + private: + DISALLOW_COPY_AND_ASSIGN(FlagDisabledResourceRemover); +}; + +} // namespace aapt diff --git a/tools/aapt2/link/FlaggedResources_test.cpp b/tools/aapt2/link/FlaggedResources_test.cpp new file mode 100644 index 000000000000..c901b5866279 --- /dev/null +++ b/tools/aapt2/link/FlaggedResources_test.cpp @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "LoadedApk.h" +#include "cmd/Dump.h" +#include "io/StringStream.h" +#include "test/Test.h" +#include "text/Printer.h" + +using ::aapt::io::StringOutputStream; +using ::aapt::text::Printer; +using testing::Eq; +using testing::Ne; + +namespace aapt { + +using FlaggedResourcesTest = CommandTestFixture; + +static android::NoOpDiagnostics noop_diag; + +void DumpStringPoolToString(LoadedApk* loaded_apk, std::string* output) { + StringOutputStream output_stream(output); + Printer printer(&output_stream); + + DumpStringsCommand command(&printer, &noop_diag); + ASSERT_EQ(command.Dump(loaded_apk), 0); + output_stream.Flush(); +} + +void DumpResourceTableToString(LoadedApk* loaded_apk, std::string* output) { + StringOutputStream output_stream(output); + Printer printer(&output_stream); + + DumpTableCommand command(&printer, &noop_diag); + ASSERT_EQ(command.Dump(loaded_apk), 0); + output_stream.Flush(); +} + +void DumpChunksToString(LoadedApk* loaded_apk, std::string* output) { + StringOutputStream output_stream(output); + Printer printer(&output_stream); + + DumpChunks command(&printer, &noop_diag); + ASSERT_EQ(command.Dump(loaded_apk), 0); + output_stream.Flush(); +} + +TEST_F(FlaggedResourcesTest, DisabledStringRemovedFromPool) { + auto apk_path = file::BuildPath({android::base::GetExecutableDirectory(), "resapp.apk"}); + auto loaded_apk = LoadedApk::LoadApkFromPath(apk_path, &noop_diag); + + std::string output; + DumpStringPoolToString(loaded_apk.get(), &output); + + std::string excluded = "DONTFIND"; + ASSERT_EQ(output.find(excluded), std::string::npos); +} + +TEST_F(FlaggedResourcesTest, DisabledResourcesRemovedFromTable) { + auto apk_path = file::BuildPath({android::base::GetExecutableDirectory(), "resapp.apk"}); + auto loaded_apk = LoadedApk::LoadApkFromPath(apk_path, &noop_diag); + + std::string output; + DumpResourceTableToString(loaded_apk.get(), &output); +} + +TEST_F(FlaggedResourcesTest, DisabledResourcesRemovedFromTableChunks) { + auto apk_path = file::BuildPath({android::base::GetExecutableDirectory(), "resapp.apk"}); + auto loaded_apk = LoadedApk::LoadApkFromPath(apk_path, &noop_diag); + + std::string output; + DumpChunksToString(loaded_apk.get(), &output); + + ASSERT_EQ(output.find("res4"), std::string::npos); + ASSERT_EQ(output.find("str1"), std::string::npos); +} + +TEST_F(FlaggedResourcesTest, DisabledResourcesInRJava) { + auto r_path = file::BuildPath({android::base::GetExecutableDirectory(), "resource-flagging-java", + "com", "android", "intenal", "flaggedresources", "R.java"}); + std::string r_contents; + ::android::base::ReadFileToString(r_path, &r_contents); + + ASSERT_NE(r_contents.find("public static final int res4"), std::string::npos); + ASSERT_NE(r_contents.find("public static final int str1"), std::string::npos); +} + +} // namespace aapt diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt index 5dde265c79fb..36bfbefdb086 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt @@ -58,17 +58,19 @@ class HostStubGen(val options: HostStubGenOptions) { // Dump the classes, if specified. options.inputJarDumpFile.ifSet { - PrintWriter(it).use { pw -> allClasses.dump(pw) } - log.i("Dump file created at $it") + log.iTime("Dump file created at $it") { + PrintWriter(it).use { pw -> allClasses.dump(pw) } + } } options.inputJarAsKeepAllFile.ifSet { - PrintWriter(it).use { - pw -> allClasses.forEach { - classNode -> printAsTextPolicy(pw, classNode) + log.iTime("Dump file created at $it") { + PrintWriter(it).use { pw -> + allClasses.forEach { classNode -> + printAsTextPolicy(pw, classNode) + } } } - log.i("Dump file created at $it") } // Build the filters. @@ -91,16 +93,18 @@ class HostStubGen(val options: HostStubGenOptions) { // Dump statistics, if specified. options.statsFile.ifSet { - PrintWriter(it).use { pw -> stats.dumpOverview(pw) } - log.i("Dump file created at $it") + log.iTime("Dump file created at $it") { + PrintWriter(it).use { pw -> stats.dumpOverview(pw) } + } } options.apiListFile.ifSet { - PrintWriter(it).use { pw -> - // TODO, when dumping a jar that's not framework-minus-apex.jar, we need to feed - // framework-minus-apex.jar so that we can dump inherited methods from it. - ApiDumper(pw, allClasses, null, filter).dump() + log.iTime("API list file created at $it") { + PrintWriter(it).use { pw -> + // TODO, when dumping a jar that's not framework-minus-apex.jar, we need to feed + // framework-minus-apex.jar so that we can dump inherited methods from it. + ApiDumper(pw, allClasses, null, filter).dump() + } } - log.i("API list file created at $it") } } @@ -221,47 +225,48 @@ class HostStubGen(val options: HostStubGenOptions) { log.i("Converting %s into [stub: %s, impl: %s] ...", inJar, outStubJar, outImplJar) log.i("ASM CheckClassAdapter is %s", if (enableChecker) "enabled" else "disabled") - val start = System.currentTimeMillis() - - val packageRedirector = PackageRedirectRemapper(options.packageRedirects) + log.iTime("Transforming jar") { + val packageRedirector = PackageRedirectRemapper(options.packageRedirects) - var itemIndex = 0 - var numItemsProcessed = 0 - var numItems = -1 // == Unknown + var itemIndex = 0 + var numItemsProcessed = 0 + var numItems = -1 // == Unknown - log.withIndent { - // Open the input jar file and process each entry. - ZipFile(inJar).use { inZip -> - - numItems = inZip.size() - val shardStart = numItems * shard / numShards - val shardNextStart = numItems * (shard + 1) / numShards - - maybeWithZipOutputStream(outStubJar) { stubOutStream -> - maybeWithZipOutputStream(outImplJar) { implOutStream -> - val inEntries = inZip.entries() - while (inEntries.hasMoreElements()) { - val entry = inEntries.nextElement() - val inShard = (shardStart <= itemIndex) && (itemIndex < shardNextStart) - itemIndex++ - if (!inShard) { - continue - } - convertSingleEntry(inZip, entry, stubOutStream, implOutStream, + log.withIndent { + // Open the input jar file and process each entry. + ZipFile(inJar).use { inZip -> + + numItems = inZip.size() + val shardStart = numItems * shard / numShards + val shardNextStart = numItems * (shard + 1) / numShards + + maybeWithZipOutputStream(outStubJar) { stubOutStream -> + maybeWithZipOutputStream(outImplJar) { implOutStream -> + val inEntries = inZip.entries() + while (inEntries.hasMoreElements()) { + val entry = inEntries.nextElement() + val inShard = (shardStart <= itemIndex) + && (itemIndex < shardNextStart) + itemIndex++ + if (!inShard) { + continue + } + convertSingleEntry( + inZip, entry, stubOutStream, implOutStream, filter, packageRedirector, remapper, - enableChecker, classes, errors, stats) - numItemsProcessed++ + enableChecker, classes, errors, stats + ) + numItemsProcessed++ + } + log.i("Converted all entries.") } - log.i("Converted all entries.") } + outStubJar?.let { log.i("Created stub: $it") } + outImplJar?.let { log.i("Created impl: $it") } } - outStubJar?.let { log.i("Created stub: $it") } - outImplJar?.let { log.i("Created impl: $it") } } + log.i("%d / %d item(s) processed.", numItemsProcessed, numItems) } - val end = System.currentTimeMillis() - log.i("Done transforming the jar in %.1f second(s). %d / %d item(s) processed.", - (end - start) / 1000.0, numItemsProcessed, numItems) } private fun <T> maybeWithZipOutputStream(filename: String?, block: (ZipOutputStream?) -> T): T { diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt index 18065ba56c52..ee4a06fb983d 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt @@ -185,6 +185,16 @@ class HostStubGenLogger { println(LogLevel.Debug, format, *args) } + inline fun <T> iTime(message: String, block: () -> T): T { + val start = System.currentTimeMillis() + val ret = block() + val end = System.currentTimeMillis() + + log.i("%s: took %.1f second(s).", message, (end - start) / 1000.0) + + return ret + } + inline fun forVerbose(block: () -> Unit) { if (isEnabled(LogLevel.Verbose)) { block() diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt index 92906a75b93a..2607df63f146 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt @@ -184,49 +184,50 @@ class ClassNodes { * Load all the classes, without code. */ fun loadClassStructures(inJar: String): ClassNodes { - log.i("Reading class structure from $inJar ...") - val start = System.currentTimeMillis() - - val allClasses = ClassNodes() - - log.withIndent { - ZipFile(inJar).use { inZip -> - val inEntries = inZip.entries() - - while (inEntries.hasMoreElements()) { - val entry = inEntries.nextElement() - - BufferedInputStream(inZip.getInputStream(entry)).use { bis -> - if (entry.name.endsWith(".class")) { - val cr = ClassReader(bis) - val cn = ClassNode() - cr.accept(cn, ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG - or ClassReader.SKIP_FRAMES) - if (!allClasses.addClass(cn)) { - log.w("Duplicate class found: ${cn.name}") - } - } else if (entry.name.endsWith(".dex")) { - // Seems like it's an ART jar file. We can't process it. - // It's a fatal error. - throw InvalidJarFileException( - "$inJar is not a desktop jar file. It contains a *.dex file.") - } else { - // Unknown file type. Skip. - while (bis.available() > 0) { - bis.skip((1024 * 1024).toLong()) + log.iTime("Reading class structure from $inJar") { + val allClasses = ClassNodes() + + log.withIndent { + ZipFile(inJar).use { inZip -> + val inEntries = inZip.entries() + + while (inEntries.hasMoreElements()) { + val entry = inEntries.nextElement() + + BufferedInputStream(inZip.getInputStream(entry)).use { bis -> + if (entry.name.endsWith(".class")) { + val cr = ClassReader(bis) + val cn = ClassNode() + cr.accept( + cn, ClassReader.SKIP_CODE + or ClassReader.SKIP_DEBUG + or ClassReader.SKIP_FRAMES + ) + if (!allClasses.addClass(cn)) { + log.w("Duplicate class found: ${cn.name}") + } + } else if (entry.name.endsWith(".dex")) { + // Seems like it's an ART jar file. We can't process it. + // It's a fatal error. + throw InvalidJarFileException( + "$inJar is not a desktop jar file." + + " It contains a *.dex file." + ) + } else { + // Unknown file type. Skip. + while (bis.available() > 0) { + bis.skip((1024 * 1024).toLong()) + } } } } } } + if (allClasses.size == 0) { + log.w("$inJar contains no *.class files.") + } + return allClasses } - if (allClasses.size == 0) { - log.w("$inJar contains no *.class files.") - } - - val end = System.currentTimeMillis() - log.i("Done reading class structure in %.1f second(s).", (end - start) / 1000.0) - return allClasses } } }
\ No newline at end of file diff --git a/tools/systemfeatures/Android.bp b/tools/systemfeatures/Android.bp new file mode 100644 index 000000000000..2cebfe9790d0 --- /dev/null +++ b/tools/systemfeatures/Android.bp @@ -0,0 +1,63 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +java_library_host { + name: "systemfeatures-gen-lib", + srcs: [ + "src/**/*.java", + "src/**/*.kt", + ], + static_libs: [ + "guava", + "javapoet", + ], +} + +java_binary_host { + name: "systemfeatures-gen-tool", + main_class: "com.android.systemfeatures.SystemFeaturesGenerator", + static_libs: ["systemfeatures-gen-lib"], +} + +// TODO(b/203143243): Add golden diff test for generated sources. +// Functional runtime behavior is covered in systemfeatures-gen-tests. +genrule { + name: "systemfeatures-gen-tests-srcs", + cmd: "$(location systemfeatures-gen-tool) com.android.systemfeatures.RwNoFeatures --readonly=false > $(location RwNoFeatures.java) && " + + "$(location systemfeatures-gen-tool) com.android.systemfeatures.RoNoFeatures --readonly=true > $(location RoNoFeatures.java) && " + + "$(location systemfeatures-gen-tool) com.android.systemfeatures.RwFeatures --readonly=false --feature=WATCH:1 --feature=WIFI:0 --feature=VULKAN:-1 --feature=AUTO: > $(location RwFeatures.java) && " + + "$(location systemfeatures-gen-tool) com.android.systemfeatures.RoFeatures --readonly=true --feature=WATCH:1 --feature=WIFI:0 --feature=VULKAN:-1 --feature=AUTO: > $(location RoFeatures.java)", + out: [ + "RwNoFeatures.java", + "RoNoFeatures.java", + "RwFeatures.java", + "RoFeatures.java", + ], + tools: ["systemfeatures-gen-tool"], +} + +java_test_host { + name: "systemfeatures-gen-tests", + test_suites: ["general-tests"], + srcs: [ + "tests/**/*.java", + ":systemfeatures-gen-tests-srcs", + ], + test_options: { + unit_test: true, + }, + static_libs: [ + "aconfig-annotations-lib", + "framework-annotations-lib", + "junit", + "objenesis", + "mockito", + "truth", + ], +} diff --git a/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt b/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt new file mode 100644 index 000000000000..9bfda451067f --- /dev/null +++ b/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt @@ -0,0 +1,218 @@ +/* + * 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.systemfeatures + +import com.google.common.base.CaseFormat +import com.squareup.javapoet.ClassName +import com.squareup.javapoet.JavaFile +import com.squareup.javapoet.MethodSpec +import com.squareup.javapoet.TypeSpec +import javax.lang.model.element.Modifier + +/* + * Simple Java code generator that takes as input a list of defined features and generates an + * accessory class based on the provided versions. + * + * <p>Example: + * + * <pre> + * <cmd> com.foo.RoSystemFeatures --readonly=true \ + * --feature=WATCH:0 --feature=AUTOMOTIVE: --feature=VULKAN:9348 + * </pre> + * + * This generates a class that has the following signature: + * + * <pre> + * package com.foo; + * public final class RoSystemFeatures { + * @AssumeTrueForR8 + * public static boolean hasFeatureWatch(Context context); + * @AssumeFalseForR8 + * public static boolean hasFeatureAutomotive(Context context); + * @AssumeTrueForR8 + * public static boolean hasFeatureVulkan(Context context); + * public static Boolean maybeHasFeature(String feature, int version); + * } + * </pre> + */ +object SystemFeaturesGenerator { + private const val FEATURE_ARG = "--feature=" + private const val READONLY_ARG = "--readonly=" + private val PACKAGEMANAGER_CLASS = ClassName.get("android.content.pm", "PackageManager") + private val CONTEXT_CLASS = ClassName.get("android.content", "Context") + private val ASSUME_TRUE_CLASS = + ClassName.get("com.android.aconfig.annotations", "AssumeTrueForR8") + private val ASSUME_FALSE_CLASS = + ClassName.get("com.android.aconfig.annotations", "AssumeFalseForR8") + + private fun usage() { + println("Usage: SystemFeaturesGenerator <outputClassName> [options]") + println(" Options:") + println(" --readonly=true|false Whether to encode features as build-time constants") + println(" --feature=\$NAME:\$VER A feature+version pair (blank version == disabled)") + } + + /** Main entrypoint for build-time system feature codegen. */ + @JvmStatic + fun main(args: Array<String>) { + if (args.size < 1) { + usage() + return + } + + var readonly = false + var outputClassName: ClassName? = null + val features = mutableListOf<FeatureInfo>() + for (arg in args) { + when { + arg.startsWith(READONLY_ARG) -> + readonly = arg.substring(READONLY_ARG.length).toBoolean() + arg.startsWith(FEATURE_ARG) -> { + features.add(parseFeatureArg(arg)) + } + else -> outputClassName = ClassName.bestGuess(arg) + } + } + + outputClassName + ?: run { + println("Output class name must be provided.") + usage() + return + } + + val classBuilder = + TypeSpec.classBuilder(outputClassName) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addJavadoc("@hide") + + addFeatureMethodsToClass(classBuilder, readonly, features) + addMaybeFeatureMethodToClass(classBuilder, readonly, features) + + // TODO(b/203143243): Add validation of build vs runtime values to ensure consistency. + JavaFile.builder(outputClassName.packageName(), classBuilder.build()) + .build() + .writeTo(System.out) + } + + /* + * Parses a feature argument of the form "--feature=$NAME:$VER", where "$VER" is optional. + * * "--feature=WATCH:0" -> Feature enabled w/ version 0 (default version when enabled) + * * "--feature=WATCH:7" -> Feature enabled w/ version 7 + * * "--feature=WATCH:" -> Feature disabled + */ + private fun parseFeatureArg(arg: String): FeatureInfo { + val featureArgs = arg.substring(FEATURE_ARG.length).split(":") + val name = featureArgs[0].let { if (!it.startsWith("FEATURE_")) "FEATURE_$it" else it } + val version = featureArgs.getOrNull(1)?.toIntOrNull() + return FeatureInfo(name, version) + } + + /* + * Adds per-feature query methods to the class with the form: + * {@code public static boolean hasFeatureX(Context context)}, + * returning the fallback value from PackageManager if not readonly. + */ + private fun addFeatureMethodsToClass( + builder: TypeSpec.Builder, + readonly: Boolean, + features: List<FeatureInfo> + ) { + for (feature in features) { + // Turn "FEATURE_FOO" into "hasFeatureFoo". + val methodName = + "has" + CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, feature.name) + val methodBuilder = + MethodSpec.methodBuilder(methodName) + .addModifiers(Modifier.PUBLIC, Modifier.STATIC) + .returns(Boolean::class.java) + .addParameter(CONTEXT_CLASS, "context") + + if (readonly) { + val featureEnabled = compareValues(feature.version, 0) >= 0 + methodBuilder.addAnnotation( + if (featureEnabled) ASSUME_TRUE_CLASS else ASSUME_FALSE_CLASS + ) + methodBuilder.addStatement("return $featureEnabled") + } else { + methodBuilder.addStatement( + "return hasFeatureFallback(context, \$T.\$N)", + PACKAGEMANAGER_CLASS, + feature.name + ) + } + builder.addMethod(methodBuilder.build()) + } + + if (!readonly) { + builder.addMethod( + MethodSpec.methodBuilder("hasFeatureFallback") + .addModifiers(Modifier.PRIVATE, Modifier.STATIC) + .returns(Boolean::class.java) + .addParameter(CONTEXT_CLASS, "context") + .addParameter(String::class.java, "featureName") + .addStatement( + "return context.getPackageManager().hasSystemFeature(featureName, 0)" + ) + .build() + ) + } + } + + /* + * Adds a generic query method to the class with the form: {@code public static boolean + * maybeHasFeature(String featureName, int version)}, returning null if the feature version is + * undefined or not readonly. + * + * This method is useful for internal usage within the framework, e.g., from the implementation + * of {@link android.content.pm.PackageManager#hasSystemFeature(Context)}, when we may only + * want a valid result if it's defined as readonly, and we want a custom fallback otherwise + * (e.g., to the existing runtime binder query). + */ + private fun addMaybeFeatureMethodToClass( + builder: TypeSpec.Builder, + readonly: Boolean, + features: List<FeatureInfo> + ) { + val methodBuilder = + MethodSpec.methodBuilder("maybeHasFeature") + .addModifiers(Modifier.PUBLIC, Modifier.STATIC) + .addAnnotation(ClassName.get("android.annotation", "Nullable")) + .returns(Boolean::class.javaObjectType) // Use object type for nullability + .addParameter(String::class.java, "featureName") + .addParameter(Int::class.java, "version") + + if (readonly) { + methodBuilder.beginControlFlow("switch (featureName)") + for (feature in features) { + methodBuilder.addCode("case \$T.\$N: ", PACKAGEMANAGER_CLASS, feature.name) + if (feature.version != null) { + methodBuilder.addStatement("return \$L >= version", feature.version) + } else { + methodBuilder.addStatement("return false") + } + } + methodBuilder.addCode("default: ") + methodBuilder.addStatement("break") + methodBuilder.endControlFlow() + } + methodBuilder.addStatement("return null") + builder.addMethod(methodBuilder.build()) + } + + private data class FeatureInfo(val name: String, val version: Int?) +} diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/MaximizeAppWindowTest.kt b/tools/systemfeatures/tests/Context.java index f89908228bbf..630bc0771a01 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/MaximizeAppWindowTest.kt +++ b/tools/systemfeatures/tests/Context.java @@ -14,21 +14,14 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.desktopmode.functional +package android.content; -import android.platform.test.annotations.Postsubmit -import com.android.wm.shell.flicker.service.desktopmode.scenarios.MaximizeAppWindow -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.BlockJUnit4ClassRunner +import android.content.pm.PackageManager; -/** Functional test for [MaximizeAppWindow]. */ -@RunWith(BlockJUnit4ClassRunner::class) -@Postsubmit -open class MaximizeAppWindowTest : MaximizeAppWindow() -{ - @Test - override fun maximizeAppWindow() { - super.maximizeAppWindow() +/** Stub for testing. */ +public class Context { + /** @hide */ + public PackageManager getPackageManager() { + return null; } } diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/DragAppWindowMultiWindowTest.kt b/tools/systemfeatures/tests/PackageManager.java index ed1d4887e818..645d500bc762 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/DragAppWindowMultiWindowTest.kt +++ b/tools/systemfeatures/tests/PackageManager.java @@ -14,21 +14,17 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.service.desktopmode.functional +package android.content.pm; -import android.platform.test.annotations.Postsubmit -import com.android.wm.shell.flicker.service.desktopmode.scenarios.DragAppWindowMultiWindow -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.BlockJUnit4ClassRunner +/** Stub for testing */ +public class PackageManager { + public static final String FEATURE_AUTO = "automotive"; + public static final String FEATURE_VULKAN = "vulkan"; + public static final String FEATURE_WATCH = "watch"; + public static final String FEATURE_WIFI = "wifi"; -/** Functional test for [DragAppWindowMultiWindow]. */ -@RunWith(BlockJUnit4ClassRunner::class) -@Postsubmit -open class DragAppWindowMultiWindowTest : DragAppWindowMultiWindow() -{ - @Test - override fun dragAppWindow() { - super.dragAppWindow() + /** @hide */ + public boolean hasSystemFeature(String featureName, int version) { + return false; } } diff --git a/tools/systemfeatures/tests/SystemFeaturesGeneratorTest.java b/tools/systemfeatures/tests/SystemFeaturesGeneratorTest.java new file mode 100644 index 000000000000..547d2cbd26f9 --- /dev/null +++ b/tools/systemfeatures/tests/SystemFeaturesGeneratorTest.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemfeatures; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.pm.PackageManager; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@RunWith(JUnit4.class) +public class SystemFeaturesGeneratorTest { + + @Rule public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock private Context mContext; + @Mock private PackageManager mPackageManager; + + @Before + public void setUp() { + when(mContext.getPackageManager()).thenReturn(mPackageManager); + } + + @Test + public void testReadonlyDisabledNoDefinedFeatures() { + // Always report null for conditional queries if readonly codegen is disabled. + assertThat(RwNoFeatures.maybeHasFeature(PackageManager.FEATURE_WATCH, 0)).isNull(); + assertThat(RwNoFeatures.maybeHasFeature(PackageManager.FEATURE_WIFI, 0)).isNull(); + assertThat(RwNoFeatures.maybeHasFeature(PackageManager.FEATURE_VULKAN, 0)).isNull(); + assertThat(RwNoFeatures.maybeHasFeature(PackageManager.FEATURE_AUTO, 0)).isNull(); + assertThat(RwNoFeatures.maybeHasFeature("com.arbitrary.feature", 0)).isNull(); + } + + @Test + public void testReadonlyNoDefinedFeatures() { + // If no features are explicitly declared as readonly available, always report + // null for conditional queries. + assertThat(RoNoFeatures.maybeHasFeature(PackageManager.FEATURE_WATCH, 0)).isNull(); + assertThat(RoNoFeatures.maybeHasFeature(PackageManager.FEATURE_WIFI, 0)).isNull(); + assertThat(RoNoFeatures.maybeHasFeature(PackageManager.FEATURE_VULKAN, 0)).isNull(); + assertThat(RoNoFeatures.maybeHasFeature(PackageManager.FEATURE_AUTO, 0)).isNull(); + assertThat(RoNoFeatures.maybeHasFeature("com.arbitrary.feature", 0)).isNull(); + } + + @Test + public void testReadonlyDisabledWithDefinedFeatures() { + // Always fall back to the PackageManager for defined, explicit features queries. + when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH, 0)).thenReturn(true); + assertThat(RwFeatures.hasFeatureWatch(mContext)).isTrue(); + + when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH, 0)).thenReturn(false); + assertThat(RwFeatures.hasFeatureWatch(mContext)).isFalse(); + + when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI, 0)).thenReturn(true); + assertThat(RwFeatures.hasFeatureWifi(mContext)).isTrue(); + + when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_VULKAN, 0)).thenReturn(false); + assertThat(RwFeatures.hasFeatureVulkan(mContext)).isFalse(); + + when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTO, 0)).thenReturn(false); + assertThat(RwFeatures.hasFeatureAuto(mContext)).isFalse(); + + // For defined and undefined features, conditional queries should report null (unknown). + assertThat(RwFeatures.maybeHasFeature(PackageManager.FEATURE_WATCH, 0)).isNull(); + assertThat(RwFeatures.maybeHasFeature(PackageManager.FEATURE_WIFI, 0)).isNull(); + assertThat(RwFeatures.maybeHasFeature(PackageManager.FEATURE_VULKAN, 0)).isNull(); + assertThat(RwFeatures.maybeHasFeature(PackageManager.FEATURE_AUTO, 0)).isNull(); + assertThat(RwFeatures.maybeHasFeature("com.arbitrary.feature", 0)).isNull(); + } + + @Test + public void testReadonlyWithDefinedFeatures() { + // Always use the build-time feature version for defined, explicit feature queries, never + // falling back to the runtime query. + assertThat(RoFeatures.hasFeatureWatch(mContext)).isTrue(); + assertThat(RoFeatures.hasFeatureWifi(mContext)).isTrue(); + assertThat(RoFeatures.hasFeatureVulkan(mContext)).isFalse(); + assertThat(RoFeatures.hasFeatureAuto(mContext)).isFalse(); + verify(mPackageManager, never()).hasSystemFeature(anyString(), anyInt()); + + // For defined feature types, conditional queries should reflect the build-time versions. + // VERSION=1 + assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_WATCH, -1)).isTrue(); + assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_WATCH, 0)).isTrue(); + assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_WATCH, 100)).isFalse(); + + // VERSION=0 + assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_WIFI, -1)).isTrue(); + assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_WIFI, 0)).isTrue(); + assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_WIFI, 100)).isFalse(); + + // VERSION=-1 + assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_VULKAN, -1)).isTrue(); + assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_VULKAN, 0)).isFalse(); + assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_VULKAN, 100)).isFalse(); + + // DISABLED + assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_AUTO, -1)).isFalse(); + assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_AUTO, 0)).isFalse(); + assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_AUTO, 100)).isFalse(); + + // For undefined types, conditional queries should report null (unknown). + assertThat(RoFeatures.maybeHasFeature("com.arbitrary.feature", -1)).isNull(); + assertThat(RoFeatures.maybeHasFeature("com.arbitrary.feature", 0)).isNull(); + assertThat(RoFeatures.maybeHasFeature("com.arbitrary.feature", 100)).isNull(); + } +} |