diff options
238 files changed, 3219 insertions, 1322 deletions
diff --git a/DREAM_MANAGER_OWNERS b/DREAM_MANAGER_OWNERS new file mode 100644 index 000000000000..48bde6024cba --- /dev/null +++ b/DREAM_MANAGER_OWNERS @@ -0,0 +1 @@ +brycelee@google.com diff --git a/Ravenwood.bp b/Ravenwood.bp index 74b34fbcf2a1..412f2b746887 100644 --- a/Ravenwood.bp +++ b/Ravenwood.bp @@ -104,6 +104,18 @@ genrule { ], } +genrule { + name: "framework-minus-apex.ravenwood.keep_all", + defaults: ["ravenwood-internal-only-visibility-genrule"], + cmd: "cp $(in) $(out)", + srcs: [ + ":framework-minus-apex.ravenwood-base{hoststubgen_keep_all.txt}", + ], + out: [ + "hoststubgen_framework-minus-apex_keep_all.txt", + ], +} + java_library { name: "services.core-for-hoststubgen", installable: false, // host only jar. @@ -189,6 +201,18 @@ genrule { ], } +genrule { + name: "services.core.ravenwood.keep_all", + defaults: ["ravenwood-internal-only-visibility-genrule"], + cmd: "cp $(in) $(out)", + srcs: [ + ":services.core.ravenwood-base{hoststubgen_keep_all.txt}", + ], + out: [ + "hoststubgen_services.core_keep_all.txt", + ], +} + java_library { name: "services.core.ravenwood-jarjar", installable: false, diff --git a/core/java/android/app/AppCompatTaskInfo.java b/core/java/android/app/AppCompatTaskInfo.java index 7724c2369954..92543b1c7646 100644 --- a/core/java/android/app/AppCompatTaskInfo.java +++ b/core/java/android/app/AppCompatTaskInfo.java @@ -32,6 +32,11 @@ public class AppCompatTaskInfo implements Parcelable { public boolean topActivityEligibleForLetterboxEducation; /** + * Whether the letterbox education is enabled + */ + public boolean isLetterboxEducationEnabled; + + /** * Whether the direct top activity is in size compat mode on foreground. */ public boolean topActivityInSizeCompat; @@ -178,6 +183,7 @@ public class AppCompatTaskInfo implements Parcelable { == that.topActivityEligibleForUserAspectRatioButton && topActivityEligibleForLetterboxEducation == that.topActivityEligibleForLetterboxEducation + && isLetterboxEducationEnabled == that.isLetterboxEducationEnabled && topActivityLetterboxVerticalPosition == that.topActivityLetterboxVerticalPosition && topActivityLetterboxHorizontalPosition == that.topActivityLetterboxHorizontalPosition @@ -192,6 +198,7 @@ public class AppCompatTaskInfo implements Parcelable { * Reads the AppCompatTaskInfo from a parcel. */ void readFromParcel(Parcel source) { + isLetterboxEducationEnabled = source.readBoolean(); topActivityInSizeCompat = source.readBoolean(); topActivityEligibleForLetterboxEducation = source.readBoolean(); isLetterboxDoubleTapEnabled = source.readBoolean(); @@ -212,6 +219,7 @@ public class AppCompatTaskInfo implements Parcelable { */ @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeBoolean(isLetterboxEducationEnabled); dest.writeBoolean(topActivityInSizeCompat); dest.writeBoolean(topActivityEligibleForLetterboxEducation); dest.writeBoolean(isLetterboxDoubleTapEnabled); @@ -232,6 +240,7 @@ public class AppCompatTaskInfo implements Parcelable { return "AppCompatTaskInfo { topActivityInSizeCompat=" + topActivityInSizeCompat + " topActivityEligibleForLetterboxEducation= " + topActivityEligibleForLetterboxEducation + + "isLetterboxEducationEnabled= " + isLetterboxEducationEnabled + " isLetterboxDoubleTapEnabled= " + isLetterboxDoubleTapEnabled + " topActivityEligibleForUserAspectRatioButton= " + topActivityEligibleForUserAspectRatioButton diff --git a/core/java/android/app/DreamManager.java b/core/java/android/app/DreamManager.java index ef6982e4a13c..4ac40a1f77b2 100644 --- a/core/java/android/app/DreamManager.java +++ b/core/java/android/app/DreamManager.java @@ -18,6 +18,7 @@ package android.app; import static android.Manifest.permission.WRITE_SECURE_SETTINGS; +import android.annotation.FlaggedApi; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemService; @@ -30,6 +31,7 @@ import android.os.ServiceManager; import android.os.UserHandle; import android.provider.Settings; import android.service.dreams.DreamService; +import android.service.dreams.Flags; import android.service.dreams.IDreamManager; /** @@ -217,4 +219,19 @@ public class DreamManager { } return false; } + + /** + * Sets whether the dream is obscured by something. + * + * @hide + */ + @FlaggedApi(Flags.FLAG_DREAM_HANDLES_BEING_OBSCURED) + @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE) + public void setDreamIsObscured(boolean isObscured) { + try { + mService.setDreamIsObscured(isObscured); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS index 97c2e43a1db6..2e38c06a7479 100644 --- a/core/java/android/app/OWNERS +++ b/core/java/android/app/OWNERS @@ -60,6 +60,9 @@ per-file ReceiverInfo* = file:/BROADCASTS_OWNERS # ComponentCaller per-file ComponentCaller.java = file:COMPONENT_CALLER_OWNERS +# DreamManager +per-file DreamManager.java = file:/DREAM_MANAGER_OWNERS + # GrammaticalInflectionManager per-file *GrammaticalInflection* = file:/services/core/java/com/android/server/grammaticalinflection/OWNERS per-file grammatical_inflection_manager.aconfig = file:/services/core/java/com/android/server/grammaticalinflection/OWNERS diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java index 57b5c13a659d..3213b40b3437 100644 --- a/core/java/android/appwidget/AppWidgetManager.java +++ b/core/java/android/appwidget/AppWidgetManager.java @@ -1041,6 +1041,7 @@ public class AppWidgetManager { */ public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId) { if (mService == null) { + Log.e(TAG, "Service wasn't initialized, appWidgetId=" + appWidgetId); return null; } try { @@ -1048,6 +1049,9 @@ public class AppWidgetManager { if (info != null) { // Converting complex to dp. info.updateDimensions(mDisplayMetrics); + } else { + Log.e(TAG, "App widget provider info is null. PackageName=" + mPackageName + + " appWidgetId-" + appWidgetId); } return info; } catch (RemoteException e) { diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java index 5f6bdbf193b9..38ab590b3c46 100644 --- a/core/java/android/service/dreams/DreamService.java +++ b/core/java/android/service/dreams/DreamService.java @@ -18,7 +18,7 @@ package android.service.dreams; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.service.dreams.Flags.dreamHandlesConfirmKeys; -import static android.service.dreams.Flags.dreamTracksFocus; +import static android.service.dreams.Flags.dreamHandlesBeingObscured; import android.annotation.FlaggedApi; import android.annotation.IdRes; @@ -571,15 +571,6 @@ public class DreamService extends Service implements Window.Callback { /** {@inheritDoc} */ @Override public void onWindowFocusChanged(boolean hasFocus) { - if (!dreamTracksFocus()) { - return; - } - - try { - mDreamManager.onDreamFocusChanged(hasFocus); - } catch (RemoteException ex) { - // system server died - } } /** {@inheritDoc} */ @@ -1737,7 +1728,7 @@ public class DreamService extends Service implements Window.Callback { @Override public void comeToFront() { - if (!dreamTracksFocus()) { + if (!dreamHandlesBeingObscured()) { return; } diff --git a/core/java/android/service/dreams/IDreamManager.aidl b/core/java/android/service/dreams/IDreamManager.aidl index 85f0368a7b5c..cf98bfe05faf 100644 --- a/core/java/android/service/dreams/IDreamManager.aidl +++ b/core/java/android/service/dreams/IDreamManager.aidl @@ -48,5 +48,6 @@ interface IDreamManager { void setSystemDreamComponent(in ComponentName componentName); void registerDreamOverlayService(in ComponentName componentName); void startDreamActivity(in Intent intent); - void onDreamFocusChanged(in boolean hasFocus); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE)") + oneway void setDreamIsObscured(in boolean isObscured); } diff --git a/core/java/android/service/dreams/flags.aconfig b/core/java/android/service/dreams/flags.aconfig index a42eaff68917..54d950c18af8 100644 --- a/core/java/android/service/dreams/flags.aconfig +++ b/core/java/android/service/dreams/flags.aconfig @@ -39,8 +39,11 @@ flag { } flag { - name: "dream_tracks_focus" + name: "dream_handles_being_obscured" namespace: "communal" - description: "This flag enables the ability for dreams to track whether or not they have focus" - bug: "331798001" + description: "This flag enables the ability for dreams to handle being obscured" + bug: "337302237" + metadata { + purpose: PURPOSE_BUGFIX + } } diff --git a/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java b/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java index 29a6db6a12a0..8237b20260ea 100644 --- a/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java +++ b/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java @@ -105,6 +105,21 @@ public abstract class OnDeviceSandboxedInferenceService extends Service { public static final String SERVICE_INTERFACE = "android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService"; + // TODO(339594686): make API + /** + * @hide + */ + public static final String REGISTER_MODEL_UPDATE_CALLBACK_BUNDLE_KEY = + "register_model_update_callback"; + /** + * @hide + */ + public static final String MODEL_LOADED_BUNDLE_KEY = "model_loaded"; + /** + * @hide + */ + public static final String MODEL_UNLOADED_BUNDLE_KEY = "model_unloaded"; + private IRemoteStorageService mRemoteStorageService; /** diff --git a/core/jni/platform/host/HostRuntime.cpp b/core/jni/platform/host/HostRuntime.cpp index bf2fddab3d41..acef609448ad 100644 --- a/core/jni/platform/host/HostRuntime.cpp +++ b/core/jni/platform/host/HostRuntime.cpp @@ -330,7 +330,7 @@ static void init_keyboard(JNIEnv* env, const vector<string>& keyboardPaths) { InputDeviceInfo info = InputDeviceInfo(); info.initialize(keyboardId, 0, 0, InputDeviceIdentifier(), "keyboard " + std::to_string(keyboardId), true, false, - ui::ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); info.setKeyboardType(AINPUT_KEYBOARD_TYPE_ALPHABETIC); info.setKeyCharacterMap(*charMap); diff --git a/core/res/res/drawable/ic_signal_cellular_1_4_bar.xml b/core/res/res/drawable/ic_signal_cellular_1_4_bar.xml index 7c45c20a758b..c692967d36b3 100644 --- a/core/res/res/drawable/ic_signal_cellular_1_4_bar.xml +++ b/core/res/res/drawable/ic_signal_cellular_1_4_bar.xml @@ -22,11 +22,11 @@ <path android:fillColor="@android:color/white" android:pathData="M20,7v13H7L20,7 M22,2L2,22h20V2L22,2z" /> - <clip-path android:name="triangle" android:pathData="M20,7v13H7L20,7z"> + <clip-path android:name="triangle" android:pathData="M21,5 V21 H5 z"> <!-- 1 bar. move to higher ground. --> <path android:name="ic_signal_cellular_1_4_bar" android:fillColor="@android:color/white" - android:pathData="M6,0 H11 V20 H6 z" /> + android:pathData="M0,0 H11 V24 H0 z" /> </clip-path> </vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_signal_cellular_1_5_bar.xml b/core/res/res/drawable/ic_signal_cellular_1_5_bar.xml index 02b646d310e5..b01c26972ac8 100644 --- a/core/res/res/drawable/ic_signal_cellular_1_5_bar.xml +++ b/core/res/res/drawable/ic_signal_cellular_1_5_bar.xml @@ -22,11 +22,11 @@ <path android:fillColor="@android:color/white" android:pathData="M20,7V20H7L20,7m2-5L2,22H22V2Z" /> - <clip-path android:name="triangle" android:pathData="M20,7v13H7L20,7z"> + <clip-path android:name="triangle" android:pathData="M21,5 V21 H5 z"> <!-- 1 bar. might have to call you back. --> <path android:name="ic_signal_cellular_1_5_bar" android:fillColor="@android:color/white" - android:pathData="M6,0 H12 V20 H6 z" /> + android:pathData="M0,0 H12 V24 H0 z" /> </clip-path> </vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_signal_cellular_2_4_bar.xml b/core/res/res/drawable/ic_signal_cellular_2_4_bar.xml index 514d1690abcf..982623d41060 100644 --- a/core/res/res/drawable/ic_signal_cellular_2_4_bar.xml +++ b/core/res/res/drawable/ic_signal_cellular_2_4_bar.xml @@ -22,11 +22,11 @@ <path android:fillColor="@android:color/white" android:pathData="M20,7v13H7L20,7 M22,2L2,22h20V2L22,2z" /> - <clip-path android:name="triangle" android:pathData="M20,7v13H7L20,7z"> + <clip-path android:name="triangle" android:pathData="M21,5 V21 H5 z"> <!-- 2 bars. 2 out of 4 ain't bad. --> <path android:name="ic_signal_cellular_2_4_bar" android:fillColor="@android:color/white" - android:pathData="M6,0 H14 V20 H6 z" /> + android:pathData="M0,0 H14 V24 H0 z" /> </clip-path> </vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_signal_cellular_2_5_bar.xml b/core/res/res/drawable/ic_signal_cellular_2_5_bar.xml index a97f771a6632..75daadd213dc 100644 --- a/core/res/res/drawable/ic_signal_cellular_2_5_bar.xml +++ b/core/res/res/drawable/ic_signal_cellular_2_5_bar.xml @@ -23,11 +23,11 @@ <path android:fillColor="@android:color/white" android:pathData="M20,7V20H7L20,7m2-5L2,22H22V2Z" /> - <clip-path android:name="triangle" android:pathData="M20,7v13H7L20,7z"> + <clip-path android:name="triangle" android:pathData="M21,5 V21 H5 z"> <!-- 2 bars. hanging in there. --> <path android:name="ic_signal_cellular_2_5_bar" android:fillColor="@android:color/white" - android:pathData="M6,0 H14 V20 H6 z" /> + android:pathData="M0,0 H14 V24 H0 z" /> </clip-path> </vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_signal_cellular_3_4_bar.xml b/core/res/res/drawable/ic_signal_cellular_3_4_bar.xml index 1bacf4ad678f..4e4bea397d26 100644 --- a/core/res/res/drawable/ic_signal_cellular_3_4_bar.xml +++ b/core/res/res/drawable/ic_signal_cellular_3_4_bar.xml @@ -22,11 +22,11 @@ <path android:fillColor="@android:color/white" android:pathData="M20,7v13H7L20,7 M22,2L2,22h20V2L22,2z" /> - <clip-path android:name="triangle" android:pathData="M20,7v13H7L20,7z"> + <clip-path android:name="triangle" android:pathData="M21,5 V21 H5 z"> <!-- 3 bars. quite nice. --> <path android:name="ic_signal_cellular_3_4_bar" android:fillColor="@android:color/white" - android:pathData="M6,0 H17 V20 H6 z" /> + android:pathData="M0,0 H17 V24 H0 z" /> </clip-path> </vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_signal_cellular_3_5_bar.xml b/core/res/res/drawable/ic_signal_cellular_3_5_bar.xml index 2789d3e9305c..9a98c2982574 100644 --- a/core/res/res/drawable/ic_signal_cellular_3_5_bar.xml +++ b/core/res/res/drawable/ic_signal_cellular_3_5_bar.xml @@ -22,11 +22,11 @@ <path android:fillColor="@android:color/white" android:pathData="M20,7V20H7L20,7m2-5L2,22H22V2Z" /> - <clip-path android:name="triangle" android:pathData="M20,7v13H7L20,7z"> + <clip-path android:name="triangle" android:pathData="M21,5 V21 H5 z"> <!-- 3 bars. not great, not terrible. --> <path android:name="ic_signal_cellular_3_5_bar" android:fillColor="@android:color/white" - android:pathData="M6,0 H16 V20 H6 z" /> + android:pathData="M0,0 H16 V24 H0 z" /> </clip-path> </vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_signal_cellular_4_5_bar.xml b/core/res/res/drawable/ic_signal_cellular_4_5_bar.xml index 8286dbb5576f..2a37d011c650 100644 --- a/core/res/res/drawable/ic_signal_cellular_4_5_bar.xml +++ b/core/res/res/drawable/ic_signal_cellular_4_5_bar.xml @@ -22,11 +22,11 @@ <path android:fillColor="@android:color/white" android:pathData="M20,7V20H7L20,7m2-5L2,22H22V2Z" /> - <clip-path android:name="triangle" android:pathData="M20,7v13H7L20,7z"> + <clip-path android:name="triangle" android:pathData="M21,5 V21 H5 z"> <!-- 4 bars. extremely respectable. --> <path android:name="ic_signal_cellular_4_5_bar" android:fillColor="@android:color/white" - android:pathData="M6,0 H18 V20 H6 z" /> + android:pathData="M0,0 H18 V24 H0 z" /> </clip-path> </vector>
\ No newline at end of file diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 37771a2a3a24..5bd20332f381 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -4717,6 +4717,13 @@ <!-- The component name for the default system on-device sandboxed inference service. --> <string name="config_defaultOnDeviceSandboxedInferenceService" translatable="false"></string> + <!-- The broadcast intent name for notifying when the on-device model is loading --> + <string name="config_onDeviceIntelligenceModelLoadedBroadcastKey" translatable="false"></string> + + <!-- The broadcast intent name for notifying when the on-device model has been unloaded --> + <string name="config_onDeviceIntelligenceModelUnloadedBroadcastKey" translatable="false"></string> + + <!-- Component name that accepts ACTION_SEND intents for requesting ambient context consent for wearable sensing. --> <string translatable="false" name="config_defaultWearableSensingConsentComponent"></string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index e5768e4a1def..ae79a4c68f9e 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3943,6 +3943,8 @@ <java-symbol type="string" name="config_defaultWearableSensingService" /> <java-symbol type="string" name="config_defaultOnDeviceIntelligenceService" /> <java-symbol type="string" name="config_defaultOnDeviceSandboxedInferenceService" /> + <java-symbol type="string" name="config_onDeviceIntelligenceModelLoadedBroadcastKey" /> + <java-symbol type="string" name="config_onDeviceIntelligenceModelUnloadedBroadcastKey" /> <java-symbol type="string" name="config_retailDemoPackage" /> <java-symbol type="string" name="config_retailDemoPackageSignature" /> diff --git a/core/tests/coretests/src/android/net/OWNERS b/core/tests/coretests/src/android/net/OWNERS index a779c00814cb..beb77dc8f4fd 100644 --- a/core/tests/coretests/src/android/net/OWNERS +++ b/core/tests/coretests/src/android/net/OWNERS @@ -1,4 +1,5 @@ include /services/core/java/com/android/server/net/OWNERS -per-file SSL*,Uri*,Url* = prb@google.com,oth@google.com,narayan@google.com,ngeoffray@google.com +per-file SSL*,Url* = prb@google.com,oth@google.com,narayan@google.com,ngeoffray@google.com per-file SntpClient* = file:/services/core/java/com/android/server/timedetector/OWNERS +per-file Uri* = varunshah@google.com diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java index 5c292f173e5b..bfac24b81d2f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java @@ -188,6 +188,11 @@ public class CompatUIController implements OnDisplaysChangedListener, */ private boolean mHasShownUserAspectRatioSettingsButton = false; + /** + * This is true when the rechability education is displayed for the first time. + */ + private boolean mIsFirstReachabilityEducationRunning; + public CompatUIController(@NonNull Context context, @NonNull ShellInit shellInit, @NonNull ShellController shellController, @@ -252,9 +257,35 @@ public class CompatUIController implements OnDisplaysChangedListener, removeLayouts(taskInfo.taskId); return; } - + // We're showing the first reachability education so we ignore incoming TaskInfo + // until the education flow has completed or we double tap. + if (mIsFirstReachabilityEducationRunning) { + return; + } + if (taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed) { + if (taskInfo.appCompatTaskInfo.isLetterboxEducationEnabled) { + createOrUpdateLetterboxEduLayout(taskInfo, taskListener); + } else if (!taskInfo.appCompatTaskInfo.isFromLetterboxDoubleTap) { + // In this case the app is letterboxed and the letterbox education + // is disabled. In this case we need to understand if it's the first + // time we show the reachability education. When this is happening + // we need to ignore all the incoming TaskInfo until the education + // completes. If we come from a double tap we follow the normal flow. + final boolean topActivityPillarboxed = + taskInfo.appCompatTaskInfo.isTopActivityPillarboxed(); + final boolean isFirstTimeHorizontalReachabilityEdu = topActivityPillarboxed + && !mCompatUIConfiguration.hasSeenHorizontalReachabilityEducation(taskInfo); + final boolean isFirstTimeVerticalReachabilityEdu = !topActivityPillarboxed + && !mCompatUIConfiguration.hasSeenVerticalReachabilityEducation(taskInfo); + if (isFirstTimeHorizontalReachabilityEdu || isFirstTimeVerticalReachabilityEdu) { + mIsFirstReachabilityEducationRunning = true; + mCompatUIConfiguration.setSeenLetterboxEducation(taskInfo.userId); + createOrUpdateReachabilityEduLayout(taskInfo, taskListener); + return; + } + } + } createOrUpdateCompatLayout(taskInfo, taskListener); - createOrUpdateLetterboxEduLayout(taskInfo, taskListener); createOrUpdateRestartDialogLayout(taskInfo, taskListener); if (mCompatUIConfiguration.getHasSeenLetterboxEducation(taskInfo.userId)) { createOrUpdateReachabilityEduLayout(taskInfo, taskListener); @@ -589,6 +620,7 @@ public class CompatUIController implements OnDisplaysChangedListener, private void onInitialReachabilityEduDismissed(@NonNull TaskInfo taskInfo, @NonNull ShellTaskOrganizer.TaskListener taskListener) { // We need to update the UI otherwise it will not be shown until the user relaunches the app + mIsFirstReachabilityEducationRunning = false; createOrUpdateUserAspectRatioSettingsLayout(taskInfo, taskListener); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java index b41454d932a5..5af4c3b0a716 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java @@ -41,15 +41,6 @@ public class DesktopModeStatus { public static final boolean IS_DISPLAY_CHANGE_ENABLED = SystemProperties.getBoolean( "persist.wm.debug.desktop_change_display", false); - - /** - * Flag to indicate that desktop stashing is enabled. - * When enabled, swiping home from desktop stashes the open apps. Next app that launches, - * will be added to the desktop. - */ - private static final boolean IS_STASHING_ENABLED = SystemProperties.getBoolean( - "persist.wm.debug.desktop_stashing", false); - /** * Flag to indicate whether to apply shadows to windows in desktop mode. */ @@ -109,14 +100,6 @@ public class DesktopModeStatus { } /** - * Return {@code true} if desktop task stashing is enabled when going home. - * Allows users to use home screen to add tasks to desktop. - */ - public static boolean isStashingEnabled() { - return IS_STASHING_ENABLED; - } - - /** * Return whether to use window shadows. * * @param isFocusedWindow whether the window to apply shadows to is focused 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 2d508b2e6e3d..6bbc8fec2894 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 @@ -48,7 +48,6 @@ class DesktopModeTaskRepository { val activeTasks: ArraySet<Int> = ArraySet(), val visibleTasks: ArraySet<Int> = ArraySet(), val minimizedTasks: ArraySet<Int> = ArraySet(), - var stashed: Boolean = false ) // Token of the current wallpaper activity, used to remove it when the last task is removed @@ -95,10 +94,8 @@ class DesktopModeTaskRepository { visibleTasksListeners[visibleTasksListener] = executor displayData.keyIterator().forEach { displayId -> val visibleTasksCount = getVisibleTaskCount(displayId) - val stashed = isStashed(displayId) executor.execute { visibleTasksListener.onTasksVisibilityChanged(displayId, visibleTasksCount) - visibleTasksListener.onStashedChanged(displayId, stashed) } } } @@ -400,26 +397,6 @@ class DesktopModeTaskRepository { } /** - * Update stashed status on display with id [displayId] - */ - fun setStashed(displayId: Int, stashed: Boolean) { - val data = displayData.getOrCreate(displayId) - val oldValue = data.stashed - data.stashed = stashed - if (oldValue != stashed) { - KtProtoLog.d( - WM_SHELL_DESKTOP_MODE, - "DesktopTaskRepo: mark stashed=%b displayId=%d", - stashed, - displayId - ) - visibleTasksListeners.forEach { (listener, executor) -> - executor.execute { listener.onStashedChanged(displayId, stashed) } - } - } - } - - /** * Removes and returns the bounds saved before maximizing the given task. */ fun removeBoundsBeforeMaximize(taskId: Int): Rect? { @@ -433,13 +410,6 @@ class DesktopModeTaskRepository { boundsBeforeMaximizeByTaskId.set(taskId, Rect(bounds)) } - /** - * Check if display with id [displayId] has desktop tasks stashed - */ - fun isStashed(displayId: Int): Boolean { - return displayData[displayId]?.stashed ?: false - } - internal fun dump(pw: PrintWriter, prefix: String) { val innerPrefix = "$prefix " pw.println("${prefix}DesktopModeTaskRepository") @@ -455,7 +425,6 @@ class DesktopModeTaskRepository { pw.println("${prefix}Display $displayId:") pw.println("${innerPrefix}activeTasks=${data.activeTasks.toDumpString()}") pw.println("${innerPrefix}visibleTasks=${data.visibleTasks.toDumpString()}") - pw.println("${innerPrefix}stashed=${data.stashed}") } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index b0d59231500b..b2bdbfefb9aa 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -240,34 +240,6 @@ class DesktopTasksController( } } - /** - * Stash desktop tasks on display with id [displayId]. - * - * When desktop tasks are stashed, launcher home screen icons are fully visible. New apps - * launched in this state will be added to the desktop. Existing desktop tasks will be brought - * back to front during the launch. - */ - fun stashDesktopApps(displayId: Int) { - if (DesktopModeStatus.isStashingEnabled()) { - KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: stashDesktopApps") - desktopModeTaskRepository.setStashed(displayId, true) - } - } - - /** - * Clear the stashed state for the given display - */ - fun hideStashedDesktopApps(displayId: Int) { - if (DesktopModeStatus.isStashingEnabled()) { - KtProtoLog.v( - WM_SHELL_DESKTOP_MODE, - "DesktopTasksController: hideStashedApps displayId=%d", - displayId - ) - desktopModeTaskRepository.setStashed(displayId, false) - } - } - /** Get number of tasks that are marked as visible */ fun getVisibleTaskCount(displayId: Int): Int { return desktopModeTaskRepository.getVisibleTaskCount(displayId) @@ -871,8 +843,6 @@ class DesktopTasksController( val result = triggerTask?.let { task -> when { request.type == TRANSIT_TO_BACK -> handleBackNavigation(task) - // If display has tasks stashed, handle as stashed launch - task.isStashed -> handleStashedTaskLaunch(task, transition) // Check if the task has a top transparent activity shouldLaunchAsModal(task) -> handleTransparentTaskLaunch(task) // Check if fullscreen task should be updated @@ -911,12 +881,8 @@ class DesktopTasksController( .forEach { finishTransaction.setCornerRadius(it.leash, cornerRadius) } } - private val TaskInfo.isStashed: Boolean - get() = desktopModeTaskRepository.isStashed(displayId) - - private fun shouldLaunchAsModal(task: TaskInfo): Boolean { - return Flags.enableDesktopWindowingModalsPolicy() && isSingleTopActivityTranslucent(task) - } + private fun shouldLaunchAsModal(task: TaskInfo) = + Flags.enableDesktopWindowingModalsPolicy() && isSingleTopActivityTranslucent(task) private fun shouldRemoveWallpaper(request: TransitionRequestInfo): Boolean { return Flags.enableDesktopWindowingWallpaperActivity() && @@ -976,24 +942,6 @@ class DesktopTasksController( return null } - private fun handleStashedTaskLaunch( - task: RunningTaskInfo, - transition: IBinder - ): WindowContainerTransaction { - KtProtoLog.d( - WM_SHELL_DESKTOP_MODE, - "DesktopTasksController: launch apps with stashed on transition taskId=%d", - task.taskId - ) - val wct = WindowContainerTransaction() - val taskToMinimize = - bringDesktopAppsToFrontBeforeShowingNewTask(task.displayId, wct, task.taskId) - addMoveToDesktopChanges(wct, task) - desktopModeTaskRepository.setStashed(task.displayId, false) - addPendingMinimizeTransition(transition, taskToMinimize) - return wct - } - // Always launch transparent tasks in fullscreen. private fun handleTransparentTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction? { // Already fullscreen, no-op. @@ -1467,25 +1415,25 @@ class DesktopTasksController( ) { c -> c.showDesktopApps(displayId, remoteTransition) } } - override fun stashDesktopApps(displayId: Int) { + override fun showDesktopApp(taskId: Int) { ExecutorUtils.executeRemoteCallWithTaskPermission( controller, - "stashDesktopApps" - ) { c -> c.stashDesktopApps(displayId) } + "showDesktopApp" + ) { c -> c.moveTaskToFront(taskId) } } - override fun hideStashedDesktopApps(displayId: Int) { - ExecutorUtils.executeRemoteCallWithTaskPermission( - controller, - "hideStashedDesktopApps" - ) { c -> c.hideStashedDesktopApps(displayId) } + override fun stashDesktopApps(displayId: Int) { + KtProtoLog.w( + WM_SHELL_DESKTOP_MODE, + "IDesktopModeImpl: stashDesktopApps is deprecated" + ) } - override fun showDesktopApp(taskId: Int) { - ExecutorUtils.executeRemoteCallWithTaskPermission( - controller, - "showDesktopApp" - ) { c -> c.moveTaskToFront(taskId) } + override fun hideStashedDesktopApps(displayId: Int) { + KtProtoLog.w( + WM_SHELL_DESKTOP_MODE, + "IDesktopModeImpl: hideStashedDesktopApps is deprecated" + ) } override fun getVisibleTaskCount(displayId: Int): Int { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl index fa4352241193..c36f8deb6ecc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl @@ -28,10 +28,10 @@ interface IDesktopMode { /** Show apps on the desktop on the given display */ void showDesktopApps(int displayId, in RemoteTransition remoteTransition); - /** Stash apps on the desktop to allow launching another app from home screen */ + /** @deprecated use {@link #showDesktopApps} instead. */ void stashDesktopApps(int displayId); - /** Hide apps that may be stashed */ + /** @deprecated this is no longer supported. */ void hideStashedDesktopApps(int displayId); /** Bring task with the given id to front */ diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt index 984abf8cf8b4..bc486c277aa5 100644 --- a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt @@ -32,7 +32,7 @@ import org.junit.runners.Parameterized /** * Test launching a new activity from bubble. * - * To run this test: `atest WMShellFlickerTestsBubbles:ChangeActiveActivityFromBubbleTest` + * To run this test: `atest WMShellFlickerTests:MultiBubblesScreen` * * Actions: * ``` diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt index 886b70c5e464..2a9b1078afe3 100644 --- a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt @@ -35,7 +35,7 @@ import org.junit.runners.Parameterized /** * Test launching a new activity from bubble. * - * To run this test: `atest WMShellFlickerTestsBubbles:DragToDismissBubbleScreenTest` + * To run this test: `atest WMShellFlickerTests:DismissBubbleScreen` * * Actions: * ``` 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..9ef49c1c9e7e 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 @@ -38,7 +38,7 @@ import org.junit.runners.Parameterized /** * Test launching a new activity from bubble. * - * To run this test: `atest WMShellFlickerTestsBubbles:OpenActivityFromBubbleOnLocksreenTest` + * To run this test: `atest WMShellFlickerTests:OpenActivityFromBubbleOnLocksreenTest` * * Actions: * ``` diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt index 463fe0e60da3..ef7fbfb79beb 100644 --- a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt @@ -29,7 +29,7 @@ import org.junit.runners.Parameterized /** * Test launching a new activity from bubble. * - * To run this test: `atest WMShellFlickerTestsBubbles:OpenActivityFromBubbleTest` + * To run this test: `atest WMShellFlickerTests:ExpandBubbleScreen` * * Actions: * ``` diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt index 8df50567a29c..87224b151b78 100644 --- a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt @@ -29,7 +29,7 @@ import org.junit.runners.Parameterized /** * Test creating a bubble notification * - * To run this test: `atest WMShellFlickerTestsBubbles:SendBubbleNotificationTest` + * To run this test: `atest WMShellFlickerTests:LaunchBubbleScreen` * * Actions: * ``` diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java index afae653f0682..9c008647104a 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java @@ -668,6 +668,18 @@ public class CompatUIControllerTest extends ShellTestCase { Assert.assertTrue(mController.hasShownUserAspectRatioSettingsButton()); } + @Test + public void testLetterboxEduLayout_notCreatedWhenLetterboxEducationIsDisabled() { + TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true, + CAMERA_COMPAT_CONTROL_HIDDEN); + taskInfo.appCompatTaskInfo.isLetterboxEducationEnabled = false; + + mController.onCompatInfoChanged(taskInfo, mMockTaskListener); + + verify(mController, never()).createLetterboxEduWindowManager(any(), eq(taskInfo), + eq(mMockTaskListener)); + } + private static TaskInfo createTaskInfo(int displayId, int taskId, boolean hasSizeCompat, @CameraCompatControlState int cameraCompatControlState) { return createTaskInfo(displayId, taskId, hasSizeCompat, cameraCompatControlState, @@ -694,6 +706,8 @@ public class CompatUIControllerTest extends ShellTestCase { taskInfo.isVisible = isVisible; taskInfo.isFocused = isFocused; taskInfo.isTopActivityTransparent = isTopActivityTransparent; + taskInfo.appCompatTaskInfo.isLetterboxEducationEnabled = true; + taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed = true; return taskInfo; } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt index dca7be12fffc..8f59f30da697 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt @@ -182,18 +182,6 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { } @Test - fun addListener_notifiesStashed() { - repo.setStashed(DEFAULT_DISPLAY, true) - val listener = TestVisibilityListener() - val executor = TestShellExecutor() - repo.addVisibleTasksListener(listener, executor) - executor.flushAll() - - assertThat(listener.stashedOnDefaultDisplay).isTrue() - assertThat(listener.stashedChangesOnDefaultDisplay).isEqualTo(1) - } - - @Test fun addListener_tasksOnDifferentDisplay_doesNotNotify() { repo.updateVisibleFreeformTasks(SECOND_DISPLAY, taskId = 1, visible = true) val listener = TestVisibilityListener() @@ -400,65 +388,6 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { } @Test - fun setStashed_stateIsUpdatedForTheDisplay() { - repo.setStashed(DEFAULT_DISPLAY, true) - assertThat(repo.isStashed(DEFAULT_DISPLAY)).isTrue() - assertThat(repo.isStashed(SECOND_DISPLAY)).isFalse() - - repo.setStashed(DEFAULT_DISPLAY, false) - assertThat(repo.isStashed(DEFAULT_DISPLAY)).isFalse() - } - - @Test - fun setStashed_notifyListener() { - val listener = TestVisibilityListener() - val executor = TestShellExecutor() - repo.addVisibleTasksListener(listener, executor) - repo.setStashed(DEFAULT_DISPLAY, true) - executor.flushAll() - assertThat(listener.stashedOnDefaultDisplay).isTrue() - assertThat(listener.stashedChangesOnDefaultDisplay).isEqualTo(1) - - repo.setStashed(DEFAULT_DISPLAY, false) - executor.flushAll() - assertThat(listener.stashedOnDefaultDisplay).isFalse() - assertThat(listener.stashedChangesOnDefaultDisplay).isEqualTo(2) - } - - @Test - fun setStashed_secondCallDoesNotNotify() { - val listener = TestVisibilityListener() - val executor = TestShellExecutor() - repo.addVisibleTasksListener(listener, executor) - repo.setStashed(DEFAULT_DISPLAY, true) - repo.setStashed(DEFAULT_DISPLAY, true) - executor.flushAll() - assertThat(listener.stashedChangesOnDefaultDisplay).isEqualTo(1) - } - - @Test - fun setStashed_tracksPerDisplay() { - val listener = TestVisibilityListener() - val executor = TestShellExecutor() - repo.addVisibleTasksListener(listener, executor) - - repo.setStashed(DEFAULT_DISPLAY, true) - executor.flushAll() - assertThat(listener.stashedOnDefaultDisplay).isTrue() - assertThat(listener.stashedOnSecondaryDisplay).isFalse() - - repo.setStashed(SECOND_DISPLAY, true) - executor.flushAll() - assertThat(listener.stashedOnDefaultDisplay).isTrue() - assertThat(listener.stashedOnSecondaryDisplay).isTrue() - - repo.setStashed(DEFAULT_DISPLAY, false) - executor.flushAll() - assertThat(listener.stashedOnDefaultDisplay).isFalse() - assertThat(listener.stashedOnSecondaryDisplay).isTrue() - } - - @Test fun removeFreeformTask_removesTaskBoundsBeforeMaximize() { val taskId = 1 repo.saveBoundsBeforeMaximize(taskId, Rect(0, 0, 200, 200)) @@ -598,12 +527,6 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { var visibleChangesOnDefaultDisplay = 0 var visibleChangesOnSecondaryDisplay = 0 - var stashedOnDefaultDisplay = false - var stashedOnSecondaryDisplay = false - - var stashedChangesOnDefaultDisplay = 0 - var stashedChangesOnSecondaryDisplay = 0 - override fun onTasksVisibilityChanged(displayId: Int, visibleTasksCount: Int) { when (displayId) { DEFAULT_DISPLAY -> { @@ -617,20 +540,6 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { else -> fail("Visible task listener received unexpected display id: $displayId") } } - - override fun onStashedChanged(displayId: Int, stashed: Boolean) { - when (displayId) { - DEFAULT_DISPLAY -> { - stashedOnDefaultDisplay = stashed - stashedChangesOnDefaultDisplay++ - } - SECOND_DISPLAY -> { - stashedOnSecondaryDisplay = stashed - stashedChangesOnDefaultDisplay++ - } - else -> fail("Visible task listener received unexpected display id: $displayId") - } - } } companion object { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt index 3f76c4f556f7..7e55628b5641 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -1044,29 +1044,6 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun handleRequest_fullscreenTask_desktopStashed_returnWCTWithAllAppsBroughtToFront() { - assumeTrue(ENABLE_SHELL_TRANSITIONS) - whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true) - - val stashedFreeformTask = setUpFreeformTask(DEFAULT_DISPLAY) - markTaskHidden(stashedFreeformTask) - - val fullscreenTask = createFullscreenTask(DEFAULT_DISPLAY) - - controller.stashDesktopApps(DEFAULT_DISPLAY) - - val result = controller.handleRequest(Binder(), createTransition(fullscreenTask)) - assertThat(result).isNotNull() - result!!.assertReorderSequence(stashedFreeformTask, fullscreenTask) - assertThat(result.changes[fullscreenTask.token.asBinder()]?.windowingMode) - .isEqualTo(WINDOWING_MODE_FREEFORM) - - // Stashed state should be cleared - assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isFalse() - } - - @Test fun handleRequest_freeformTask_freeformVisible_returnNull() { assumeTrue(ENABLE_SHELL_TRANSITIONS) @@ -1133,27 +1110,6 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun handleRequest_freeformTask_desktopStashed_returnWCTWithAllAppsBroughtToFront() { - assumeTrue(ENABLE_SHELL_TRANSITIONS) - whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true) - - val stashedFreeformTask = setUpFreeformTask(DEFAULT_DISPLAY) - markTaskHidden(stashedFreeformTask) - - val freeformTask = createFreeformTask(DEFAULT_DISPLAY) - - controller.stashDesktopApps(DEFAULT_DISPLAY) - - val result = controller.handleRequest(Binder(), createTransition(freeformTask)) - assertThat(result).isNotNull() - result?.assertReorderSequence(stashedFreeformTask, freeformTask) - - // Stashed state should be cleared - assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isFalse() - } - - @Test fun handleRequest_notOpenOrToFrontTransition_returnNull() { assumeTrue(ENABLE_SHELL_TRANSITIONS) @@ -1269,29 +1225,6 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - fun stashDesktopApps_stateUpdates() { - whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true) - - controller.stashDesktopApps(DEFAULT_DISPLAY) - - assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isTrue() - assertThat(desktopModeTaskRepository.isStashed(SECOND_DISPLAY)).isFalse() - } - - @Test - fun hideStashedDesktopApps_stateUpdates() { - whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true) - - desktopModeTaskRepository.setStashed(DEFAULT_DISPLAY, true) - desktopModeTaskRepository.setStashed(SECOND_DISPLAY, true) - controller.hideStashedDesktopApps(DEFAULT_DISPLAY) - - assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isFalse() - // Check that second display is not affected - assertThat(desktopModeTaskRepository.isStashed(SECOND_DISPLAY)).isTrue() - } - - @Test fun desktopTasksVisibilityChange_visible_setLaunchAdjacentDisabled() { val task = setUpFreeformTask() clearInvocations(launchAdjacentController) diff --git a/libs/input/MouseCursorController.cpp b/libs/input/MouseCursorController.cpp index 5cf5a1d00815..f1ee3256dbee 100644 --- a/libs/input/MouseCursorController.cpp +++ b/libs/input/MouseCursorController.cpp @@ -467,10 +467,10 @@ void MouseCursorController::startAnimationLocked() REQUIRES(mLock) { std::function<bool(nsecs_t)> func = std::bind(&MouseCursorController::doAnimations, this, _1); /* - * Using ui::ADISPLAY_ID_NONE for displayId here to avoid removing the callback + * Using ui::LogicalDisplayId::INVALID for displayId here to avoid removing the callback * if a TouchSpotController with the same display is removed. */ - mContext.addAnimationCallback(ui::ADISPLAY_ID_NONE, func); + mContext.addAnimationCallback(ui::LogicalDisplayId::INVALID, func); } } // namespace android diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h index 70e5c2455d81..c6430f7f36ff 100644 --- a/libs/input/PointerController.h +++ b/libs/input/PointerController.h @@ -109,7 +109,7 @@ private: struct Locked { Presentation presentation; - ui::LogicalDisplayId pointerDisplayId = ui::ADISPLAY_ID_NONE; + ui::LogicalDisplayId pointerDisplayId = ui::LogicalDisplayId::INVALID; std::vector<gui::DisplayInfo> mDisplayInfos; std::unordered_map<ui::LogicalDisplayId, TouchSpotController> spotControllers; diff --git a/libs/input/SpriteController.h b/libs/input/SpriteController.h index 070c90c372ff..e147c567ae2d 100644 --- a/libs/input/SpriteController.h +++ b/libs/input/SpriteController.h @@ -174,7 +174,7 @@ private: int32_t layer{0}; float alpha{1.0f}; SpriteTransformationMatrix transformationMatrix; - ui::LogicalDisplayId displayId{ui::ADISPLAY_ID_DEFAULT}; + ui::LogicalDisplayId displayId{ui::LogicalDisplayId::DEFAULT}; sp<SurfaceControl> surfaceControl; int32_t surfaceWidth{0}; diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp index 7a133801f514..2dcb1f1d1650 100644 --- a/libs/input/tests/PointerController_test.cpp +++ b/libs/input/tests/PointerController_test.cpp @@ -166,7 +166,7 @@ protected: PointerControllerTest(); ~PointerControllerTest(); - void ensureDisplayViewportIsSet(ui::LogicalDisplayId displayId = ui::ADISPLAY_ID_DEFAULT); + void ensureDisplayViewportIsSet(ui::LogicalDisplayId displayId = ui::LogicalDisplayId::DEFAULT); sp<MockSprite> mPointerSprite; sp<MockPointerControllerPolicyInterface> mPolicy; @@ -335,23 +335,23 @@ TEST_F(PointerControllerTest, updatesSkipScreenshotFlagForTouchSpots) { // Update spots to sync state with sprite mPointerController->setSpots(&testSpotCoords, testIdToIndex.cbegin(), testIdBits, - ui::ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); testing::Mock::VerifyAndClearExpectations(testSpotSprite.get()); // Marking the display to skip screenshot should update sprite as well - mPointerController->setSkipScreenshot(ui::ADISPLAY_ID_DEFAULT, true); + mPointerController->setSkipScreenshot(ui::LogicalDisplayId::DEFAULT, true); EXPECT_CALL(*testSpotSprite, setSkipScreenshot).With(testing::Args<0>(true)); // Update spots to sync state with sprite mPointerController->setSpots(&testSpotCoords, testIdToIndex.cbegin(), testIdBits, - ui::ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); testing::Mock::VerifyAndClearExpectations(testSpotSprite.get()); // Reset flag and verify again - mPointerController->setSkipScreenshot(ui::ADISPLAY_ID_DEFAULT, false); + mPointerController->setSkipScreenshot(ui::LogicalDisplayId::DEFAULT, false); EXPECT_CALL(*testSpotSprite, setSkipScreenshot).With(testing::Args<0>(false)); mPointerController->setSpots(&testSpotCoords, testIdToIndex.cbegin(), testIdBits, - ui::ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); testing::Mock::VerifyAndClearExpectations(testSpotSprite.get()); } diff --git a/packages/SettingsLib/IllustrationPreference/Android.bp b/packages/SettingsLib/IllustrationPreference/Android.bp index 6407810367cf..c3a91a20c339 100644 --- a/packages/SettingsLib/IllustrationPreference/Android.bp +++ b/packages/SettingsLib/IllustrationPreference/Android.bp @@ -21,6 +21,7 @@ android_library { "SettingsLibColor", "androidx.preference_preference", "lottie", + "settingslib_illustrationpreference_flags_lib", ], sdk_version: "system_current", @@ -31,3 +32,24 @@ android_library { "com.android.permission", ], } + +aconfig_declarations { + name: "settingslib_illustrationpreference_flags", + package: "com.android.settingslib.widget.flags", + container: "system", + srcs: [ + "aconfig/illustrationpreference.aconfig", + ], +} + +java_aconfig_library { + name: "settingslib_illustrationpreference_flags_lib", + aconfig_declarations: "settingslib_illustrationpreference_flags", + + min_sdk_version: "30", + + apex_available: [ + "//apex_available:platform", + "com.android.permission", + ], +} diff --git a/packages/SettingsLib/IllustrationPreference/aconfig/illustrationpreference.aconfig b/packages/SettingsLib/IllustrationPreference/aconfig/illustrationpreference.aconfig new file mode 100644 index 000000000000..e566d89facaf --- /dev/null +++ b/packages/SettingsLib/IllustrationPreference/aconfig/illustrationpreference.aconfig @@ -0,0 +1,12 @@ +package: "com.android.settingslib.widget.flags" +container: "system" + +flag { + name: "auto_hide_empty_lottie_res" + namespace: "android_settings" + description: "Hides IllustrationPreference when Lottie resource is an empty file" + bug: "337873972" + metadata { + purpose: PURPOSE_BUGFIX + } +}
\ No newline at end of file diff --git a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java index 815a101957ad..bbf0315aa475 100644 --- a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java +++ b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java @@ -39,12 +39,14 @@ import androidx.preference.Preference; import androidx.preference.PreferenceViewHolder; import androidx.vectordrawable.graphics.drawable.Animatable2Compat; +import com.android.settingslib.widget.flags.Flags; import com.android.settingslib.widget.preference.illustration.R; import com.airbnb.lottie.LottieAnimationView; import com.airbnb.lottie.LottieDrawable; import java.io.FileNotFoundException; +import java.io.IOException; import java.io.InputStream; /** @@ -142,7 +144,7 @@ public class IllustrationPreference extends Preference { illustrationFrame.setLayoutParams(lp); illustrationView.setCacheComposition(mCacheComposition); - handleImageWithAnimation(illustrationView); + handleImageWithAnimation(illustrationView, illustrationFrame); handleImageFrameMaxHeight(backgroundView, illustrationView); if (mIsAutoScale) { @@ -332,7 +334,8 @@ public class IllustrationPreference extends Preference { } } - private void handleImageWithAnimation(LottieAnimationView illustrationView) { + private void handleImageWithAnimation(LottieAnimationView illustrationView, + ViewGroup container) { if (mImageDrawable != null) { resetAnimations(illustrationView); illustrationView.setImageDrawable(mImageDrawable); @@ -356,6 +359,25 @@ public class IllustrationPreference extends Preference { } if (mImageResId > 0) { + if (Flags.autoHideEmptyLottieRes()) { + // Check if resource is empty + try (InputStream is = illustrationView.getResources() + .openRawResource(mImageResId)) { + int check = is.read(); + // -1 = end of stream. if first read is end of stream, then file is empty + if (check == -1) { + illustrationView.setVisibility(View.GONE); + container.setVisibility(View.GONE); + return; + } + } catch (IOException e) { + Log.w(TAG, "Unable to open Lottie raw resource", e); + } + + illustrationView.setVisibility(View.VISIBLE); + container.setVisibility(View.VISIBLE); + } + resetAnimations(illustrationView); illustrationView.setImageResource(mImageResId); final Drawable drawable = illustrationView.getDrawable(); diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig index 89f54d9b3b3b..9c0d29df420f 100644 --- a/packages/SettingsLib/aconfig/settingslib.aconfig +++ b/packages/SettingsLib/aconfig/settingslib.aconfig @@ -62,3 +62,10 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "allow_all_widgets_on_lockscreen_by_default" + namespace: "systemui" + description: "Allow all widgets on the lock screen by default." + bug: "328261690" +} diff --git a/packages/SettingsLib/tests/robotests/Android.bp b/packages/SettingsLib/tests/robotests/Android.bp index f4ddd0ac59df..f87b5190c7a1 100644 --- a/packages/SettingsLib/tests/robotests/Android.bp +++ b/packages/SettingsLib/tests/robotests/Android.bp @@ -51,6 +51,7 @@ android_robolectric_test { "androidx.core_core", "flag-junit", "settingslib_media_flags_lib", + "settingslib_illustrationpreference_flags_lib", "testng", // TODO: remove once JUnit on Android provides assertThrows ], java_resource_dirs: ["config"], diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java index 6590bbdcdae6..ca53fc2ba47e 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java @@ -26,10 +26,14 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import android.content.Context; +import android.content.res.Resources; import android.graphics.drawable.AnimatedImageDrawable; import android.graphics.drawable.AnimatedVectorDrawable; import android.graphics.drawable.AnimationDrawable; import android.net.Uri; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; @@ -39,11 +43,13 @@ import android.widget.ImageView; import androidx.preference.PreferenceViewHolder; import androidx.test.core.app.ApplicationProvider; +import com.android.settingslib.widget.flags.Flags; import com.android.settingslib.widget.preference.illustration.R; import com.airbnb.lottie.LottieAnimationView; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -51,10 +57,14 @@ import org.mockito.MockitoAnnotations; import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; +import java.io.ByteArrayInputStream; + @RunWith(RobolectricTestRunner.class) public class IllustrationPreferenceTest { + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Mock private ViewGroup mRootView; private Uri mImageUri; @@ -66,6 +76,7 @@ public class IllustrationPreferenceTest { private final Context mContext = ApplicationProvider.getApplicationContext(); private IllustrationPreference.OnBindListener mOnBindListener; private LottieAnimationView mOnBindListenerAnimationView; + private FrameLayout mIllustrationFrame; @Before public void setUp() { @@ -75,14 +86,14 @@ public class IllustrationPreferenceTest { mBackgroundView = new ImageView(mContext); mAnimationView = spy(new LottieAnimationView(mContext)); mMiddleGroundLayout = new FrameLayout(mContext); - final FrameLayout illustrationFrame = new FrameLayout(mContext); - illustrationFrame.setLayoutParams( + mIllustrationFrame = new FrameLayout(mContext); + mIllustrationFrame.setLayoutParams( new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); doReturn(mMiddleGroundLayout).when(mRootView).findViewById(R.id.middleground_layout); doReturn(mBackgroundView).when(mRootView).findViewById(R.id.background_view); doReturn(mAnimationView).when(mRootView).findViewById(R.id.lottie_view); - doReturn(illustrationFrame).when(mRootView).findViewById(R.id.illustration_frame); + doReturn(mIllustrationFrame).when(mRootView).findViewById(R.id.illustration_frame); mViewHolder = spy(PreferenceViewHolder.createInstanceForTests(mRootView)); final AttributeSet attributeSet = Robolectric.buildAttributeSet().build(); @@ -158,11 +169,13 @@ public class IllustrationPreferenceTest { } @Test + @DisableFlags(Flags.FLAG_AUTO_HIDE_EMPTY_LOTTIE_RES) public void playLottieAnimationWithResource_verifyFailureListener() { // fake the valid lottie image final int fakeValidResId = 111; doNothing().when(mAnimationView).setImageResource(fakeValidResId); doReturn(null).when(mAnimationView).getDrawable(); + doNothing().when(mAnimationView).setAnimation(fakeValidResId); mPreference.setLottieAnimationResId(fakeValidResId); mPreference.onBindViewHolder(mViewHolder); @@ -171,6 +184,50 @@ public class IllustrationPreferenceTest { } @Test + @DisableFlags(Flags.FLAG_AUTO_HIDE_EMPTY_LOTTIE_RES) + public void handleImageWithAnimation_emptyInputStreamDisabledFlag_verifyContainerVisible() { + doNothing().when(mAnimationView).setImageResource(111); + doReturn(null).when(mAnimationView).getDrawable(); + + mPreference.setLottieAnimationResId(111); + mPreference.onBindViewHolder(mViewHolder); + + assertThat(mAnimationView.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mIllustrationFrame.getVisibility()).isEqualTo(View.VISIBLE); + } + + @Test + @EnableFlags(Flags.FLAG_AUTO_HIDE_EMPTY_LOTTIE_RES) + public void handleImageWithAnimation_emptyInputStreamEnabledFlag_verifyContainerHidden() { + Resources res = spy(mContext.getResources()); + doReturn(res).when(mAnimationView).getResources(); + doReturn(new ByteArrayInputStream(new byte[] {})).when(res).openRawResource(111); + + mPreference.setLottieAnimationResId(111); + mPreference.onBindViewHolder(mViewHolder); + + assertThat(mAnimationView.getVisibility()).isEqualTo(View.GONE); + assertThat(mIllustrationFrame.getVisibility()).isEqualTo(View.GONE); + } + + @Test + @EnableFlags(Flags.FLAG_AUTO_HIDE_EMPTY_LOTTIE_RES) + public void handleImageWithAnimation_nonEmptyInputStreamEnabledFlag_verifyContainerVisible() { + Resources res = spy(mContext.getResources()); + doReturn(res).when(mAnimationView).getResources(); + doReturn(new ByteArrayInputStream(new byte[] { 1, 2, 3 })).when(res).openRawResource(111); + doNothing().when(mAnimationView).setImageResource(111); + doNothing().when(mAnimationView).setAnimation(111); + doReturn(null).when(mAnimationView).getDrawable(); + + mPreference.setLottieAnimationResId(111); + mPreference.onBindViewHolder(mViewHolder); + + assertThat(mAnimationView.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mIllustrationFrame.getVisibility()).isEqualTo(View.VISIBLE); + } + + @Test public void setMaxHeight_smallerThanRestrictedHeight_matchResult() { final int restrictedHeight = mContext.getResources().getDimensionPixelSize( diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 626e219fca24..582b6a1489f7 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -470,6 +470,17 @@ flag { } flag { + name: "fix_screenshot_action_dismiss_system_windows" + namespace: "systemui" + description: "Dismiss existing system windows when starting action from screenshot UI" + bug: "309933761" + metadata { + purpose: PURPOSE_BUGFIX + } +} + + +flag { name: "screenshot_private_profile_behavior_fix" namespace: "systemui" description: "Private profile support for screenshots" @@ -891,4 +902,14 @@ flag { metadata { purpose: PURPOSE_BUGFIX } -}
\ No newline at end of file +} + +flag { + name: "media_controls_user_initiated_dismiss" + namespace: "systemui" + description: "Only dismiss media notifications when the control was removed by the user." + bug: "335875159" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/CollectAsStateDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/CollectAsStateDetector.kt new file mode 100644 index 000000000000..94620c4c73b4 --- /dev/null +++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/CollectAsStateDetector.kt @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.systemui.lint + +import com.android.tools.lint.client.api.UElementHandler +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 org.jetbrains.uast.UElement +import org.jetbrains.uast.UFile +import org.jetbrains.uast.UImportStatement + +class CollectAsStateDetector : Detector(), SourceCodeScanner { + + override fun getApplicableUastTypes(): List<Class<out UElement>> { + return listOf(UFile::class.java) + } + + override fun createUastHandler(context: JavaContext): UElementHandler { + return object : UElementHandler() { + override fun visitFile(node: UFile) { + node.imports.forEach { importStatement -> + visitImportStatement(context, importStatement) + } + } + } + } + + private fun visitImportStatement( + context: JavaContext, + importStatement: UImportStatement, + ) { + val importText = importStatement.importReference?.asSourceString() ?: return + if (ILLEGAL_IMPORT == importText) { + context.report( + issue = ISSUE, + scope = importStatement, + location = context.getLocation(importStatement), + message = "collectAsState considered harmful", + ) + } + } + + companion object { + @JvmField + val ISSUE = + Issue.create( + id = "OverlyEagerCollectAsState", + briefDescription = "collectAsState considered harmful", + explanation = + """ + go/sysui-compose#collect-as-state + + Don't use collectAsState as it will set up a coroutine that keeps collecting from a + flow until its coroutine scope becomes inactive. This prevents the work from being + properly paused while the surrounding lifecycle becomes paused or stopped and is + therefore considered harmful. + + Instead, use Flow.collectAsStateWithLifecycle(initial: T) or + StateFlow.collectAsStateWithLifecycle(). These APIs correctly pause the collection + coroutine while the lifecycle drops below the specified minActiveState (which + defaults to STARTED meaning that it will pause when the Compose-hosting window + becomes invisible). + """ + .trimIndent(), + category = Category.PERFORMANCE, + priority = 8, + severity = Severity.ERROR, + implementation = + Implementation( + CollectAsStateDetector::class.java, + Scope.JAVA_FILE_SCOPE, + ), + ) + + private val ILLEGAL_IMPORT = "androidx.compose.runtime.collectAsState" + } +} 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 cecbc474a18a..73ac6ccf8f76 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 @@ -32,6 +32,7 @@ class SystemUIIssueRegistry : IssueRegistry() { BindServiceOnMainThreadDetector.ISSUE, BroadcastSentViaContextDetector.ISSUE, CleanArchitectureDependencyViolationDetector.ISSUE, + CollectAsStateDetector.ISSUE, DumpableNotRegisteredDetector.ISSUE, FlowDetector.SHARED_FLOW_CREATION, SlowUserQueryDetector.ISSUE_SLOW_USER_ID_QUERY, diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/CollectAsStateDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/CollectAsStateDetectorTest.kt new file mode 100644 index 000000000000..6962b4eb31e6 --- /dev/null +++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/CollectAsStateDetectorTest.kt @@ -0,0 +1,106 @@ +/* + * 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 CollectAsStateDetectorTest : SystemUILintDetectorTest() { + + override fun getDetector(): Detector { + return CollectAsStateDetector() + } + + override fun getIssues(): List<Issue> { + return listOf( + CollectAsStateDetector.ISSUE, + ) + } + + @Test + fun testViolation() { + lint() + .files(COLLECT_AS_STATE_STUB, COLLECT_WITH_LIFECYCLE_AS_STATE_STUB, GOOD_FILE, BAD_FILE) + .issues(CollectAsStateDetector.ISSUE) + .run() + .expect( + """ +src/com/android/internal/systemui/lint/Bad.kt:3: Error: collectAsState considered harmful [OverlyEagerCollectAsState] +import androidx.compose.runtime.collectAsState +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +1 errors, 0 warnings + """ + .trimIndent() + ) + } + + @Test + fun testNoViolation() { + lint() + .files(COLLECT_AS_STATE_STUB, COLLECT_WITH_LIFECYCLE_AS_STATE_STUB, GOOD_FILE) + .issues(CollectAsStateDetector.ISSUE) + .run() + .expectClean() + } + + companion object { + private val COLLECT_AS_STATE_STUB = + TestFiles.kotlin( + """ + package androidx.compose.runtime + + fun collectAsState() {} + """ + .trimIndent() + ) + private val COLLECT_WITH_LIFECYCLE_AS_STATE_STUB = + TestFiles.kotlin( + """ + package androidx.lifecycle.compose + + fun collectAsStateWithLifecycle() {} + """ + .trimIndent() + ) + + private val BAD_FILE = + TestFiles.kotlin( + """ + package com.android.internal.systemui.lint + + import androidx.compose.runtime.collectAsState + + class Bad + """ + .trimIndent() + ) + + private val GOOD_FILE = + TestFiles.kotlin( + """ + package com.android.internal.systemui.lint + + import androidx.lifecycle.compose.collectAsStateWithLifecycle + + class Good + """ + .trimIndent() + ) + } +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt index c22b50d9dd64..fa01a4bf46c5 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt @@ -56,7 +56,6 @@ import androidx.compose.material3.windowsizeclass.WindowHeightSizeClass import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -77,6 +76,7 @@ import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.times +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.PlatformButton import com.android.compose.animation.Easings import com.android.compose.animation.scene.ElementKey @@ -111,7 +111,7 @@ fun BouncerContent( dialogFactory: BouncerDialogFactory, modifier: Modifier = Modifier, ) { - val isSideBySideSupported by viewModel.isSideBySideSupported.collectAsState() + val isSideBySideSupported by viewModel.isSideBySideSupported.collectAsStateWithLifecycle() val layout = calculateLayout(isSideBySideSupported = isSideBySideSupported) Box( @@ -220,7 +220,7 @@ private fun SplitLayout( viewModel: BouncerViewModel, modifier: Modifier = Modifier, ) { - val authMethod by viewModel.authMethodViewModel.collectAsState() + val authMethod by viewModel.authMethodViewModel.collectAsStateWithLifecycle() Row( modifier = @@ -316,7 +316,7 @@ private fun BesideUserSwitcherLayout( val (isSwapped, setSwapped) = rememberSaveable(isLeftToRight) { mutableStateOf(!isLeftToRight) } val isHeightExpanded = LocalWindowSizeClass.current.heightSizeClass == WindowHeightSizeClass.Expanded - val authMethod by viewModel.authMethodViewModel.collectAsState() + val authMethod by viewModel.authMethodViewModel.collectAsStateWithLifecycle() Row( modifier = @@ -480,7 +480,7 @@ private fun FoldAware( modifier: Modifier = Modifier, ) { val foldPosture: FoldPosture by foldPosture() - val isSplitAroundTheFoldRequired by viewModel.isFoldSplitRequired.collectAsState() + val isSplitAroundTheFoldRequired by viewModel.isFoldSplitRequired.collectAsStateWithLifecycle() val isSplitAroundTheFold = foldPosture == FoldPosture.Tabletop && isSplitAroundTheFoldRequired val currentSceneKey = if (isSplitAroundTheFold) SceneKeys.SplitSceneKey else SceneKeys.ContiguousSceneKey @@ -562,7 +562,7 @@ private fun StatusMessage( viewModel: BouncerMessageViewModel, modifier: Modifier = Modifier, ) { - val message: MessageViewModel? by viewModel.message.collectAsState() + val message: MessageViewModel? by viewModel.message.collectAsStateWithLifecycle() DisposableEffect(Unit) { viewModel.onShown() @@ -612,7 +612,7 @@ private fun OutputArea( modifier: Modifier = Modifier, ) { val authMethodViewModel: AuthMethodBouncerViewModel? by - viewModel.authMethodViewModel.collectAsState() + viewModel.authMethodViewModel.collectAsStateWithLifecycle() when (val nonNullViewModel = authMethodViewModel) { is PinBouncerViewModel -> @@ -642,7 +642,7 @@ private fun InputArea( modifier: Modifier = Modifier, ) { val authMethodViewModel: AuthMethodBouncerViewModel? by - viewModel.authMethodViewModel.collectAsState() + viewModel.authMethodViewModel.collectAsStateWithLifecycle() when (val nonNullViewModel = authMethodViewModel) { is PinBouncerViewModel -> { @@ -668,7 +668,8 @@ private fun ActionArea( viewModel: BouncerViewModel, modifier: Modifier = Modifier, ) { - val actionButton: BouncerActionButtonModel? by viewModel.actionButton.collectAsState() + val actionButton: BouncerActionButtonModel? by + viewModel.actionButton.collectAsStateWithLifecycle() val appearFadeInAnimatable = remember { Animatable(0f) } val appearMoveAnimatable = remember { Animatable(0f) } val appearAnimationInitialOffset = with(LocalDensity.current) { 80.dp.toPx() } @@ -735,7 +736,7 @@ private fun Dialog( bouncerViewModel: BouncerViewModel, dialogFactory: BouncerDialogFactory, ) { - val dialogViewModel by bouncerViewModel.dialogViewModel.collectAsState() + val dialogViewModel by bouncerViewModel.dialogViewModel.collectAsStateWithLifecycle() var dialog: AlertDialog? by remember { mutableStateOf(null) } dialogViewModel?.let { viewModel -> @@ -772,8 +773,8 @@ private fun UserSwitcher( return } - val selectedUserImage by viewModel.selectedUserImage.collectAsState(null) - val dropdownItems by viewModel.userSwitcherDropdown.collectAsState(emptyList()) + val selectedUserImage by viewModel.selectedUserImage.collectAsStateWithLifecycle(null) + val dropdownItems by viewModel.userSwitcherDropdown.collectAsStateWithLifecycle(emptyList()) Column( horizontalAlignment = Alignment.CenterHorizontally, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt index 2dcd0ff05c73..203bd7a69a7a 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt @@ -27,7 +27,6 @@ import androidx.compose.material3.TextField import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.ExperimentalComposeUiApi @@ -49,6 +48,7 @@ import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.PlatformIconButton import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel import com.android.systemui.common.ui.compose.SelectedUserAwareInputConnection @@ -62,18 +62,20 @@ internal fun PasswordBouncer( modifier: Modifier = Modifier, ) { val focusRequester = remember { FocusRequester() } - val isTextFieldFocusRequested by viewModel.isTextFieldFocusRequested.collectAsState() + val isTextFieldFocusRequested by + viewModel.isTextFieldFocusRequested.collectAsStateWithLifecycle() LaunchedEffect(isTextFieldFocusRequested) { if (isTextFieldFocusRequested) { focusRequester.requestFocus() } } - val password: String by viewModel.password.collectAsState() - val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsState() - val animateFailure: Boolean by viewModel.animateFailure.collectAsState() - val isImeSwitcherButtonVisible by viewModel.isImeSwitcherButtonVisible.collectAsState() - val selectedUserId by viewModel.selectedUserId.collectAsState() + val password: String by viewModel.password.collectAsStateWithLifecycle() + val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsStateWithLifecycle() + val animateFailure: Boolean by viewModel.animateFailure.collectAsStateWithLifecycle() + val isImeSwitcherButtonVisible by + viewModel.isImeSwitcherButtonVisible.collectAsStateWithLifecycle() + val selectedUserId by viewModel.selectedUserId.collectAsStateWithLifecycle() DisposableEffect(Unit) { onDispose { viewModel.onHidden() } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt index d7e9c10e7224..9c2fd64052b4 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt @@ -30,7 +30,6 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -47,6 +46,7 @@ import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.integerResource import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.Easings import com.android.compose.modifiers.thenIf import com.android.internal.R @@ -86,14 +86,15 @@ internal fun PatternBouncer( val lineStrokeWidth = with(density) { LINE_STROKE_WIDTH_DP.dp.toPx() } // All dots that should be rendered on the grid. - val dots: List<PatternDotViewModel> by viewModel.dots.collectAsState() + val dots: List<PatternDotViewModel> by viewModel.dots.collectAsStateWithLifecycle() // The most recently selected dot, if the user is currently dragging. - val currentDot: PatternDotViewModel? by viewModel.currentDot.collectAsState() + val currentDot: PatternDotViewModel? by viewModel.currentDot.collectAsStateWithLifecycle() // The dots selected so far, if the user is currently dragging. - val selectedDots: List<PatternDotViewModel> by viewModel.selectedDots.collectAsState() - val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsState() - val isAnimationEnabled: Boolean by viewModel.isPatternVisible.collectAsState() - val animateFailure: Boolean by viewModel.animateFailure.collectAsState() + val selectedDots: List<PatternDotViewModel> by + viewModel.selectedDots.collectAsStateWithLifecycle() + val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsStateWithLifecycle() + val isAnimationEnabled: Boolean by viewModel.isPatternVisible.collectAsStateWithLifecycle() + val animateFailure: Boolean by viewModel.animateFailure.collectAsStateWithLifecycle() // Map of animatables for the scale of each dot, keyed by dot. val dotScalingAnimatables = remember(dots) { dots.associateWith { Animatable(1f) } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt index 5651a4646b2d..64ace2f18372 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt @@ -33,7 +33,6 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -49,6 +48,7 @@ import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalView import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.Easings import com.android.compose.grid.VerticalGrid import com.android.compose.modifiers.thenIf @@ -74,12 +74,13 @@ fun PinPad( ) { DisposableEffect(Unit) { onDispose { viewModel.onHidden() } } - val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsState() - val backspaceButtonAppearance by viewModel.backspaceButtonAppearance.collectAsState() - val confirmButtonAppearance by viewModel.confirmButtonAppearance.collectAsState() - val animateFailure: Boolean by viewModel.animateFailure.collectAsState() + val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsStateWithLifecycle() + val backspaceButtonAppearance by + viewModel.backspaceButtonAppearance.collectAsStateWithLifecycle() + val confirmButtonAppearance by viewModel.confirmButtonAppearance.collectAsStateWithLifecycle() + val animateFailure: Boolean by viewModel.animateFailure.collectAsStateWithLifecycle() val isDigitButtonAnimationEnabled: Boolean by - viewModel.isDigitButtonAnimationEnabled.collectAsState() + viewModel.isDigitButtonAnimationEnabled.collectAsStateWithLifecycle() val buttonScaleAnimatables = remember { List(12) { Animatable(1f) } } LaunchedEffect(animateFailure) { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinInputDisplay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinInputDisplay.kt index 1a97912c77bb..465eade4e169 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinInputDisplay.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinInputDisplay.kt @@ -42,7 +42,6 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.key import androidx.compose.runtime.mutableStateListOf @@ -65,6 +64,7 @@ import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.PlatformOutlinedButton import com.android.compose.animation.Easings import com.android.keyguard.PinShapeAdapter @@ -86,7 +86,7 @@ fun PinInputDisplay( viewModel: PinBouncerViewModel, modifier: Modifier = Modifier, ) { - val hintedPinLength: Int? by viewModel.hintedPinLength.collectAsState() + val hintedPinLength: Int? by viewModel.hintedPinLength.collectAsStateWithLifecycle() val shapeAnimations = rememberShapeAnimations(viewModel.pinShapes) // The display comes in two different flavors: @@ -119,7 +119,7 @@ private fun HintingPinInputDisplay( hintedPinLength: Int, modifier: Modifier = Modifier, ) { - val pinInput: PinInputViewModel by viewModel.pinInput.collectAsState() + val pinInput: PinInputViewModel by viewModel.pinInput.collectAsStateWithLifecycle() // [ClearAll] marker pointing at the beginning of the current pin input. // When a new [ClearAll] token is added to the [pinInput], the clear-all animation is played // and the marker is advanced manually to the most recent marker. See LaunchedEffect below. @@ -257,9 +257,10 @@ private fun RegularPinInputDisplay( @Composable private fun SimArea(viewModel: PinBouncerViewModel) { - val isLockedEsim by viewModel.isLockedEsim.collectAsState() - val isSimUnlockingDialogVisible by viewModel.isSimUnlockingDialogVisible.collectAsState() - val errorDialogMessage by viewModel.errorDialogMessage.collectAsState() + val isLockedEsim by viewModel.isLockedEsim.collectAsStateWithLifecycle() + val isSimUnlockingDialogVisible by + viewModel.isSimUnlockingDialogVisible.collectAsStateWithLifecycle() + val errorDialogMessage by viewModel.errorDialogMessage.collectAsStateWithLifecycle() var unlockDialog: Dialog? by remember { mutableStateOf(null) } var errorDialog: Dialog? by remember { mutableStateOf(null) } val context = LocalView.current.context diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/SelectedUserAwareInputConnection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/SelectedUserAwareInputConnection.kt index c8e145034551..694326d0549c 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/SelectedUserAwareInputConnection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/SelectedUserAwareInputConnection.kt @@ -35,7 +35,7 @@ import androidx.compose.ui.platform.PlatformTextInputMethodRequest * ``` * @Composable * fun YourFunction(viewModel: YourViewModel) { - * val selectedUserId by viewModel.selectedUserId.collectAsState() + * val selectedUserId by viewModel.selectedUserId.collectAsStateWithLifecycle() * * SelectedUserAwareInputConnection(selectedUserId) { * TextField(...) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt index 8144d15020fa..296fc27ac0ff 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt @@ -22,12 +22,12 @@ import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.systemBars import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import kotlinx.coroutines.flow.StateFlow /** The bounds and [CutoutLocation] of the current display. */ @@ -45,7 +45,7 @@ fun ScreenDecorProvider( screenCornerRadius: Float, content: @Composable () -> Unit, ) { - val cutout by displayCutout.collectAsState() + val cutout by displayCutout.collectAsStateWithLifecycle() val screenCornerRadiusDp = with(LocalDensity.current) { screenCornerRadius.toDp() } val density = LocalDensity.current diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt index 08e452cd0bd1..8ee8ea4b4f4c 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt @@ -6,13 +6,13 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.dimensionResource +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.Edge import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.ElementMatcher @@ -85,8 +85,9 @@ fun CommunalContainer( content: CommunalContent, ) { val coroutineScope = rememberCoroutineScope() - val currentSceneKey: SceneKey by viewModel.currentScene.collectAsState(CommunalScenes.Blank) - val touchesAllowed by viewModel.touchesAllowed.collectAsState(initial = false) + val currentSceneKey: SceneKey by + viewModel.currentScene.collectAsStateWithLifecycle(CommunalScenes.Blank) + val touchesAllowed by viewModel.touchesAllowed.collectAsStateWithLifecycle(initialValue = false) val state: MutableSceneTransitionLayoutState = remember { MutableSceneTransitionLayoutState( initialScene = currentSceneKey, @@ -149,7 +150,8 @@ private fun SceneScope.CommunalScene( content: CommunalContent, modifier: Modifier = Modifier, ) { - val backgroundColor by colors.backgroundColor.collectAsState() + val backgroundColor by colors.backgroundColor.collectAsStateWithLifecycle() + Box( modifier = Modifier.element(Communal.Elements.Scrim) 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 02621f6c84f8..2a52c60c820e 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 @@ -78,7 +78,6 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.State -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -124,6 +123,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.window.Popup import androidx.core.view.setPadding +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.window.layout.WindowMetricsCalculator import com.android.compose.modifiers.thenIf import com.android.compose.theme.LocalAndroidColorScheme @@ -156,20 +156,21 @@ fun CommunalHub( onOpenWidgetPicker: (() -> Unit)? = null, onEditDone: (() -> Unit)? = null, ) { - val communalContent by viewModel.communalContent.collectAsState(initial = emptyList()) - val currentPopup by viewModel.currentPopup.collectAsState(initial = null) + val communalContent by + viewModel.communalContent.collectAsStateWithLifecycle(initialValue = emptyList()) + val currentPopup by viewModel.currentPopup.collectAsStateWithLifecycle(initialValue = null) var removeButtonCoordinates: LayoutCoordinates? by remember { mutableStateOf(null) } var toolbarSize: IntSize? by remember { mutableStateOf(null) } var gridCoordinates: LayoutCoordinates? by remember { mutableStateOf(null) } var isDraggingToRemove by remember { mutableStateOf(false) } val gridState = rememberLazyGridState() val contentListState = rememberContentListState(widgetConfigurator, communalContent, viewModel) - val reorderingWidgets by viewModel.reorderingWidgets.collectAsState() - val selectedKey = viewModel.selectedKey.collectAsState() + val reorderingWidgets by viewModel.reorderingWidgets.collectAsStateWithLifecycle() + val selectedKey = viewModel.selectedKey.collectAsStateWithLifecycle() val removeButtonEnabled by remember { derivedStateOf { selectedKey.value != null || reorderingWidgets } } - val isEmptyState by viewModel.isEmptyState.collectAsState(initial = false) + val isEmptyState by viewModel.isEmptyState.collectAsStateWithLifecycle(initialValue = false) val contentPadding = gridContentPadding(viewModel.isEditMode, toolbarSize) val contentOffset = beforeContentPadding(contentPadding).toOffset() @@ -303,9 +304,9 @@ fun CommunalHub( if (viewModel is CommunalViewModel && dialogFactory != null) { val isEnableWidgetDialogShowing by - viewModel.isEnableWidgetDialogShowing.collectAsState(false) + viewModel.isEnableWidgetDialogShowing.collectAsStateWithLifecycle(false) val isEnableWorkProfileDialogShowing by - viewModel.isEnableWorkProfileDialogShowing.collectAsState(false) + viewModel.isEnableWorkProfileDialogShowing.collectAsStateWithLifecycle(false) EnableWidgetDialog( isEnableWidgetDialogVisible = isEnableWidgetDialogShowing, @@ -860,7 +861,7 @@ private fun WidgetContent( contentListState: ContentListState, ) { val context = LocalContext.current - val isFocusable by viewModel.isFocusable.collectAsState(initial = false) + val isFocusable by viewModel.isFocusable.collectAsStateWithLifecycle(initialValue = false) val accessibilityLabel = remember(model, context) { model.providerInfo.loadLabel(context.packageManager).toString().trim() @@ -868,7 +869,7 @@ private fun WidgetContent( val clickActionLabel = stringResource(R.string.accessibility_action_label_select_widget) val removeWidgetActionLabel = stringResource(R.string.accessibility_action_label_remove_widget) val placeWidgetActionLabel = stringResource(R.string.accessibility_action_label_place_widget) - val selectedKey by viewModel.selectedKey.collectAsState() + val selectedKey by viewModel.selectedKey.collectAsStateWithLifecycle() val selectedIndex = selectedKey?.let { key -> contentListState.list.indexOfFirst { it.key == key } } Box( @@ -1109,7 +1110,7 @@ private fun Umo(viewModel: BaseCommunalViewModel, modifier: Modifier = Modifier) @Composable fun AccessibilityContainer(viewModel: BaseCommunalViewModel, content: @Composable () -> Unit) { val context = LocalContext.current - val isFocusable by viewModel.isFocusable.collectAsState(initial = false) + val isFocusable by viewModel.isFocusable.collectAsStateWithLifecycle(initialValue = false) Box( modifier = Modifier.fillMaxWidth().wrapContentHeight().thenIf( diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/fold/ui/composable/FoldPosture.kt b/packages/SystemUI/compose/features/src/com/android/systemui/fold/ui/composable/FoldPosture.kt index e77ade91a93b..17dac7e2e6d5 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/fold/ui/composable/FoldPosture.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/fold/ui/composable/FoldPosture.kt @@ -18,11 +18,11 @@ package com.android.systemui.fold.ui.composable import androidx.compose.runtime.Composable import androidx.compose.runtime.State -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.produceState import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalContext +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.window.layout.WindowInfoTracker import com.android.systemui.fold.ui.helper.FoldPosture import com.android.systemui.fold.ui.helper.foldPostureInternal @@ -32,7 +32,8 @@ import com.android.systemui.fold.ui.helper.foldPostureInternal fun foldPosture(): State<FoldPosture> { val context = LocalContext.current val infoTracker = remember(context) { WindowInfoTracker.getOrCreate(context) } - val layoutInfo by infoTracker.windowLayoutInfo(context).collectAsState(initial = null) + val layoutInfo by + infoTracker.windowLayoutInfo(context).collectAsStateWithLifecycle(initialValue = null) return produceState<FoldPosture>( initialValue = FoldPosture.Folded, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt index a8d801abdcd0..67840c7fc696 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt @@ -29,7 +29,6 @@ import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.key import androidx.compose.ui.Alignment @@ -37,6 +36,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.theme.PlatformTheme import com.android.systemui.keyboard.stickykeys.shared.model.Locked import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey @@ -57,7 +57,7 @@ fun createStickyKeyIndicatorView(context: Context, viewModel: StickyKeysIndicato @Composable fun StickyKeysIndicator(viewModel: StickyKeysIndicatorViewModel) { - val stickyKeys by viewModel.indicatorContent.collectAsState(emptyMap()) + val stickyKeys by viewModel.indicatorContent.collectAsStateWithLifecycle(emptyMap()) StickyKeysIndicator(stickyKeys) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt index 4bef9efe79d1..6d8c47d84850 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt @@ -18,11 +18,11 @@ package com.android.systemui.keyguard.ui.composable import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalView +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.SceneScope import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor import com.android.systemui.keyguard.ui.composable.blueprint.ComposableLockscreenSceneBlueprint @@ -51,7 +51,7 @@ constructor( modifier: Modifier = Modifier, ) { val coroutineScope = rememberCoroutineScope() - val blueprintId by viewModel.blueprintId(coroutineScope).collectAsState() + val blueprintId by viewModel.blueprintId(coroutineScope).collectAsStateWithLifecycle() val view = LocalView.current DisposableEffect(view) { clockInteractor.clockEventController.registerListeners(view) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt index 472484aa74d9..4555f13a1a2c 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt @@ -26,13 +26,13 @@ import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxScope import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Rect import androidx.compose.ui.input.pointer.pointerInput +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel /** Container for lockscreen content that handles long-press to bring up the settings menu. */ @@ -42,7 +42,8 @@ fun LockscreenLongPress( modifier: Modifier = Modifier, content: @Composable BoxScope.(onSettingsMenuPlaces: (coordinates: Rect?) -> Unit) -> Unit, ) { - val isEnabled: Boolean by viewModel.isLongPressHandlingEnabled.collectAsState(initial = false) + val isEnabled: Boolean by + viewModel.isLongPressHandlingEnabled.collectAsStateWithLifecycle(initialValue = false) val (settingsMenuBounds, setSettingsMenuBounds) = remember { mutableStateOf<Rect?>(null) } val interactionSource = remember { MutableInteractionSource() } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BurnInState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BurnInState.kt index 8129e41b4977..ba25719f1d60 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BurnInState.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BurnInState.kt @@ -21,11 +21,11 @@ import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.union import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalDensity +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters import com.android.systemui.plugins.clocks.ClockController @@ -37,7 +37,7 @@ import kotlin.math.roundToInt fun rememberBurnIn( clockInteractor: KeyguardClockInteractor, ): BurnInState { - val clock by clockInteractor.currentClock.collectAsState() + val clock by clockInteractor.currentClock.collectAsStateWithLifecycle() val (smartspaceTop, onSmartspaceTopChanged) = remember { mutableStateOf<Float?>(null) } val (smallClockTop, onSmallClockTopChanged) = remember { mutableStateOf<Float?>(null) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt index 315253524b61..abff93d15c55 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt @@ -22,13 +22,13 @@ import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.Layout import androidx.compose.ui.unit.IntRect +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.SceneScope import com.android.compose.modifiers.padding import com.android.systemui.keyguard.ui.composable.LockscreenLongPress @@ -67,8 +67,8 @@ constructor( override fun SceneScope.Content(modifier: Modifier) { val isUdfpsVisible = viewModel.isUdfpsVisible val shouldUseSplitNotificationShade by - viewModel.shouldUseSplitNotificationShade.collectAsState() - val unfoldTranslations by viewModel.unfoldTranslations.collectAsState() + viewModel.shouldUseSplitNotificationShade.collectAsStateWithLifecycle() + val unfoldTranslations by viewModel.unfoldTranslations.collectAsStateWithLifecycle() LockscreenLongPress( viewModel = viewModel.longPress, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt index 9d31955122eb..c83f62c4281c 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt @@ -22,13 +22,13 @@ import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.Layout import androidx.compose.ui.unit.IntRect +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.SceneScope import com.android.compose.modifiers.padding import com.android.systemui.keyguard.ui.composable.LockscreenLongPress @@ -70,8 +70,8 @@ constructor( override fun SceneScope.Content(modifier: Modifier) { val isUdfpsVisible = viewModel.isUdfpsVisible val shouldUseSplitNotificationShade by - viewModel.shouldUseSplitNotificationShade.collectAsState() - val unfoldTranslations by viewModel.unfoldTranslations.collectAsState() + viewModel.shouldUseSplitNotificationShade.collectAsStateWithLifecycle() + val unfoldTranslations by viewModel.unfoldTranslations.collectAsStateWithLifecycle() LockscreenLongPress( viewModel = viewModel.longPress, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt index abbf0ea892ec..aaf49ff00aca 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt @@ -17,7 +17,6 @@ package com.android.systemui.keyguard.ui.composable.modifier import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -25,6 +24,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.boundsInWindow import androidx.compose.ui.layout.onPlaced +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters import com.android.systemui.keyguard.ui.viewmodel.BurnInScaleViewModel @@ -44,8 +44,10 @@ fun Modifier.burnInAware( val translationYState = remember { mutableStateOf(0F) } val copiedParams = params.copy(translationY = { translationYState.value }) val burnIn = viewModel.movement(copiedParams) - val translationX by burnIn.map { it.translationX.toFloat() }.collectAsState(initial = 0f) - val translationY by burnIn.map { it.translationY.toFloat() }.collectAsState(initial = 0f) + val translationX by + burnIn.map { it.translationX.toFloat() }.collectAsStateWithLifecycle(initialValue = 0f) + val translationY by + burnIn.map { it.translationY.toFloat() }.collectAsStateWithLifecycle(initialValue = 0f) translationYState.value = translationY val scaleViewModel by burnIn @@ -55,7 +57,7 @@ fun Modifier.burnInAware( scaleClockOnly = it.scaleClockOnly, ) } - .collectAsState(initial = BurnInScaleViewModel()) + .collectAsStateWithLifecycle(initialValue = BurnInScaleViewModel()) return this.graphicsLayer { this.translationX = if (isClock) 0F else translationX diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt index 09ec76df3aea..218779da20b9 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt @@ -25,13 +25,13 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.viewinterop.AndroidView import androidx.core.view.contains +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.SceneScope import com.android.compose.modifiers.padding import com.android.systemui.customization.R @@ -59,9 +59,11 @@ constructor( onTopChanged: (top: Float?) -> Unit, modifier: Modifier = Modifier, ) { - val currentClock by viewModel.currentClock.collectAsState() + val currentClock by viewModel.currentClock.collectAsStateWithLifecycle() val smallTopMargin by - viewModel.smallClockTopMargin.collectAsState(viewModel.getSmallClockTopMargin()) + viewModel.smallClockTopMargin.collectAsStateWithLifecycle( + viewModel.getSmallClockTopMargin() + ) if (currentClock?.smallClock?.view == null) { return } @@ -89,7 +91,7 @@ constructor( @Composable fun SceneScope.LargeClock(burnInParams: BurnInParameters, modifier: Modifier = Modifier) { - val currentClock by viewModel.currentClock.collectAsState() + val currentClock by viewModel.currentClock.collectAsStateWithLifecycle() if (currentClock?.largeClock?.view == null) { return } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt index c37d626ca8c5..3ca2b9c1d86c 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt @@ -18,9 +18,9 @@ package com.android.systemui.keyguard.ui.composable.section import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.SceneScope import com.android.systemui.keyguard.ui.viewmodel.KeyguardMediaViewModel import com.android.systemui.media.controls.ui.composable.MediaCarousel @@ -40,7 +40,7 @@ constructor( @Composable fun SceneScope.KeyguardMediaCarousel() { - val isMediaVisible by keyguardMediaViewModel.isMediaVisible.collectAsState() + val isMediaVisible by keyguardMediaViewModel.isMediaVisible.collectAsStateWithLifecycle() MediaCarousel( isVisible = isMediaVisible, 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 f48fa88b9722..7f80dfa703bc 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 @@ -20,13 +20,13 @@ import android.view.ViewGroup import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.SceneScope import com.android.compose.modifiers.thenIf import com.android.systemui.Flags @@ -40,16 +40,19 @@ import com.android.systemui.notifications.ui.composable.ConstrainedNotificationS import com.android.systemui.res.R import com.android.systemui.shade.LargeScreenHeaderHelper import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout +import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer import com.android.systemui.statusbar.notification.stack.ui.viewbinder.SharedNotificationContainerBinder import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel +import dagger.Lazy import javax.inject.Inject @SysUISingleton class NotificationSection @Inject constructor( + private val stackScrollView: Lazy<NotificationScrollView>, private val viewModel: NotificationsPlaceholderViewModel, private val aodBurnInViewModel: AodBurnInViewModel, sharedNotificationContainer: SharedNotificationContainer, @@ -88,9 +91,9 @@ constructor( @Composable fun SceneScope.Notifications(burnInParams: BurnInParameters?, modifier: Modifier = Modifier) { val shouldUseSplitNotificationShade by - lockscreenContentViewModel.shouldUseSplitNotificationShade.collectAsState() + lockscreenContentViewModel.shouldUseSplitNotificationShade.collectAsStateWithLifecycle() val areNotificationsVisible by - lockscreenContentViewModel.areNotificationsVisible.collectAsState() + lockscreenContentViewModel.areNotificationsVisible.collectAsStateWithLifecycle() val splitShadeTopMargin: Dp = if (Flags.centralizedStatusBarHeightFix()) { LargeScreenHeaderHelper.getLargeScreenHeaderHeight(LocalContext.current).dp @@ -103,6 +106,7 @@ constructor( } ConstrainedNotificationStack( + stackScrollView = stackScrollView.get(), viewModel = viewModel, modifier = modifier diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt index fc8b3b9009ce..44bda956b9f6 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt @@ -26,7 +26,6 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -34,6 +33,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.SceneScope import com.android.compose.modifiers.padding import com.android.systemui.keyguard.KeyguardUnlockAnimationController @@ -160,7 +160,7 @@ constructor( private fun Weather( modifier: Modifier = Modifier, ) { - val isVisible by keyguardSmartspaceViewModel.isWeatherVisible.collectAsState() + val isVisible by keyguardSmartspaceViewModel.isWeatherVisible.collectAsStateWithLifecycle() if (!isVisible) { return } @@ -187,7 +187,7 @@ constructor( private fun Date( modifier: Modifier = Modifier, ) { - val isVisible by keyguardSmartspaceViewModel.isDateVisible.collectAsState() + val isVisible by keyguardSmartspaceViewModel.isDateVisible.collectAsStateWithLifecycle() if (!isVisible) { return } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt index 63c70c97ed07..88b8298335aa 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt @@ -25,7 +25,6 @@ import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext @@ -33,6 +32,7 @@ import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntOffset +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.SceneScope import com.android.compose.animation.scene.SceneTransitionLayout import com.android.compose.modifiers.thenIf @@ -62,9 +62,9 @@ constructor( fun DefaultClockLayout( modifier: Modifier = Modifier, ) { - val currentClockLayout by clockViewModel.currentClockLayout.collectAsState() + val currentClockLayout by clockViewModel.currentClockLayout.collectAsStateWithLifecycle() val hasCustomPositionUpdatedAnimation by - clockViewModel.hasCustomPositionUpdatedAnimation.collectAsState() + clockViewModel.hasCustomPositionUpdatedAnimation.collectAsStateWithLifecycle() val currentScene = when (currentClockLayout) { KeyguardClockViewModel.ClockLayout.SPLIT_SHADE_LARGE_CLOCK -> @@ -133,7 +133,7 @@ constructor( @Composable private fun SceneScope.LargeClockWithSmartSpace(shouldOffSetClockToOneHalf: Boolean = false) { val burnIn = rememberBurnIn(clockInteractor) - val isLargeClockVisible by clockViewModel.isLargeClockVisible.collectAsState() + val isLargeClockVisible by clockViewModel.isLargeClockVisible.collectAsStateWithLifecycle() LaunchedEffect(isLargeClockVisible) { if (isLargeClockVisible) { @@ -170,8 +170,8 @@ constructor( @Composable private fun SceneScope.WeatherLargeClockWithSmartSpace(modifier: Modifier = Modifier) { val burnIn = rememberBurnIn(clockInteractor) - val isLargeClockVisible by clockViewModel.isLargeClockVisible.collectAsState() - val currentClockState = clockViewModel.currentClock.collectAsState() + val isLargeClockVisible by clockViewModel.isLargeClockVisible.collectAsStateWithLifecycle() + val currentClockState = clockViewModel.currentClock.collectAsStateWithLifecycle() LaunchedEffect(isLargeClockVisible) { if (isLargeClockVisible) { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt index 01d62a36fc62..cf2e895b044b 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt @@ -38,7 +38,6 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember @@ -64,6 +63,7 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import androidx.compose.ui.util.lerp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.NestedScrollBehavior import com.android.compose.animation.scene.SceneScope @@ -112,7 +112,7 @@ fun SceneScope.HeadsUpNotificationSpace( modifier: Modifier = Modifier, isPeekFromBottom: Boolean = false, ) { - val headsUpHeight = viewModel.headsUpHeight.collectAsState() + val headsUpHeight = viewModel.headsUpHeight.collectAsStateWithLifecycle() Element( Notifications.Elements.HeadsUpNotificationPlaceholder, @@ -138,6 +138,7 @@ fun SceneScope.HeadsUpNotificationSpace( /** Adds the space where notification stack should appear in the scene. */ @Composable fun SceneScope.ConstrainedNotificationStack( + stackScrollView: NotificationScrollView, viewModel: NotificationsPlaceholderViewModel, modifier: Modifier = Modifier, ) { @@ -146,6 +147,7 @@ fun SceneScope.ConstrainedNotificationStack( modifier.onSizeChanged { viewModel.onConstrainedAvailableSpaceChanged(it.height) } ) { NotificationPlaceholder( + stackScrollView = stackScrollView, viewModel = viewModel, modifier = Modifier.fillMaxSize(), ) @@ -178,9 +180,10 @@ fun SceneScope.NotificationScrollingStack( shadeSession.rememberSaveableSession(saver = ScrollState.Saver, key = null) { ScrollState(initial = 0) } - val syntheticScroll = viewModel.syntheticScroll.collectAsState(0f) - val isCurrentGestureOverscroll = viewModel.isCurrentGestureOverscroll.collectAsState(false) - val expansionFraction by viewModel.expandFraction.collectAsState(0f) + val syntheticScroll = viewModel.syntheticScroll.collectAsStateWithLifecycle(0f) + val isCurrentGestureOverscroll = + viewModel.isCurrentGestureOverscroll.collectAsStateWithLifecycle(false) + val expansionFraction by viewModel.expandFraction.collectAsStateWithLifecycle(0f) val navBarHeight = with(density) { WindowInsets.systemBars.asPaddingValues().calculateBottomPadding().toPx() } @@ -193,7 +196,8 @@ fun SceneScope.NotificationScrollingStack( */ val stackHeight = remember { mutableIntStateOf(0) } - val scrimRounding = viewModel.shadeScrimRounding.collectAsState(ShadeScrimRounding()) + val scrimRounding = + viewModel.shadeScrimRounding.collectAsStateWithLifecycle(ShadeScrimRounding()) // the offset for the notifications scrim. Its upper bound is 0, and its lower bound is // calculated in minScrimOffset. The scrim is the same height as the screen minus the @@ -334,6 +338,7 @@ fun SceneScope.NotificationScrollingStack( .debugBackground(viewModel, DEBUG_BOX_COLOR) ) { NotificationPlaceholder( + stackScrollView = stackScrollView, viewModel = viewModel, modifier = Modifier.verticalNestedScrollToScene( @@ -390,6 +395,7 @@ fun SceneScope.NotificationShelfSpace( @Composable private fun SceneScope.NotificationPlaceholder( + stackScrollView: NotificationScrollView, viewModel: NotificationsPlaceholderViewModel, modifier: Modifier = Modifier, ) { @@ -408,10 +414,8 @@ private fun SceneScope.NotificationPlaceholder( " bounds=${coordinates.boundsInWindow()}" } // NOTE: positionInWindow.y scrolls off screen, but boundsInWindow.top will not - viewModel.onStackBoundsChanged( - top = positionInWindow.y, - bottom = positionInWindow.y + coordinates.size.height, - ) + stackScrollView.setStackTop(positionInWindow.y) + stackScrollView.setStackBottom(positionInWindow.y + coordinates.size.height) } ) { content {} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt index 73cb72ca062e..b808044b76ce 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt @@ -36,7 +36,6 @@ import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.key import androidx.compose.ui.Alignment @@ -46,6 +45,7 @@ import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.theme.LocalAndroidColorScheme import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.people.ui.viewmodel.PeopleTileViewModel @@ -64,8 +64,8 @@ fun PeopleScreen( viewModel: PeopleViewModel, onResult: (PeopleViewModel.Result) -> Unit, ) { - val priorityTiles by viewModel.priorityTiles.collectAsState() - val recentTiles by viewModel.recentTiles.collectAsState() + val priorityTiles by viewModel.priorityTiles.collectAsStateWithLifecycle() + val recentTiles by viewModel.recentTiles.collectAsStateWithLifecycle() // Call [onResult] this activity when the ViewModel tells us so. LaunchedEffect(viewModel.result) { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt index 2f241cec37ee..e8da4bd2d2cd 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt @@ -44,7 +44,6 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -69,6 +68,7 @@ import androidx.compose.ui.unit.em import androidx.compose.ui.unit.sp import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.compose.animation.Expandable import com.android.compose.animation.scene.SceneScope @@ -132,8 +132,8 @@ fun FooterActions( val context = LocalContext.current // Collect alphas as soon as we are composed, even when not visible. - val alpha by viewModel.alpha.collectAsState() - val backgroundAlpha = viewModel.backgroundAlpha.collectAsState() + val alpha by viewModel.alpha.collectAsStateWithLifecycle() + val backgroundAlpha = viewModel.backgroundAlpha.collectAsStateWithLifecycle() var security by remember { mutableStateOf<FooterActionsSecurityButtonViewModel?>(null) } var foregroundServices by remember { @@ -181,7 +181,6 @@ fun FooterActions( val horizontalPadding = dimensionResource(R.dimen.qs_content_horizontal_padding) Row( modifier - .sysuiResTag("qs_footer_actions") .fillMaxWidth() .graphicsLayer { this.alpha = alpha } .then(backgroundModifier) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/BrightnessMirror.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/BrightnessMirror.kt index ca6b3434d90e..73a624a030fe 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/BrightnessMirror.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/BrightnessMirror.kt @@ -21,13 +21,13 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.offset import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.viewinterop.AndroidView +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.modifiers.height import com.android.compose.modifiers.width import com.android.systemui.qs.ui.adapter.QSSceneAdapter @@ -40,13 +40,13 @@ fun BrightnessMirror( qsSceneAdapter: QSSceneAdapter, modifier: Modifier = Modifier, ) { - val isShowing by viewModel.isShowing.collectAsState() + val isShowing by viewModel.isShowing.collectAsStateWithLifecycle() val mirrorAlpha by animateFloatAsState( targetValue = if (isShowing) 1f else 0f, label = "alphaAnimationBrightnessMirrorShowing", ) - val mirrorOffsetAndSize by viewModel.locationAndSize.collectAsState() + val mirrorOffsetAndSize by viewModel.locationAndSize.collectAsStateWithLifecycle() val offset = IntOffset(0, mirrorOffsetAndSize.yOffset) Box(modifier = modifier.fillMaxSize().graphicsLayer { alpha = mirrorAlpha }) { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt index 46be6b898b8d..d1099883c5e5 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt @@ -22,18 +22,19 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.layout.layout import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.viewinterop.AndroidView +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.MovableElementScenePicker import com.android.compose.animation.scene.SceneScope import com.android.compose.animation.scene.TransitionState import com.android.compose.animation.scene.ValueKey import com.android.compose.modifiers.thenIf +import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.qs.ui.adapter.QSSceneAdapter import com.android.systemui.qs.ui.adapter.QSSceneAdapter.State.Companion.Collapsing import com.android.systemui.qs.ui.adapter.QSSceneAdapter.State.Expanding @@ -143,7 +144,9 @@ fun SceneScope.QuickSettings( MovableElement( key = QuickSettings.Elements.Content, modifier = - modifier.fillMaxWidth().layout { measurable, constraints -> + modifier.sysuiResTag("quick_settings_panel").fillMaxWidth().layout { + measurable, + constraints -> val placeable = measurable.measure(constraints) // Use the height of the correct view based on the scene it is being composed in val height = heightProvider().coerceAtLeast(0) @@ -161,9 +164,11 @@ private fun QuickSettingsContent( state: QSSceneAdapter.State, modifier: Modifier = Modifier, ) { - val qsView by qsSceneAdapter.qsView.collectAsState(null) + val qsView by qsSceneAdapter.qsView.collectAsStateWithLifecycle(null) val isCustomizing by - qsSceneAdapter.isCustomizerShowing.collectAsState(qsSceneAdapter.isCustomizerShowing.value) + qsSceneAdapter.isCustomizerShowing.collectAsStateWithLifecycle( + qsSceneAdapter.isCustomizerShowing.value + ) QuickSettingsTheme { val context = LocalContext.current 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 6ae0efa46d19..d76b19f3fa82 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 @@ -51,7 +51,6 @@ import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment @@ -63,6 +62,7 @@ import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.colorResource import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.SceneScope import com.android.compose.animation.scene.TransitionState import com.android.compose.animation.scene.animateSceneFloatAsState @@ -163,7 +163,8 @@ private fun SceneScope.QuickSettingsScene( ) { val cutoutLocation = LocalDisplayCutout.current.location - val brightnessMirrorShowing by viewModel.brightnessMirrorViewModel.isShowing.collectAsState() + val brightnessMirrorShowing by + viewModel.brightnessMirrorViewModel.isShowing.collectAsStateWithLifecycle() val contentAlpha by animateFloatAsState( targetValue = if (brightnessMirrorShowing) 0f else 1f, @@ -198,10 +199,11 @@ private fun SceneScope.QuickSettingsScene( Modifier.displayCutoutPadding() }, ) { - val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState() - val isCustomizerShowing by viewModel.qsSceneAdapter.isCustomizerShowing.collectAsState() + val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsStateWithLifecycle() + val isCustomizerShowing by + viewModel.qsSceneAdapter.isCustomizerShowing.collectAsStateWithLifecycle() val customizingAnimationDuration by - viewModel.qsSceneAdapter.customizerAnimationDuration.collectAsState() + viewModel.qsSceneAdapter.customizerAnimationDuration.collectAsStateWithLifecycle() val screenHeight = LocalRawScreenHeight.current BackHandler( @@ -343,10 +345,10 @@ private fun SceneScope.QuickSettingsScene( viewModel.qsSceneAdapter, { viewModel.qsSceneAdapter.qsHeight }, isSplitShade = false, - modifier = Modifier.sysuiResTag("quick_settings_panel") + modifier = Modifier ) - val isMediaVisible by viewModel.isMediaVisible.collectAsState() + val isMediaVisible by viewModel.isMediaVisible.collectAsStateWithLifecycle() MediaCarousel( isVisible = isMediaVisible, @@ -362,7 +364,8 @@ private fun SceneScope.QuickSettingsScene( isCustomizing = isCustomizing, customizingAnimationDuration = customizingAnimationDuration, lifecycleOwner = lifecycleOwner, - modifier = Modifier.align(Alignment.CenterHorizontally), + modifier = + Modifier.align(Alignment.CenterHorizontally).sysuiResTag("qs_footer_actions"), ) } NotificationScrollingStack( diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt index 7af9b7bb90e9..92b2b4e886b9 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt @@ -23,7 +23,6 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope @@ -34,6 +33,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.PointerEventPass import androidx.compose.ui.input.pointer.motionEventSpy import androidx.compose.ui.input.pointer.pointerInput +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.MutableSceneTransitionLayoutState import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.SceneTransitionLayout @@ -68,8 +68,9 @@ fun SceneContainer( modifier: Modifier = Modifier, ) { val coroutineScope = rememberCoroutineScope() - val currentSceneKey: SceneKey by viewModel.currentScene.collectAsState() - val currentDestinations by viewModel.currentDestinationScenes(coroutineScope).collectAsState() + val currentSceneKey: SceneKey by viewModel.currentScene.collectAsStateWithLifecycle() + val currentDestinations by + viewModel.currentDestinationScenes(coroutineScope).collectAsStateWithLifecycle() val state: MutableSceneTransitionLayoutState = remember { MutableSceneTransitionLayoutState( initialScene = currentSceneKey, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt index d5287362c36d..00ef11d3b745 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt @@ -30,13 +30,13 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.ReadOnlyComposable -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.LowestZIndexScenePicker import com.android.compose.animation.scene.SceneScope @@ -51,7 +51,7 @@ fun SceneScope.OverlayShade( modifier: Modifier = Modifier, content: @Composable () -> Unit, ) { - val backgroundScene by viewModel.backgroundScene.collectAsState() + val backgroundScene by viewModel.backgroundScene.collectAsStateWithLifecycle() Box(modifier) { if (backgroundScene == Scenes.Lockscreen) { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt index 709a416c0366..ac3e015e52a9 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt @@ -35,7 +35,6 @@ import androidx.compose.foundation.layout.widthIn import androidx.compose.material3.ColorScheme import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember @@ -52,6 +51,7 @@ import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.max import androidx.compose.ui.viewinterop.AndroidView +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.LowestZIndexScenePicker import com.android.compose.animation.scene.SceneScope @@ -118,7 +118,7 @@ fun SceneScope.CollapsedShadeHeader( statusBarIconController: StatusBarIconController, modifier: Modifier = Modifier, ) { - val isDisabled by viewModel.isDisabled.collectAsState() + val isDisabled by viewModel.isDisabled.collectAsStateWithLifecycle() if (isDisabled) { return } @@ -138,7 +138,7 @@ fun SceneScope.CollapsedShadeHeader( } } - val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsState() + val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsStateWithLifecycle() // This layout assumes it is globally positioned at (0, 0) and is the // same size as the screen. @@ -271,7 +271,7 @@ fun SceneScope.ExpandedShadeHeader( statusBarIconController: StatusBarIconController, modifier: Modifier = Modifier, ) { - val isDisabled by viewModel.isDisabled.collectAsState() + val isDisabled by viewModel.isDisabled.collectAsStateWithLifecycle() if (isDisabled) { return } @@ -280,7 +280,7 @@ fun SceneScope.ExpandedShadeHeader( derivedStateOf { shouldUseExpandedFormat(layoutState.transitionState) } } - val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsState() + val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsStateWithLifecycle() Box(modifier = modifier.sysuiResTag(ShadeHeader.TestTags.Root)) { if (isPrivacyChipVisible) { @@ -435,7 +435,7 @@ private fun ShadeCarrierGroup( modifier: Modifier = Modifier, ) { Row(modifier = modifier) { - val subIds by viewModel.mobileSubIds.collectAsState() + val subIds by viewModel.mobileSubIds.collectAsStateWithLifecycle() for (subId in subIds) { Spacer(modifier = Modifier.width(5.dp)) @@ -472,10 +472,12 @@ private fun SceneScope.StatusIcons( val micSlot = stringResource(id = com.android.internal.R.string.status_bar_microphone) val locationSlot = stringResource(id = com.android.internal.R.string.status_bar_location) - val isSingleCarrier by viewModel.isSingleCarrier.collectAsState() - val isPrivacyChipEnabled by viewModel.isPrivacyChipEnabled.collectAsState() - val isMicCameraIndicationEnabled by viewModel.isMicCameraIndicationEnabled.collectAsState() - val isLocationIndicationEnabled by viewModel.isLocationIndicationEnabled.collectAsState() + val isSingleCarrier by viewModel.isSingleCarrier.collectAsStateWithLifecycle() + val isPrivacyChipEnabled by viewModel.isPrivacyChipEnabled.collectAsStateWithLifecycle() + val isMicCameraIndicationEnabled by + viewModel.isMicCameraIndicationEnabled.collectAsStateWithLifecycle() + val isLocationIndicationEnabled by + viewModel.isLocationIndicationEnabled.collectAsStateWithLifecycle() AndroidView( factory = { context -> @@ -544,7 +546,7 @@ private fun SceneScope.PrivacyChip( viewModel: ShadeHeaderViewModel, modifier: Modifier = Modifier, ) { - val privacyList by viewModel.privacyItems.collectAsState() + val privacyList by viewModel.privacyItems.collectAsStateWithLifecycle() AndroidView( factory = { context -> 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 42e6fccab0ae..a0278a616857 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 @@ -45,7 +45,6 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -58,6 +57,7 @@ import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.colorResource import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.LowestZIndexScenePicker import com.android.compose.animation.scene.SceneScope @@ -71,6 +71,7 @@ import com.android.systemui.battery.BatteryMeterViewController import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation import com.android.systemui.common.ui.compose.windowinsets.LocalDisplayCutout import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius +import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.dagger.SysUISingleton import com.android.systemui.media.controls.ui.composable.MediaCarousel import com.android.systemui.media.controls.ui.controller.MediaCarouselController @@ -177,7 +178,7 @@ private fun SceneScope.ShadeScene( modifier: Modifier = Modifier, shadeSession: SaveableSession, ) { - val shadeMode by viewModel.shadeMode.collectAsState() + val shadeMode by viewModel.shadeMode.collectAsStateWithLifecycle() when (shadeMode) { is ShadeMode.Single -> SingleShade( @@ -228,8 +229,8 @@ private fun SceneScope.SingleShade( key = QuickSettings.SharedValues.TilesSquishiness, canOverflow = false ) - val isClickable by viewModel.isClickable.collectAsState() - val isMediaVisible by viewModel.isMediaVisible.collectAsState() + val isClickable by viewModel.isClickable.collectAsStateWithLifecycle() + val isMediaVisible by viewModel.isMediaVisible.collectAsStateWithLifecycle() val shouldPunchHoleBehindScrim = layoutState.isTransitioningBetween(Scenes.Gone, Scenes.Shade) || @@ -335,10 +336,11 @@ private fun SceneScope.SplitShade( ) { val screenCornerRadius = LocalScreenCornerRadius.current - val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState() - val isCustomizerShowing by viewModel.qsSceneAdapter.isCustomizerShowing.collectAsState() + val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsStateWithLifecycle() + val isCustomizerShowing by + viewModel.qsSceneAdapter.isCustomizerShowing.collectAsStateWithLifecycle() val customizingAnimationDuration by - viewModel.qsSceneAdapter.customizerAnimationDuration.collectAsState() + viewModel.qsSceneAdapter.customizerAnimationDuration.collectAsStateWithLifecycle() val lifecycleOwner = LocalLifecycleOwner.current val footerActionsViewModel = remember(lifecycleOwner, viewModel) { viewModel.getFooterActionsViewModel(lifecycleOwner) } @@ -353,13 +355,13 @@ private fun SceneScope.SplitShade( .unfoldTranslationX( isOnStartSide = true, ) - .collectAsState(0f) + .collectAsStateWithLifecycle(0f) val unfoldTranslationXForEndSide by viewModel .unfoldTranslationX( isOnStartSide = false, ) - .collectAsState(0f) + .collectAsStateWithLifecycle(0f) val navBarBottomHeight = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() val bottomPadding by @@ -383,7 +385,8 @@ private fun SceneScope.SplitShade( } } - val brightnessMirrorShowing by viewModel.brightnessMirrorViewModel.isShowing.collectAsState() + val brightnessMirrorShowing by + viewModel.brightnessMirrorViewModel.isShowing.collectAsStateWithLifecycle() val contentAlpha by animateFloatAsState( targetValue = if (brightnessMirrorShowing) 0f else 1f, @@ -393,7 +396,7 @@ private fun SceneScope.SplitShade( viewModel.notifications.setAlphaForBrightnessMirror(contentAlpha) DisposableEffect(Unit) { onDispose { viewModel.notifications.setAlphaForBrightnessMirror(1f) } } - val isMediaVisible by viewModel.isMediaVisible.collectAsState() + val isMediaVisible by viewModel.isMediaVisible.collectAsStateWithLifecycle() val brightnessMirrorShowingModifier = Modifier.graphicsLayer { alpha = contentAlpha } @@ -444,6 +447,7 @@ private fun SceneScope.SplitShade( Column( modifier = Modifier.fillMaxSize() + .sysuiResTag("expanded_qs_scroll_view") .weight(1f) .thenIf(!isCustomizerShowing) { Modifier.verticalNestedScrollToScene() @@ -482,6 +486,7 @@ private fun SceneScope.SplitShade( lifecycleOwner = lifecycleOwner, modifier = Modifier.align(Alignment.CenterHorizontally) + .sysuiResTag("qs_footer_actions") .then(brightnessMirrorShowingModifier), ) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/VariableDayDate.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/VariableDayDate.kt index 5e107c60bee6..931ff56e56cb 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/VariableDayDate.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/VariableDayDate.kt @@ -3,9 +3,9 @@ package com.android.systemui.shade.ui.composable import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.ui.Modifier import androidx.compose.ui.layout.Layout +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.systemui.shade.ui.composable.ShadeHeader.Colors.shadeHeaderText import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel @@ -14,8 +14,8 @@ fun VariableDayDate( viewModel: ShadeHeaderViewModel, modifier: Modifier = Modifier, ) { - val longerText = viewModel.longerDateText.collectAsState() - val shorterText = viewModel.shorterDateText.collectAsState() + val longerText = viewModel.longerDateText.collectAsStateWithLifecycle() + val shorterText = viewModel.shorterDateText.collectAsStateWithLifecycle() Layout( contents = diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncButtonComponent.kt index 79d17efcacc1..fe1ebf975303 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncButtonComponent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncButtonComponent.kt @@ -16,6 +16,7 @@ package com.android.systemui.volume.panel.component.anc.ui.composable +import android.view.Gravity import androidx.compose.foundation.basicMarquee import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -25,11 +26,16 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.clearAndSetSemantics @@ -37,8 +43,10 @@ import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.role import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.systemui.res.R import com.android.systemui.volume.panel.component.anc.ui.viewmodel.AncViewModel +import com.android.systemui.volume.panel.component.popup.ui.composable.VolumePanelPopup import com.android.systemui.volume.panel.ui.composable.ComposeVolumePanelUiComponent import com.android.systemui.volume.panel.ui.composable.VolumePanelComposeScope import javax.inject.Inject @@ -52,17 +60,24 @@ constructor( @Composable override fun VolumePanelComposeScope.Content(modifier: Modifier) { - val slice by viewModel.buttonSlice.collectAsState() + val slice by viewModel.buttonSlice.collectAsStateWithLifecycle() val label = stringResource(R.string.volume_panel_noise_control_title) + val screenWidth: Float = + with(LocalDensity.current) { LocalConfiguration.current.screenWidthDp.dp.toPx() } + var gravity by remember { mutableIntStateOf(Gravity.CENTER_HORIZONTAL) } val isClickable = viewModel.isClickable(slice) val onClick = if (isClickable) { - { ancPopup.show(null) } + { with(ancPopup) { show(null, gravity) } } } else { null } + Column( - modifier = modifier, + modifier = + modifier.onGloballyPositioned { + gravity = VolumePanelPopup.calculateGravity(it, screenWidth) + }, verticalArrangement = Arrangement.spacedBy(12.dp), horizontalAlignment = Alignment.CenterHorizontally, ) { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt index e1ee01e78566..15df1be02f56 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt @@ -16,17 +16,18 @@ package com.android.systemui.volume.panel.component.anc.ui.composable +import android.view.Gravity import androidx.compose.foundation.basicMarquee import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.SideEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.slice.Slice import com.android.internal.logging.UiEventLogger import com.android.systemui.animation.Expandable @@ -47,9 +48,10 @@ constructor( ) { /** Shows a popup with the [expandable] animation. */ - fun show(expandable: Expandable?) { + fun show(expandable: Expandable?, horizontalGravity: Int) { uiEventLogger.log(VolumePanelUiEvent.VOLUME_PANEL_ANC_POPUP_SHOWN) - volumePanelPopup.show(expandable, { Title() }, { Content(it) }) + val gravity = horizontalGravity or Gravity.BOTTOM + volumePanelPopup.show(expandable, gravity, { Title() }, { Content(it) }) } @Composable @@ -65,14 +67,14 @@ constructor( @Composable private fun Content(dialog: SystemUIDialog) { - val isAvailable by viewModel.isAvailable.collectAsState(true) + val isAvailable by viewModel.isAvailable.collectAsStateWithLifecycle(true) if (!isAvailable) { SideEffect { dialog.dismiss() } return } - val slice by viewModel.popupSlice.collectAsState() + val slice by viewModel.popupSlice.collectAsStateWithLifecycle() SliceAndroidView( modifier = Modifier.fillMaxWidth(), slice = slice, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt index 0893b9d4c580..e1ae80f13312 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt @@ -16,6 +16,7 @@ package com.android.systemui.volume.panel.component.button.ui.composable +import android.view.Gravity import androidx.compose.foundation.basicMarquee import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -27,20 +28,27 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.clearAndSetSemantics import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.role import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.Expandable import com.android.systemui.animation.Expandable import com.android.systemui.common.ui.compose.Icon import com.android.systemui.volume.panel.component.button.ui.viewmodel.ButtonViewModel +import com.android.systemui.volume.panel.component.popup.ui.composable.VolumePanelPopup.Companion.calculateGravity import com.android.systemui.volume.panel.ui.composable.ComposeVolumePanelUiComponent import com.android.systemui.volume.panel.ui.composable.VolumePanelComposeScope import kotlinx.coroutines.flow.StateFlow @@ -48,17 +56,22 @@ import kotlinx.coroutines.flow.StateFlow /** [ComposeVolumePanelUiComponent] implementing a clickable button from a bottom row. */ class ButtonComponent( private val viewModelFlow: StateFlow<ButtonViewModel?>, - private val onClick: (Expandable) -> Unit + private val onClick: (expandable: Expandable, horizontalGravity: Int) -> Unit ) : ComposeVolumePanelUiComponent { @Composable override fun VolumePanelComposeScope.Content(modifier: Modifier) { - val viewModelByState by viewModelFlow.collectAsState() + val viewModelByState by viewModelFlow.collectAsStateWithLifecycle() val viewModel = viewModelByState ?: return val label = viewModel.label.toString() + val screenWidth: Float = + with(LocalDensity.current) { LocalConfiguration.current.screenWidthDp.dp.toPx() } + var gravity by remember { mutableIntStateOf(Gravity.CENTER_HORIZONTAL) } + Column( - modifier = modifier, + modifier = + modifier.onGloballyPositioned { gravity = calculateGravity(it, screenWidth) }, verticalArrangement = Arrangement.spacedBy(12.dp), horizontalAlignment = Alignment.CenterHorizontally, ) { @@ -82,7 +95,7 @@ class ButtonComponent( } else { MaterialTheme.colorScheme.onSurface }, - onClick = onClick, + onClick = { onClick(it, gravity) }, ) { Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { Icon(modifier = Modifier.size(24.dp), icon = viewModel.icon) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt index 12debbc9c011..1b821d36dceb 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt @@ -29,7 +29,6 @@ import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -42,6 +41,7 @@ import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.toggleableState import androidx.compose.ui.state.ToggleableState import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.systemui.common.ui.compose.Icon import com.android.systemui.volume.panel.component.button.ui.viewmodel.ButtonViewModel import com.android.systemui.volume.panel.ui.composable.ComposeVolumePanelUiComponent @@ -56,7 +56,7 @@ class ToggleButtonComponent( @Composable override fun VolumePanelComposeScope.Content(modifier: Modifier) { - val viewModelByState by viewModelFlow.collectAsState() + val viewModelByState by viewModelFlow.collectAsStateWithLifecycle() val viewModel = viewModelByState ?: return val label = viewModel.label.toString() diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt index ded63a107e70..237bbfd714b9 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt @@ -45,7 +45,6 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -55,6 +54,7 @@ import androidx.compose.ui.semantics.liveRegion import androidx.compose.ui.semantics.onClick import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.Expandable import com.android.systemui.common.ui.compose.Icon import com.android.systemui.common.ui.compose.toColor @@ -77,9 +77,9 @@ constructor( @Composable override fun VolumePanelComposeScope.Content(modifier: Modifier) { val connectedDeviceViewModel: ConnectedDeviceViewModel? by - viewModel.connectedDeviceViewModel.collectAsState() + viewModel.connectedDeviceViewModel.collectAsStateWithLifecycle() val deviceIconViewModel: DeviceIconViewModel? by - viewModel.deviceIconViewModel.collectAsState() + viewModel.deviceIconViewModel.collectAsStateWithLifecycle() val clickLabel = stringResource(R.string.volume_panel_enter_media_output_settings) Expandable( diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt index bb4e9574c602..3b1bf2ab9dcd 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt @@ -17,6 +17,7 @@ package com.android.systemui.volume.panel.component.popup.ui.composable import android.view.Gravity +import androidx.annotation.GravityInt import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -31,6 +32,8 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.LayoutCoordinates +import androidx.compose.ui.layout.boundsInRoot import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.paneTitle @@ -60,13 +63,14 @@ constructor( */ fun show( expandable: Expandable?, + @GravityInt gravity: Int, title: @Composable (SystemUIDialog) -> Unit, content: @Composable (SystemUIDialog) -> Unit, ) { val dialog = dialogFactory.create( theme = R.style.Theme_VolumePanel_Popup, - dialogGravity = Gravity.BOTTOM, + dialogGravity = gravity, ) { PopupComposable(it, title, content) } @@ -122,4 +126,23 @@ constructor( } } } + + companion object { + + /** + * Returns absolute ([Gravity.LEFT], [Gravity.RIGHT] or [Gravity.CENTER_HORIZONTAL]) + * [GravityInt] for the popup based on the [coordinates] global position relative to the + * [screenWidthPx]. + */ + @GravityInt + fun calculateGravity(coordinates: LayoutCoordinates, screenWidthPx: Float): Int { + val bottomCenter: Float = coordinates.boundsInRoot().bottomCenter.x + val rootBottomCenter: Float = screenWidthPx / 2 + return when { + bottomCenter < rootBottomCenter -> Gravity.LEFT + bottomCenter > rootBottomCenter -> Gravity.RIGHT + else -> Gravity.CENTER_HORIZONTAL + } + } + } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt index 12d2bc2e274b..9891b5b5eba2 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt @@ -16,17 +16,18 @@ package com.android.systemui.volume.panel.component.spatialaudio.ui.composable +import android.view.Gravity import androidx.compose.foundation.basicMarquee import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.SideEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.internal.logging.UiEventLogger import com.android.systemui.animation.Expandable import com.android.systemui.common.ui.compose.Icon @@ -47,14 +48,15 @@ constructor( ) { /** Shows a popup with the [expandable] animation. */ - fun show(expandable: Expandable) { + fun show(expandable: Expandable, horizontalGravity: Int) { uiEventLogger.logWithPosition( VolumePanelUiEvent.VOLUME_PANEL_SPATIAL_AUDIO_POP_UP_SHOWN, 0, null, viewModel.spatialAudioButtons.value.indexOfFirst { it.button.isActive } ) - volumePanelPopup.show(expandable, { Title() }, { Content(it) }) + val gravity = horizontalGravity or Gravity.BOTTOM + volumePanelPopup.show(expandable, gravity, { Title() }, { Content(it) }) } @Composable @@ -70,14 +72,14 @@ constructor( @Composable private fun Content(dialog: SystemUIDialog) { - val isAvailable by viewModel.isAvailable.collectAsState() + val isAvailable by viewModel.isAvailable.collectAsStateWithLifecycle() if (!isAvailable) { SideEffect { dialog.dismiss() } return } - val enabledModelStates by viewModel.spatialAudioButtons.collectAsState() + val enabledModelStates by viewModel.spatialAudioButtons.collectAsStateWithLifecycle() if (enabledModelStates.isEmpty()) { return } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt index 1def7fe95a4b..072e91a25444 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt @@ -40,7 +40,6 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.IconButtonDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.State -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -52,6 +51,7 @@ import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.stateDescription import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.PlatformSliderColors import com.android.compose.modifiers.padding import com.android.systemui.res.R @@ -84,7 +84,7 @@ fun ColumnVolumeSliders( modifier = Modifier.fillMaxWidth(), ) { val sliderViewModel: SliderViewModel = viewModels.first() - val sliderState by viewModels.first().slider.collectAsState() + val sliderState by viewModels.first().slider.collectAsStateWithLifecycle() val sliderPadding by topSliderPadding(isExpandable) VolumeSlider( @@ -119,7 +119,7 @@ fun ColumnVolumeSliders( Column { for (index in 1..viewModels.lastIndex) { val sliderViewModel: SliderViewModel = viewModels[index] - val sliderState by sliderViewModel.slider.collectAsState() + val sliderState by sliderViewModel.slider.collectAsStateWithLifecycle() transition.AnimatedVisibility( modifier = Modifier.padding(top = 16.dp), visible = { it || !isExpandable }, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt index bb17499f021f..d15430faa0a0 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt @@ -18,9 +18,9 @@ package com.android.systemui.volume.panel.component.volume.ui.composable import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.PlatformSliderColors import com.android.compose.grid.VerticalGrid import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.SliderViewModel @@ -39,7 +39,7 @@ fun GridVolumeSliders( horizontalSpacing = 24.dp, ) { for (sliderViewModel in viewModels) { - val sliderState = sliderViewModel.slider.collectAsState().value + val sliderState = sliderViewModel.slider.collectAsStateWithLifecycle().value VolumeSlider( modifier = Modifier.fillMaxWidth(), state = sliderState, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlidersComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlidersComponent.kt index 79056b26d051..770c5d5c247c 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlidersComponent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlidersComponent.kt @@ -18,9 +18,9 @@ package com.android.systemui.volume.panel.component.volume.ui.composable import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.PlatformSliderDefaults import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.SliderViewModel import com.android.systemui.volume.panel.component.volume.ui.viewmodel.AudioVolumeComponentViewModel @@ -38,7 +38,8 @@ constructor( @Composable override fun VolumePanelComposeScope.Content(modifier: Modifier) { - val sliderViewModels: List<SliderViewModel> by viewModel.sliderViewModels.collectAsState() + val sliderViewModels: List<SliderViewModel> by + viewModel.sliderViewModels.collectAsStateWithLifecycle() if (sliderViewModels.isEmpty()) { return } @@ -52,7 +53,7 @@ constructor( val expandableViewModel: SlidersExpandableViewModel by viewModel .isExpandable(isPortrait) - .collectAsState(SlidersExpandableViewModel.Unavailable) + .collectAsStateWithLifecycle(SlidersExpandableViewModel.Unavailable) if (expandableViewModel is SlidersExpandableViewModel.Unavailable) { return } 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 a602e25e05c1..83b8158a9bcf 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 @@ -24,7 +24,6 @@ import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -32,6 +31,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.paneTitle import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.systemui.res.R import com.android.systemui.volume.panel.ui.layout.ComponentsLayout import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelState @@ -54,8 +54,8 @@ fun VolumePanelRoot( } val accessibilityTitle = stringResource(R.string.accessibility_volume_settings) - val state: VolumePanelState by viewModel.volumePanelState.collectAsState() - val components by viewModel.componentsLayout.collectAsState(null) + val state: VolumePanelState by viewModel.volumePanelState.collectAsStateWithLifecycle() + val components by viewModel.componentsLayout.collectAsStateWithLifecycle(null) with(VolumePanelComposeScope(state)) { components?.let { componentsState -> diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt index 1cdc2b69034b..407bf4cac633 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt @@ -114,7 +114,7 @@ class CommunalMediaRepositoryImplTest : SysuiTestCase() { // Change to media unavailable and notify the listener. whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(false) - mediaDataListenerCaptor.value.onMediaDataRemoved("key") + mediaDataListenerCaptor.value.onMediaDataRemoved("key", false) runCurrent() // Media active now returns false. diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt index ce7b60e86f8f..325a324da8ad 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt @@ -29,6 +29,7 @@ import android.platform.test.annotations.EnableFlags import android.provider.Settings import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.settingslib.flags.Flags.FLAG_ALLOW_ALL_WIDGETS_ON_LOCKSCREEN_BY_DEFAULT import com.android.systemui.Flags.FLAG_COMMUNAL_HUB import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.broadcastDispatcher @@ -202,6 +203,18 @@ class CommunalSettingsRepositoryImplTest : SysuiTestCase() { .isEqualTo(AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD) } + @EnableFlags(FLAG_COMMUNAL_HUB, FLAG_ALLOW_ALL_WIDGETS_ON_LOCKSCREEN_BY_DEFAULT) + @Test + fun hubShowsAllWidgetsByDefaultWhenFlagEnabled() = + testScope.runTest { + val setting by collectLastValue(underTest.getWidgetCategories(PRIMARY_USER)) + assertThat(setting?.categories) + .isEqualTo( + AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD + + AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN + ) + } + private fun setKeyguardFeaturesDisabled(user: UserInfo, disabledFlags: Int) { whenever(kosmos.devicePolicyManager.getKeyguardDisabledFeatures(nullable(), eq(user.id))) .thenReturn(disabledFlags) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java index e3dd9aefa6d0..8bfa5cff8b97 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java @@ -27,6 +27,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.DreamManager; import android.content.res.Resources; import android.graphics.Region; import android.os.Handler; @@ -45,8 +46,10 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.ambient.touch.scrim.BouncerlessScrimController; import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor; import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback; +import com.android.systemui.communal.domain.interactor.CommunalInteractor; import com.android.systemui.complication.ComplicationHostViewController; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; +import com.android.systemui.shade.domain.interactor.ShadeInteractor; import com.android.systemui.statusbar.BlurUtils; import kotlinx.coroutines.CoroutineDispatcher; @@ -115,6 +118,12 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase { DreamOverlayStateController mStateController; @Mock KeyguardTransitionInteractor mKeyguardTransitionInteractor; + @Mock + ShadeInteractor mShadeInteractor; + @Mock + CommunalInteractor mCommunalInteractor; + @Mock + private DreamManager mDreamManager; DreamOverlayContainerViewController mController; @@ -146,7 +155,10 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase { mAnimationsController, mStateController, mBouncerlessScrimController, - mKeyguardTransitionInteractor); + mKeyguardTransitionInteractor, + mShadeInteractor, + mCommunalInteractor, + mDreamManager); } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt index d9224d7e3421..bd3b77a678db 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt @@ -27,12 +27,16 @@ import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.animation.DialogTransitionAnimator import com.android.systemui.animation.Expandable import com.android.systemui.bluetooth.mockBroadcastDialogController +import com.android.systemui.concurrency.fakeExecutor import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testScope +import com.android.systemui.media.controls.data.repository.mediaDataRepository import com.android.systemui.media.controls.domain.pipeline.MediaDataFilterImpl +import com.android.systemui.media.controls.domain.pipeline.MediaDataProcessor import com.android.systemui.media.controls.domain.pipeline.interactor.MediaControlInteractor import com.android.systemui.media.controls.domain.pipeline.interactor.mediaControlInteractor import com.android.systemui.media.controls.domain.pipeline.mediaDataFilter +import com.android.systemui.media.controls.domain.pipeline.mediaDataProcessor import com.android.systemui.media.controls.shared.model.MediaData import com.android.systemui.media.controls.util.mediaInstanceId import com.android.systemui.media.mediaOutputDialogManager @@ -211,6 +215,21 @@ class MediaControlInteractorTest : SysuiTestCase() { ) } + @Test + fun removeMediaControl() { + val listener = mock<MediaDataProcessor.Listener>() + kosmos.mediaDataProcessor.addInternalListener(listener) + + var mediaData = MediaData(userId = USER_ID, instanceId = instanceId, artist = ARTIST) + kosmos.mediaDataRepository.addMediaEntry(KEY, mediaData) + + underTest.removeMediaControl(null, instanceId, 0L) + kosmos.fakeExecutor.advanceClockToNext() + kosmos.fakeExecutor.runAllReady() + + verify(listener).onMediaDataRemoved(eq(KEY), eq(true)) + } + companion object { private const val USER_ID = 0 private const val KEY = "key" diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt new file mode 100644 index 000000000000..8cb811de851b --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar + +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import android.service.notification.NotificationListenerService +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.Flags +import com.android.systemui.SysuiTestCase +import com.android.systemui.dump.dumpManager +import com.android.systemui.media.controls.domain.pipeline.MediaDataManager +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.mockNotifCollection +import com.android.systemui.statusbar.notification.collection.notifPipeline +import com.android.systemui.statusbar.notification.collection.render.notificationVisibilityProvider +import com.android.systemui.testKosmos +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever + +@SmallTest +@RunWith(AndroidJUnit4::class) +class NotificationMediaManagerTest : SysuiTestCase() { + + private val KEY = "KEY" + + private val kosmos = testKosmos() + private val visibilityProvider = kosmos.notificationVisibilityProvider + private val notifPipeline = kosmos.notifPipeline + private val notifCollection = kosmos.mockNotifCollection + private val dumpManager = kosmos.dumpManager + private val mediaDataManager = mock<MediaDataManager>() + private val backgroundExecutor = FakeExecutor(FakeSystemClock()) + + private var listenerCaptor = argumentCaptor<MediaDataManager.Listener>() + + private lateinit var notificationMediaManager: NotificationMediaManager + + @Before + fun setup() { + notificationMediaManager = + NotificationMediaManager( + context, + visibilityProvider, + notifPipeline, + notifCollection, + mediaDataManager, + dumpManager, + backgroundExecutor, + ) + + verify(mediaDataManager).addListener(listenerCaptor.capture()) + } + + @Test + @EnableFlags(Flags.FLAG_MEDIA_CONTROLS_USER_INITIATED_DISMISS) + fun mediaDataRemoved_userInitiated_dismissNotif() { + val notifEntryCaptor = argumentCaptor<NotificationEntry>() + val notifEntry = mock<NotificationEntry>() + whenever(notifEntry.key).thenReturn(KEY) + whenever(notifEntry.ranking).thenReturn(NotificationListenerService.Ranking()) + whenever(notifPipeline.allNotifs).thenReturn(listOf(notifEntry)) + + listenerCaptor.lastValue.onMediaDataRemoved(KEY, true) + + verify(notifCollection).dismissNotification(notifEntryCaptor.capture(), any()) + assertThat(notifEntryCaptor.lastValue.key).isEqualTo(KEY) + } + + @Test + @EnableFlags(Flags.FLAG_MEDIA_CONTROLS_USER_INITIATED_DISMISS) + fun mediaDataRemoved_notUserInitiated_doesNotDismissNotif() { + listenerCaptor.lastValue.onMediaDataRemoved(KEY, false) + + verify(notifCollection, never()).dismissNotification(any(), any()) + } + + @Test + @DisableFlags(Flags.FLAG_MEDIA_CONTROLS_USER_INITIATED_DISMISS) + fun mediaDataRemoved_notUserInitiated_flagOff_dismissNotif() { + val notifEntryCaptor = argumentCaptor<NotificationEntry>() + val notifEntry = mock<NotificationEntry>() + whenever(notifEntry.key).thenReturn(KEY) + whenever(notifEntry.ranking).thenReturn(NotificationListenerService.Ranking()) + whenever(notifPipeline.allNotifs).thenReturn(listOf(notifEntry)) + + listenerCaptor.lastValue.onMediaDataRemoved(KEY, false) + + verify(notifCollection).dismissNotification(notifEntryCaptor.capture(), any()) + assertThat(notifEntryCaptor.lastValue.key).isEqualTo(KEY) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt index 1f0812da9fe9..ee9fd3494d96 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt @@ -44,13 +44,4 @@ class NotificationsPlaceholderViewModelTest : SysuiTestCase() { collectLastValue(kosmos.notificationStackAppearanceInteractor.shadeScrimBounds) assertThat(stackBounds).isEqualTo(bounds) } - - @Test - fun onStackBoundsChanged() = - kosmos.testScope.runTest { - underTest.onStackBoundsChanged(top = 5f, bottom = 500f) - assertThat(kosmos.notificationStackAppearanceInteractor.stackTop.value).isEqualTo(5f) - assertThat(kosmos.notificationStackAppearanceInteractor.stackBottom.value) - .isEqualTo(500f) - } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt index f2ce745ed293..da17366a8416 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt @@ -660,9 +660,6 @@ class SharedNotificationContainerViewModelTest(flags: FlagsParameterization) : S overrideResource(R.bool.config_use_split_notification_shade, false) configurationRepository.onAnyConfigurationChange() - keyguardInteractor.setNotificationContainerBounds( - NotificationContainerBounds(top = 1f, bottom = 2f) - ) assertThat(maxNotifications).isEqualTo(10) @@ -691,9 +688,6 @@ class SharedNotificationContainerViewModelTest(flags: FlagsParameterization) : S overrideResource(R.bool.config_use_split_notification_shade, false) configurationRepository.onAnyConfigurationChange() - keyguardInteractor.setNotificationContainerBounds( - NotificationContainerBounds(top = 1f, bottom = 2f) - ) assertThat(maxNotifications).isEqualTo(10) @@ -728,9 +722,6 @@ class SharedNotificationContainerViewModelTest(flags: FlagsParameterization) : S overrideResource(R.bool.config_use_split_notification_shade, false) configurationRepository.onAnyConfigurationChange() - keyguardInteractor.setNotificationContainerBounds( - NotificationContainerBounds(top = 1f, bottom = 2f) - ) // -1 means No Limit assertThat(maxNotifications).isEqualTo(-1) diff --git a/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt b/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt index c1be37af3aeb..a51d8ff4faa5 100644 --- a/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt +++ b/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt @@ -23,7 +23,6 @@ import androidx.compose.foundation.layout.size import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember @@ -32,6 +31,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.PlatformSlider import com.android.systemui.brightness.shared.GammaBrightness import com.android.systemui.brightness.ui.viewmodel.BrightnessSliderViewModel @@ -107,10 +107,13 @@ fun BrightnessSliderContainer( viewModel: BrightnessSliderViewModel, modifier: Modifier = Modifier, ) { - val gamma: Int by viewModel.currentBrightness.map { it.value }.collectAsState(initial = 0) + val gamma: Int by + viewModel.currentBrightness.map { it.value }.collectAsStateWithLifecycle(initialValue = 0) val coroutineScope = rememberCoroutineScope() val restriction by - viewModel.policyRestriction.collectAsState(initial = PolicyRestriction.NoRestriction) + viewModel.policyRestriction.collectAsStateWithLifecycle( + initialValue = PolicyRestriction.NoRestriction + ) BrightnessSlider( gammaValue = gamma, diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java index 5653bc27c9e9..2eca02c2b0d1 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java @@ -31,9 +31,12 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.systemui.communal.domain.interactor.CommunalInteractor; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor; import com.android.systemui.dock.DockManager; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor; +import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.shade.domain.interactor.ShadeInteractor; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.policy.BatteryController; @@ -88,6 +91,8 @@ class FalsingCollectorImpl implements FalsingCollector { private final JavaAdapter mJavaAdapter; private final SystemClock mSystemClock; private final Lazy<SelectedUserInteractor> mUserInteractor; + private final Lazy<DeviceEntryInteractor> mDeviceEntryInteractor; + private final Lazy<SceneContainerOcclusionInteractor> mSceneContainerOcclusionInteractor; private int mState; private boolean mShowingAod; @@ -170,7 +175,9 @@ class FalsingCollectorImpl implements FalsingCollector { JavaAdapter javaAdapter, SystemClock systemClock, Lazy<SelectedUserInteractor> userInteractor, - Lazy<CommunalInteractor> communalInteractorLazy) { + Lazy<CommunalInteractor> communalInteractorLazy, + Lazy<DeviceEntryInteractor> deviceEntryInteractor, + Lazy<SceneContainerOcclusionInteractor> sceneContainerOcclusionInteractor) { mFalsingDataProvider = falsingDataProvider; mFalsingManager = falsingManager; mKeyguardUpdateMonitor = keyguardUpdateMonitor; @@ -186,6 +193,8 @@ class FalsingCollectorImpl implements FalsingCollector { mSystemClock = systemClock; mUserInteractor = userInteractor; mCommunalInteractorLazy = communalInteractorLazy; + mDeviceEntryInteractor = deviceEntryInteractor; + mSceneContainerOcclusionInteractor = sceneContainerOcclusionInteractor; } @Override @@ -196,7 +205,18 @@ class FalsingCollectorImpl implements FalsingCollector { mStatusBarStateController.addCallback(mStatusBarStateListener); mState = mStatusBarStateController.getState(); - mKeyguardStateController.addCallback(mKeyguardStateControllerCallback); + if (SceneContainerFlag.isEnabled()) { + mJavaAdapter.alwaysCollectFlow( + mDeviceEntryInteractor.get().isDeviceEntered(), + this::isDeviceEnteredChanged + ); + mJavaAdapter.alwaysCollectFlow( + mSceneContainerOcclusionInteractor.get().getInvisibleDueToOcclusion(), + this::isInvisibleDueToOcclusionChanged + ); + } else { + mKeyguardStateController.addCallback(mKeyguardStateControllerCallback); + } mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateCallback); @@ -216,6 +236,14 @@ class FalsingCollectorImpl implements FalsingCollector { mDockManager.addListener(mDockEventListener); } + public void isDeviceEnteredChanged(boolean unused) { + updateSensorRegistration(); + } + + public void isInvisibleDueToOcclusionChanged(boolean unused) { + updateSensorRegistration(); + } + @Override public void onSuccessfulUnlock() { logDebug("REAL: onSuccessfulUnlock"); @@ -302,7 +330,7 @@ class FalsingCollectorImpl implements FalsingCollector { @Override public void onTouchEvent(MotionEvent ev) { logDebug("REAL: onTouchEvent(" + MotionEvent.actionToString(ev.getActionMasked()) + ")"); - if (!mKeyguardStateController.isShowing()) { + if (!isKeyguardShowing()) { avoidGesture(); return; } @@ -402,8 +430,8 @@ class FalsingCollectorImpl implements FalsingCollector { final boolean isKeyguard = mState == StatusBarState.KEYGUARD; final boolean isShadeOverOccludedKeyguard = mState == StatusBarState.SHADE - && mKeyguardStateController.isShowing() - && mKeyguardStateController.isOccluded(); + && isKeyguardShowing() + && isKeyguardOccluded(); return mScreenOn && !mShowingAod && (isKeyguard || isShadeOverOccludedKeyguard); } @@ -447,6 +475,32 @@ class FalsingCollectorImpl implements FalsingCollector { mFalsingManager.onProximityEvent(new ProximityEventImpl(proximityEvent)); } + /** + * Returns {@code true} if the keyguard is showing (whether or not the screen is on, whether or + * not an activity is occluding the keyguard, and whether or not the shade is open on top of the + * keyguard), or {@code false} if the user has dismissed the keyguard by authenticating or + * swiping up. + */ + private boolean isKeyguardShowing() { + if (SceneContainerFlag.isEnabled()) { + return !mDeviceEntryInteractor.get().isDeviceEntered().getValue(); + } else { + return mKeyguardStateController.isShowing(); + } + } + + /** + * Returns {@code true} if there is an activity display on top of ("occluding") the keyguard, or + * {@code false} if an activity is not occluding the keyguard (including if the keyguard is not + * showing at all). + */ + private boolean isKeyguardOccluded() { + if (SceneContainerFlag.isEnabled()) { + return mSceneContainerOcclusionInteractor.get().getInvisibleDueToOcclusion().getValue(); + } else { + return mKeyguardStateController.isOccluded(); + } + } static void logDebug(String msg) { if (DEBUG) { diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt index f437032d0ddb..971ab111d2f6 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt @@ -17,7 +17,6 @@ package com.android.systemui.communal import android.provider.Settings -import android.service.dreams.Flags.dreamTracksFocus import com.android.compose.animation.scene.SceneKey import com.android.systemui.CoreStartable import com.android.systemui.communal.domain.interactor.CommunalInteractor @@ -143,12 +142,10 @@ constructor( } } - if (dreamTracksFocus()) { - bgScope.launch { - communalInteractor.isIdleOnCommunal.collectLatest { - withContext(mainDispatcher) { - notificationShadeWindowController.setGlanceableHubShowing(it) - } + bgScope.launch { + communalInteractor.isIdleOnCommunal.collectLatest { + withContext(mainDispatcher) { + notificationShadeWindowController.setGlanceableHubShowing(it) } } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetCategories.kt b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetCategories.kt index 03f54c8b25d7..5cd15f278f00 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetCategories.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetCategories.kt @@ -17,15 +17,23 @@ package com.android.systemui.communal.data.model import android.appwidget.AppWidgetProviderInfo +import com.android.settingslib.flags.Flags.allowAllWidgetsOnLockscreenByDefault /** * The widget categories to display on communal hub (where categories is a bitfield with values that * match those in {@link AppWidgetProviderInfo}). */ @JvmInline -value class CommunalWidgetCategories( - // The default is keyguard widgets. - val categories: Int = AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD -) { +value class CommunalWidgetCategories(val categories: Int = defaultCategories) { fun contains(category: Int) = (categories and category) == category + + companion object { + val defaultCategories: Int + get() { + return AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD or + if (allowAllWidgetsOnLockscreenByDefault()) + AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN + else 0 + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt index e2fed6d0ea20..e5a0e5070b94 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt @@ -53,7 +53,7 @@ constructor( updateMediaModel(data) } - override fun onMediaDataRemoved(key: String) { + override fun onMediaDataRemoved(key: String, userInitiated: Boolean) { updateMediaModel() } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt index 9debe0e56083..88cb64c95c04 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt @@ -18,7 +18,6 @@ package com.android.systemui.communal.data.repository import android.app.admin.DevicePolicyManager import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL -import android.appwidget.AppWidgetProviderInfo import android.content.IntentFilter import android.content.pm.UserInfo import android.provider.Settings @@ -108,10 +107,9 @@ constructor( .onStart { emit(Unit) } .map { CommunalWidgetCategories( - // The default is to show only keyguard widgets. secureSettings.getIntForUser( GLANCEABLE_HUB_CONTENT_SETTING, - AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD, + CommunalWidgetCategories.defaultCategories, user.id ) ) diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt index f9de60984e2d..3e5126a307eb 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt @@ -75,7 +75,7 @@ constructor( scope = bgScope, // Start this eagerly since the value can be accessed synchronously. started = SharingStarted.Eagerly, - initialValue = CommunalWidgetCategories().categories + initialValue = CommunalWidgetCategories.defaultCategories ) private val workProfileUserInfoCallbackFlow: Flow<UserInfo?> = conflatedCallbackFlow { diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java index 6e043391c197..60006c68639d 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java @@ -16,6 +16,8 @@ package com.android.systemui.dreams; +import static android.service.dreams.Flags.dreamHandlesBeingObscured; + import static com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress; import static com.android.keyguard.BouncerPanelExpansionCalculator.getDreamAlphaScaledExpansion; import static com.android.keyguard.BouncerPanelExpansionCalculator.getDreamYPositionScaledExpansion; @@ -23,8 +25,10 @@ import static com.android.systemui.complication.ComplicationLayoutParams.POSITIO import static com.android.systemui.complication.ComplicationLayoutParams.POSITION_TOP; import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset; import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; +import static com.android.systemui.util.kotlin.JavaAdapterKt.combineFlows; import android.animation.Animator; +import android.app.DreamManager; import android.content.res.Resources; import android.graphics.Region; import android.os.Handler; @@ -37,7 +41,9 @@ import com.android.dream.lowlight.LowLightTransitionCoordinator; import com.android.systemui.ambient.touch.scrim.BouncerlessScrimController; import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor; import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback; +import com.android.systemui.communal.domain.interactor.CommunalInteractor; import com.android.systemui.complication.ComplicationHostViewController; +import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dreams.dagger.DreamOverlayComponent; import com.android.systemui.dreams.dagger.DreamOverlayModule; @@ -45,10 +51,12 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInterac import com.android.systemui.keyguard.shared.model.KeyguardState; import com.android.systemui.res.R; import com.android.systemui.shade.ShadeExpansionChangeEvent; +import com.android.systemui.shade.domain.interactor.ShadeInteractor; import com.android.systemui.statusbar.BlurUtils; import com.android.systemui.util.ViewController; import kotlinx.coroutines.CoroutineDispatcher; +import kotlinx.coroutines.flow.FlowKt; import java.util.Arrays; @@ -68,6 +76,8 @@ public class DreamOverlayContainerViewController extends private final DreamOverlayStateController mStateController; private final LowLightTransitionCoordinator mLowLightTransitionCoordinator; private final KeyguardTransitionInteractor mKeyguardTransitionInteractor; + private final ShadeInteractor mShadeInteractor; + private final CommunalInteractor mCommunalInteractor; private final ComplicationHostViewController mComplicationHostViewController; @@ -87,9 +97,10 @@ public class DreamOverlayContainerViewController extends // Main thread handler used to schedule periodic tasks (e.g. burn-in protection updates). private final Handler mHandler; - private final CoroutineDispatcher mMainDispatcher; + private final CoroutineDispatcher mBackgroundDispatcher; private final int mDreamOverlayMaxTranslationY; private final PrimaryBouncerCallbackInteractor mPrimaryBouncerCallbackInteractor; + private final DreamManager mDreamManager; private long mJitterStartTimeMillis; @@ -178,7 +189,7 @@ public class DreamOverlayContainerViewController extends LowLightTransitionCoordinator lowLightTransitionCoordinator, BlurUtils blurUtils, @Main Handler handler, - @Main CoroutineDispatcher mainDispatcher, + @Background CoroutineDispatcher backgroundDispatcher, @Main Resources resources, @Named(DreamOverlayModule.MAX_BURN_IN_OFFSET) int maxBurnInOffset, @Named(DreamOverlayModule.BURN_IN_PROTECTION_UPDATE_INTERVAL) long @@ -188,18 +199,23 @@ public class DreamOverlayContainerViewController extends DreamOverlayAnimationsController animationsController, DreamOverlayStateController stateController, BouncerlessScrimController bouncerlessScrimController, - KeyguardTransitionInteractor keyguardTransitionInteractor) { + KeyguardTransitionInteractor keyguardTransitionInteractor, + ShadeInteractor shadeInteractor, + CommunalInteractor communalInteractor, + DreamManager dreamManager) { super(containerView); mDreamOverlayContentView = contentView; mStatusBarViewController = statusBarViewController; mBlurUtils = blurUtils; mDreamOverlayAnimationsController = animationsController; mStateController = stateController; + mCommunalInteractor = communalInteractor; mLowLightTransitionCoordinator = lowLightTransitionCoordinator; mBouncerlessScrimController = bouncerlessScrimController; mKeyguardTransitionInteractor = keyguardTransitionInteractor; + mShadeInteractor = shadeInteractor; mComplicationHostViewController = complicationHostViewController; mDreamOverlayMaxTranslationY = resources.getDimensionPixelSize( @@ -211,11 +227,12 @@ public class DreamOverlayContainerViewController extends ViewGroup.LayoutParams.MATCH_PARENT)); mHandler = handler; - mMainDispatcher = mainDispatcher; + mBackgroundDispatcher = backgroundDispatcher; mMaxBurnInOffset = maxBurnInOffset; mBurnInProtectionUpdateInterval = burnInProtectionUpdateInterval; mMillisUntilFullJitter = millisUntilFullJitter; mPrimaryBouncerCallbackInteractor = primaryBouncerCallbackInteractor; + mDreamManager = dreamManager; } @Override @@ -238,11 +255,21 @@ public class DreamOverlayContainerViewController extends mView.getRootSurfaceControl().setTouchableRegion(emptyRegion); emptyRegion.recycle(); - collectFlow( - mView, - mKeyguardTransitionInteractor.isFinishedInStateWhere(KeyguardState::isBouncerState), - isFinished -> mAnyBouncerShowing = isFinished, - mMainDispatcher); + if (dreamHandlesBeingObscured()) { + collectFlow( + mView, + FlowKt.distinctUntilChanged(combineFlows( + mKeyguardTransitionInteractor.isFinishedInStateWhere( + KeyguardState::isBouncerState), + mShadeInteractor.isAnyExpanded(), + mCommunalInteractor.isCommunalShowing(), + (anyBouncerShowing, shadeExpanded, communalShowing) -> { + mAnyBouncerShowing = anyBouncerShowing; + return anyBouncerShowing || shadeExpanded || communalShowing; + })), + mDreamManager::setDreamIsObscured, + mBackgroundDispatcher); + } // Start dream entry animations. Skip animations for low light clock. if (!mStateController.isLowLightActive()) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java index dbaa297b7b43..68a252b2caba 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java @@ -37,6 +37,7 @@ import android.provider.Settings; import android.service.notification.ZenModeConfig; import android.text.TextUtils; import android.text.style.StyleSpan; +import android.util.Log; import androidx.core.graphics.drawable.IconCompat; import androidx.slice.Slice; @@ -212,21 +213,27 @@ public class KeyguardSliceProvider extends SliceProvider implements @AnyThread @Override public Slice onBindSlice(Uri sliceUri) { - Trace.beginSection("KeyguardSliceProvider#onBindSlice"); - Slice slice; - synchronized (this) { - ListBuilder builder = new ListBuilder(getContext(), mSliceUri, ListBuilder.INFINITY); - if (needsMediaLocked()) { - addMediaLocked(builder); - } else { - builder.addRow(new RowBuilder(mDateUri).setTitle(mLastText)); + Slice slice = null; + try { + Trace.beginSection("KeyguardSliceProvider#onBindSlice"); + synchronized (this) { + ListBuilder builder = new ListBuilder(getContext(), mSliceUri, + ListBuilder.INFINITY); + if (needsMediaLocked()) { + addMediaLocked(builder); + } else { + builder.addRow(new RowBuilder(mDateUri).setTitle(mLastText)); + } + addNextAlarmLocked(builder); + addZenModeLocked(builder); + addPrimaryActionLocked(builder); + slice = builder.build(); } - addNextAlarmLocked(builder); - addZenModeLocked(builder); - addPrimaryActionLocked(builder); - slice = builder.build(); + } catch (IllegalStateException e) { + Log.w(TAG, "Could not initialize slice", e); + } finally { + Trace.endSection(); } - Trace.endSection(); return slice; } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt index 2d7b7375f625..72857391793f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt @@ -97,6 +97,7 @@ constructor( /** Bounds of the notification container. */ val notificationContainerBounds: StateFlow<NotificationContainerBounds> by lazy { + SceneContainerFlag.assertInLegacyMode() combine( _notificationPlaceholderBounds, sharedNotificationContainerInteractor.get().configurationBasedDimensions, @@ -115,6 +116,7 @@ constructor( } fun setNotificationContainerBounds(position: NotificationContainerBounds) { + SceneContainerFlag.assertInLegacyMode() _notificationPlaceholderBounds.value = position } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt index 218967c338ea..7c745bc227f0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt @@ -57,9 +57,9 @@ class ClockSizeTransition( addTransition(SmartspaceMoveTransition(config, clockViewModel)) } - open class VisibilityBoundsTransition() : Transition() { - var captureSmartspace: Boolean = false - + abstract class VisibilityBoundsTransition() : Transition() { + abstract val captureSmartspace: Boolean + protected val TAG = this::class.simpleName!! override fun captureEndValues(transition: TransitionValues) = captureValues(transition) override fun captureStartValues(transition: TransitionValues) = captureValues(transition) override fun getTransitionProperties(): Array<String> = TRANSITION_PROPERTIES @@ -76,7 +76,7 @@ class ClockSizeTransition( parent.findViewById<View>(sharedR.id.bc_smartspace_view) ?: parent.findViewById<View>(R.id.keyguard_slice_view) if (targetSSView == null) { - Log.e(TAG, "Failed to find smartspace equivalent target for animation") + Log.e(TAG, "Failed to find smartspace equivalent target under $parent") return } transition.values[SMARTSPACE_BOUNDS] = targetSSView.getRect() @@ -109,14 +109,12 @@ class ClockSizeTransition( var fromIsVis = fromVis == View.VISIBLE var fromAlpha = startValues.values[PROP_ALPHA] as Float val fromBounds = startValues.values[PROP_BOUNDS] as Rect - val fromSSBounds = - if (captureSmartspace) startValues.values[SMARTSPACE_BOUNDS] as Rect else null + val fromSSBounds = startValues.values[SMARTSPACE_BOUNDS] as Rect? val toView = endValues.view val toVis = endValues.values[PROP_VISIBILITY] as Int val toBounds = endValues.values[PROP_BOUNDS] as Rect - val toSSBounds = - if (captureSmartspace) endValues.values[SMARTSPACE_BOUNDS] as Rect else null + val toSSBounds = endValues.values[SMARTSPACE_BOUNDS] as Rect? val toIsVis = toVis == View.VISIBLE val toAlpha = if (toIsVis) 1f else 0f @@ -221,9 +219,6 @@ class ClockSizeTransition( private const val SMARTSPACE_BOUNDS = "ClockSizeTransition:SSBounds" private val TRANSITION_PROPERTIES = arrayOf(PROP_VISIBILITY, PROP_ALPHA, PROP_BOUNDS, SMARTSPACE_BOUNDS) - - private val DEBUG = false - private val TAG = VisibilityBoundsTransition::class.simpleName!! } } @@ -232,18 +227,24 @@ class ClockSizeTransition( val viewModel: KeyguardClockViewModel, val smartspaceViewModel: KeyguardSmartspaceViewModel, ) : VisibilityBoundsTransition() { + override val captureSmartspace = !viewModel.isLargeClockVisible.value + init { duration = CLOCK_IN_MILLIS startDelay = CLOCK_IN_START_DELAY_MILLIS interpolator = CLOCK_IN_INTERPOLATOR - captureSmartspace = - !viewModel.isLargeClockVisible.value && smartspaceViewModel.isSmartspaceEnabled if (viewModel.isLargeClockVisible.value) { viewModel.currentClock.value?.let { + if (DEBUG) Log.i(TAG, "Large Clock In: ${it.largeClock.layout.views}") it.largeClock.layout.views.forEach { addTarget(it) } } + ?: run { + Log.e(TAG, "No large clock set, falling back") + addTarget(R.id.lockscreen_clock_view_large) + } } else { + if (DEBUG) Log.i(TAG, "Small Clock In") addTarget(R.id.lockscreen_clock_view) } } @@ -282,7 +283,6 @@ class ClockSizeTransition( val CLOCK_IN_INTERPOLATOR = Interpolators.LINEAR_OUT_SLOW_IN const val SMALL_CLOCK_IN_MOVE_SCALE = CLOCK_IN_MILLIS / SmartspaceMoveTransition.STATUS_AREA_MOVE_DOWN_MILLIS.toFloat() - private val TAG = ClockFaceInTransition::class.simpleName!! } } @@ -291,18 +291,24 @@ class ClockSizeTransition( val viewModel: KeyguardClockViewModel, val smartspaceViewModel: KeyguardSmartspaceViewModel, ) : VisibilityBoundsTransition() { + override val captureSmartspace = viewModel.isLargeClockVisible.value + init { duration = CLOCK_OUT_MILLIS interpolator = CLOCK_OUT_INTERPOLATOR - captureSmartspace = - viewModel.isLargeClockVisible.value && smartspaceViewModel.isSmartspaceEnabled if (viewModel.isLargeClockVisible.value) { + if (DEBUG) Log.i(TAG, "Small Clock Out") addTarget(R.id.lockscreen_clock_view) } else { viewModel.currentClock.value?.let { + if (DEBUG) Log.i(TAG, "Large Clock Out: ${it.largeClock.layout.views}") it.largeClock.layout.views.forEach { addTarget(it) } } + ?: run { + Log.e(TAG, "No large clock set, falling back") + addTarget(R.id.lockscreen_clock_view_large) + } } } @@ -339,7 +345,6 @@ class ClockSizeTransition( val CLOCK_OUT_INTERPOLATOR = Interpolators.LINEAR const val SMALL_CLOCK_OUT_MOVE_SCALE = CLOCK_OUT_MILLIS / SmartspaceMoveTransition.STATUS_AREA_MOVE_UP_MILLIS.toFloat() - private val TAG = ClockFaceOutTransition::class.simpleName!! } } @@ -348,6 +353,8 @@ class ClockSizeTransition( val config: IntraBlueprintTransition.Config, viewModel: KeyguardClockViewModel, ) : VisibilityBoundsTransition() { + override val captureSmartspace = false + init { duration = if (viewModel.isLargeClockVisible.value) STATUS_AREA_MOVE_UP_MILLIS @@ -367,4 +374,8 @@ class ClockSizeTransition( const val STATUS_AREA_MOVE_DOWN_MILLIS = 467L } } + + companion object { + val DEBUG = true + } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImpl.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImpl.kt index c02478b02ec2..96ef7d250012 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImpl.kt @@ -206,11 +206,11 @@ constructor( listeners.forEach { it.onSmartspaceMediaDataLoaded(key, data, shouldPrioritizeMutable) } } - override fun onMediaDataRemoved(key: String) { + override fun onMediaDataRemoved(key: String, userInitiated: Boolean) { allEntries.remove(key) userEntries.remove(key)?.let { // Only notify listeners if something actually changed - listeners.forEach { it.onMediaDataRemoved(key) } + listeners.forEach { it.onMediaDataRemoved(key, userInitiated) } } } @@ -246,7 +246,7 @@ constructor( // Only remove media when the profile is unavailable. if (DEBUG) Log.d(TAG, "Removing $key after profile change") userEntries.remove(key, data) - listeners.forEach { listener -> listener.onMediaDataRemoved(key) } + listeners.forEach { listener -> listener.onMediaDataRemoved(key, false) } } } } @@ -261,7 +261,7 @@ constructor( userEntries.clear() keyCopy.forEach { if (DEBUG) Log.d(TAG, "Removing $it after user change") - listenersCopy.forEach { listener -> listener.onMediaDataRemoved(it) } + listenersCopy.forEach { listener -> listener.onMediaDataRemoved(it, false) } } allEntries.forEach { (key, data) -> diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt index 3a83115642bc..143d66b69f57 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt @@ -545,8 +545,8 @@ class LegacyMediaDataManagerImpl( * External listeners registered with [addListener] will be notified after the event propagates * through the internal listener pipeline. */ - private fun notifyMediaDataRemoved(key: String) { - internalListeners.forEach { it.onMediaDataRemoved(key) } + private fun notifyMediaDataRemoved(key: String, userInitiated: Boolean = false) { + internalListeners.forEach { it.onMediaDataRemoved(key, userInitiated) } } /** @@ -578,7 +578,7 @@ class LegacyMediaDataManagerImpl( if (it.active == !timedOut && !forceUpdate) { if (it.resumption) { if (DEBUG) Log.d(TAG, "timing out resume player $key") - dismissMediaData(key, 0L /* delay */) + dismissMediaData(key, delay = 0L, userInitiated = false) } return } @@ -627,17 +627,17 @@ class LegacyMediaDataManagerImpl( } } - private fun removeEntry(key: String, logEvent: Boolean = true) { + private fun removeEntry(key: String, logEvent: Boolean = true, userInitiated: Boolean = false) { mediaEntries.remove(key)?.let { if (logEvent) { logger.logMediaRemoved(it.appUid, it.packageName, it.instanceId) } } - notifyMediaDataRemoved(key) + notifyMediaDataRemoved(key, userInitiated) } /** Dismiss a media entry. Returns false if the key was not found. */ - override fun dismissMediaData(key: String, delay: Long): Boolean { + override fun dismissMediaData(key: String, delay: Long, userInitiated: Boolean): Boolean { val existed = mediaEntries[key] != null backgroundExecutor.execute { mediaEntries[key]?.let { mediaData -> @@ -649,7 +649,10 @@ class LegacyMediaDataManagerImpl( } } } - foregroundExecutor.executeDelayed({ removeEntry(key) }, delay) + foregroundExecutor.executeDelayed( + { removeEntry(key = key, userInitiated = userInitiated) }, + delay + ) return existed } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatest.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatest.kt index ad70db5a3300..88910f944466 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatest.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatest.kt @@ -53,8 +53,8 @@ class MediaDataCombineLatest @Inject constructor() : listeners.toSet().forEach { it.onSmartspaceMediaDataLoaded(key, data) } } - override fun onMediaDataRemoved(key: String) { - remove(key) + override fun onMediaDataRemoved(key: String, userInitiated: Boolean) { + remove(key, userInitiated) } override fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean) { @@ -71,8 +71,8 @@ class MediaDataCombineLatest @Inject constructor() : } } - override fun onKeyRemoved(key: String) { - remove(key) + override fun onKeyRemoved(key: String, userInitiated: Boolean) { + remove(key, userInitiated) } /** @@ -92,10 +92,10 @@ class MediaDataCombineLatest @Inject constructor() : } } - private fun remove(key: String) { + private fun remove(key: String, userInitiated: Boolean) { entries.remove(key)?.let { val listenersCopy = listeners.toSet() - listenersCopy.forEach { it.onMediaDataRemoved(key) } + listenersCopy.forEach { it.onMediaDataRemoved(key, userInitiated) } } } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt index 5432a189cf7c..8d19ce800cbe 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt @@ -213,7 +213,7 @@ constructor( listeners.forEach { it.onSmartspaceMediaDataLoaded(key, data, shouldPrioritizeMutable) } } - override fun onMediaDataRemoved(key: String) { + override fun onMediaDataRemoved(key: String, userInitiated: Boolean) { mediaFilterRepository.removeMediaEntry(key)?.let { mediaData -> val instanceId = mediaData.instanceId mediaFilterRepository.removeSelectedUserMediaEntry(instanceId)?.let { @@ -221,7 +221,7 @@ constructor( MediaDataLoadingModel.Removed(instanceId) ) // Only notify listeners if something actually changed - listeners.forEach { it.onMediaDataRemoved(key) } + listeners.forEach { it.onMediaDataRemoved(key, userInitiated) } } } } @@ -270,7 +270,7 @@ constructor( mediaFilterRepository.addMediaDataLoadingState( MediaDataLoadingModel.Removed(data.instanceId) ) - listeners.forEach { listener -> listener.onMediaDataRemoved(key) } + listeners.forEach { listener -> listener.onMediaDataRemoved(key, false) } } } } @@ -288,7 +288,7 @@ constructor( MediaDataLoadingModel.Removed(instanceId) ) getKey(instanceId)?.let { - listenersCopy.forEach { listener -> listener.onMediaDataRemoved(it) } + listenersCopy.forEach { listener -> listener.onMediaDataRemoved(it, false) } } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManager.kt index 2331aa2170ef..8099e593b33d 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManager.kt @@ -60,7 +60,7 @@ interface MediaDataManager { ) /** Dismiss a media entry. Returns false if the key was not found. */ - fun dismissMediaData(key: String, delay: Long): Boolean + fun dismissMediaData(key: String, delay: Long, userInitiated: Boolean): Boolean /** * Called whenever the recommendation has been expired or removed by the user. This will remove @@ -136,7 +136,7 @@ interface MediaDataManager { ) {} /** Called whenever a previously existing Media notification was removed. */ - override fun onMediaDataRemoved(key: String) {} + override fun onMediaDataRemoved(key: String, userInitiated: Boolean) {} /** * Called whenever a previously existing Smartspace media data was removed. diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt index 1d7c0256b2ef..eed775242d1f 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt @@ -498,8 +498,8 @@ class MediaDataProcessor( * External listeners registered with [MediaCarouselInteractor.addListener] will be notified * after the event propagates through the internal listener pipeline. */ - private fun notifyMediaDataRemoved(key: String) { - internalListeners.forEach { it.onMediaDataRemoved(key) } + private fun notifyMediaDataRemoved(key: String, userInitiated: Boolean = false) { + internalListeners.forEach { it.onMediaDataRemoved(key, userInitiated) } } /** @@ -531,7 +531,7 @@ class MediaDataProcessor( if (it.active == !timedOut && !forceUpdate) { if (it.resumption) { if (DEBUG) Log.d(TAG, "timing out resume player $key") - dismissMediaData(key, 0L /* delay */) + dismissMediaData(key, delayMs = 0L, userInitiated = false) } return } @@ -580,17 +580,17 @@ class MediaDataProcessor( } } - private fun removeEntry(key: String, logEvent: Boolean = true) { + private fun removeEntry(key: String, logEvent: Boolean = true, userInitiated: Boolean = false) { mediaDataRepository.removeMediaEntry(key)?.let { if (logEvent) { logger.logMediaRemoved(it.appUid, it.packageName, it.instanceId) } } - notifyMediaDataRemoved(key) + notifyMediaDataRemoved(key, userInitiated) } /** Dismiss a media entry. Returns false if the key was not found. */ - fun dismissMediaData(key: String, delayMs: Long): Boolean { + fun dismissMediaData(key: String, delayMs: Long, userInitiated: Boolean): Boolean { val existed = mediaDataRepository.mediaEntries.value[key] != null backgroundExecutor.execute { mediaDataRepository.mediaEntries.value[key]?.let { mediaData -> @@ -602,16 +602,19 @@ class MediaDataProcessor( } } } - foregroundExecutor.executeDelayed({ removeEntry(key) }, delayMs) + foregroundExecutor.executeDelayed( + { removeEntry(key, userInitiated = userInitiated) }, + delayMs + ) return existed } /** Dismiss a media entry. Returns false if the corresponding key was not found. */ - fun dismissMediaData(instanceId: InstanceId, delayMs: Long): Boolean { + fun dismissMediaData(instanceId: InstanceId, delayMs: Long, userInitiated: Boolean): Boolean { val mediaEntries = mediaDataRepository.mediaEntries.value val filteredEntries = mediaEntries.filter { (_, data) -> data.instanceId == instanceId } return if (filteredEntries.isNotEmpty()) { - dismissMediaData(filteredEntries.keys.first(), delayMs) + dismissMediaData(filteredEntries.keys.first(), delayMs, userInitiated) } else { false } @@ -1579,7 +1582,7 @@ class MediaDataProcessor( ) {} /** Called whenever a previously existing Media notification was removed. */ - fun onMediaDataRemoved(key: String) {} + fun onMediaDataRemoved(key: String, userInitiated: Boolean) {} /** * Called whenever a previously existing Smartspace media data was removed. diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt index 0e2814b3d91e..043fbfaa8a23 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt @@ -111,10 +111,10 @@ constructor( } } - override fun onMediaDataRemoved(key: String) { + override fun onMediaDataRemoved(key: String, userInitiated: Boolean) { val token = entries.remove(key) token?.stop() - token?.let { listeners.forEach { it.onKeyRemoved(key) } } + token?.let { listeners.forEach { it.onKeyRemoved(key, userInitiated) } } } fun dump(pw: PrintWriter) { @@ -136,7 +136,7 @@ constructor( /** Called when the route has changed for a given notification. */ fun onMediaDeviceChanged(key: String, oldKey: String?, data: MediaDeviceData?) /** Called when the notification was removed. */ - fun onKeyRemoved(key: String) + fun onKeyRemoved(key: String, userInitiated: Boolean) } private inner class Entry( diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilter.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilter.kt index b2a8f2e71cc6..b178d84c5d18 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilter.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilter.kt @@ -137,7 +137,7 @@ constructor( // farther and dismiss the media data so that media controls for the local session // don't hang around while casting. if (!keyedTokens.get(key)!!.contains(TokenId(remote.sessionToken))) { - dispatchMediaDataRemoved(key) + dispatchMediaDataRemoved(key, userInitiated = false) } } } @@ -151,11 +151,11 @@ constructor( backgroundExecutor.execute { dispatchSmartspaceMediaDataLoaded(key, data) } } - override fun onMediaDataRemoved(key: String) { + override fun onMediaDataRemoved(key: String, userInitiated: Boolean) { // Queue on background thread to ensure ordering of loaded and removed events is maintained. backgroundExecutor.execute { keyedTokens.remove(key) - dispatchMediaDataRemoved(key) + dispatchMediaDataRemoved(key, userInitiated) } } @@ -174,8 +174,10 @@ constructor( } } - private fun dispatchMediaDataRemoved(key: String) { - foregroundExecutor.execute { listeners.toSet().forEach { it.onMediaDataRemoved(key) } } + private fun dispatchMediaDataRemoved(key: String, userInitiated: Boolean) { + foregroundExecutor.execute { + listeners.toSet().forEach { it.onMediaDataRemoved(key, userInitiated) } + } } private fun dispatchSmartspaceMediaDataLoaded(key: String, info: SmartspaceMediaData) { diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListener.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListener.kt index 29f396700831..fc319036d67e 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListener.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListener.kt @@ -169,7 +169,7 @@ constructor( mediaListeners[key] = PlaybackStateListener(key, data) } - override fun onMediaDataRemoved(key: String) { + override fun onMediaDataRemoved(key: String, userInitiated: Boolean) { mediaListeners.remove(key)?.destroy() } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt index c8889359331c..9e6230012760 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt @@ -205,12 +205,12 @@ constructor( ) } - override fun dismissMediaData(key: String, delay: Long): Boolean { - return mediaDataProcessor.dismissMediaData(key, delay) + override fun dismissMediaData(key: String, delay: Long, userInitiated: Boolean): Boolean { + return mediaDataProcessor.dismissMediaData(key, delay, userInitiated) } fun removeMediaControl(instanceId: InstanceId, delay: Long) { - mediaDataProcessor.dismissMediaData(instanceId, delay) + mediaDataProcessor.dismissMediaData(instanceId, delay, userInitiated = false) } override fun dismissSmartspaceRecommendation(key: String, delay: Long) { diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt index 9f2d132fc7a9..d1fee903e6f5 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt @@ -90,7 +90,8 @@ constructor( instanceId: InstanceId, delayMs: Long ): Boolean { - val dismissed = mediaDataProcessor.dismissMediaData(instanceId, delayMs) + val dismissed = + mediaDataProcessor.dismissMediaData(instanceId, delayMs, userInitiated = true) if (!dismissed) { Log.w( TAG, 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 45b68cad6342..b07253402645 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 @@ -193,6 +193,7 @@ constructor( private val mediaContent: ViewGroup @VisibleForTesting var pageIndicator: PageIndicator private var needsReordering: Boolean = false + private var isUserInitiatedRemovalQueued: Boolean = false private var keysNeedRemoval = mutableSetOf<String>() var shouldScrollToKey: Boolean = false private var isRtl: Boolean = false @@ -385,12 +386,15 @@ constructor( reorderAllPlayers(previousVisiblePlayerKey = null) } - keysNeedRemoval.forEach { removePlayer(it) } + keysNeedRemoval.forEach { + removePlayer(it, userInitiated = isUserInitiatedRemovalQueued) + } if (keysNeedRemoval.size > 0) { // Carousel visibility may need to be updated after late removals updateHostVisibility() } keysNeedRemoval.clear() + isUserInitiatedRemovalQueued = false // Update user visibility so that no extra impression will be logged when // activeMediaIndex resets to 0 @@ -474,18 +478,18 @@ constructor( val canRemove = data.isPlaying?.let { !it } ?: data.isClearable && !data.active if (canRemove && !Utils.useMediaResumption(context)) { - // This view isn't playing, let's remove this! This happens e.g. when - // dismissing/timing out a view. We still have the data around because - // resumption could be on, but we should save the resources and release - // this. + // This media control is both paused and timed out, and the resumption + // setting is off - let's remove it if (isReorderingAllowed) { - onMediaDataRemoved(key) + onMediaDataRemoved(key, userInitiated = MediaPlayerData.isSwipedAway) } else { + isUserInitiatedRemovalQueued = MediaPlayerData.isSwipedAway keysNeedRemoval.add(key) } } else { keysNeedRemoval.remove(key) } + MediaPlayerData.isSwipedAway = false } override fun onSmartspaceMediaDataLoaded( @@ -565,11 +569,12 @@ constructor( addSmartspaceMediaRecommendations(key, data, shouldPrioritize) } } + MediaPlayerData.isSwipedAway = false } - override fun onMediaDataRemoved(key: String) { - debugLogger.logMediaRemoved(key) - removePlayer(key) + override fun onMediaDataRemoved(key: String, userInitiated: Boolean) { + debugLogger.logMediaRemoved(key, userInitiated) + removePlayer(key, userInitiated = userInitiated) } override fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean) { @@ -1033,7 +1038,8 @@ constructor( fun removePlayer( key: String, dismissMediaData: Boolean = true, - dismissRecommendation: Boolean = true + dismissRecommendation: Boolean = true, + userInitiated: Boolean = false, ): MediaControlPanel? { if (key == MediaPlayerData.smartspaceMediaKey()) { MediaPlayerData.smartspaceMediaData?.let { @@ -1052,7 +1058,7 @@ constructor( if (dismissMediaData) { // Inform the media manager of a potentially late dismissal - mediaManager.dismissMediaData(key, delay = 0L) + mediaManager.dismissMediaData(key, delay = 0L, userInitiated = userInitiated) } if (dismissRecommendation) { // Inform the media manager of a potentially late dismissal @@ -1512,7 +1518,8 @@ constructor( } } - private fun onSwipeToDismiss() { + @VisibleForTesting + fun onSwipeToDismiss() { if (mediaFlags.isMediaControlsRefactorEnabled()) { mediaCarouselViewModel.onSwipeToDismiss() return @@ -1531,6 +1538,7 @@ constructor( it.mIsImpressed = false } } + MediaPlayerData.isSwipedAway = true logger.logSwipeDismiss() mediaManager.onSwipeToDismiss() } @@ -1557,6 +1565,7 @@ constructor( "state: ${desiredHostState?.expansion}, " + "only active ${desiredHostState?.showsOnlyActiveMedia}" ) + println("isSwipedAway: ${MediaPlayerData.isSwipedAway}") } } } @@ -1595,7 +1604,7 @@ internal object MediaPlayerData { val data: MediaData, val key: String, val updateTime: Long = 0, - val isSsReactivated: Boolean = false + val isSsReactivated: Boolean = false, ) private val comparator = @@ -1620,6 +1629,9 @@ internal object MediaPlayerData { // A map that tracks order of visible media players before they get reordered. private val visibleMediaPlayers = LinkedHashMap<String, MediaSortKey>() + // Whether the user swiped away the carousel since its last update + internal var isSwipedAway: Boolean = false + fun addMediaPlayer( key: String, data: MediaData, diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerLogger.kt index ebf1c6a10703..1be25a74dbea 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerLogger.kt @@ -53,8 +53,16 @@ constructor(@MediaCarouselControllerLog private val buffer: LogBuffer) { { "add player $str1, active: $bool1" } ) - fun logMediaRemoved(key: String) = - buffer.log(TAG, LogLevel.DEBUG, { str1 = key }, { "removing player $str1" }) + fun logMediaRemoved(key: String, userInitiated: Boolean) = + buffer.log( + TAG, + LogLevel.DEBUG, + { + str1 = key + bool1 = userInitiated + }, + { "removing player $str1, by user $bool1" } + ) fun logRecommendationLoaded(key: String, isActive: Boolean) = buffer.log( diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java index e6c785ef41f0..0bc3c43993dd 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java @@ -786,10 +786,11 @@ public class MediaControlPanel { if (mKey != null) { closeGuts(); if (!mMediaDataManagerLazy.get().dismissMediaData(mKey, - MediaViewController.GUTS_ANIMATION_DURATION + 100)) { + /* delay */ MediaViewController.GUTS_ANIMATION_DURATION + 100, + /* userInitiated */ true)) { Log.w(TAG, "Manager failed to dismiss media " + mKey); // Remove directly from carousel so user isn't stuck with defunct controls - mMediaCarouselController.removePlayer(mKey, false, false); + mMediaCarouselController.removePlayer(mKey, false, false, true); } } else { Log.w(TAG, "Dismiss media with null notification. Token uid=" diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt index eca76b603b1a..91050c8bfab3 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt @@ -105,7 +105,7 @@ class MediaHost( updateViewVisibility() } - override fun onMediaDataRemoved(key: String) { + override fun onMediaDataRemoved(key: String, userInitiated: Boolean) { updateViewVisibility() } diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java index 88a5f78e407d..061e7ecfa4a3 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java +++ b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java @@ -48,7 +48,7 @@ public class MediaDreamSentinel implements CoreStartable { } @Override - public void onMediaDataRemoved(@NonNull String key) { + public void onMediaDataRemoved(@NonNull String key, boolean userInitiated) { final boolean hasActiveMedia = mMediaDataManager.hasActiveMedia(); if (DEBUG) { Log.d(TAG, "onMediaDataRemoved(" + key + "), mAdded=" + mAdded + ", hasActiveMedia=" diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditMode.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditMode.kt index 5c17fd1bf94e..3bda7757f8e5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditMode.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditMode.kt @@ -20,9 +20,9 @@ import androidx.activity.compose.BackHandler import androidx.compose.foundation.layout.Column import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.systemui.qs.panels.ui.viewmodel.EditModeViewModel @Composable @@ -30,8 +30,8 @@ fun EditMode( viewModel: EditModeViewModel, modifier: Modifier = Modifier, ) { - val gridLayout by viewModel.gridLayout.collectAsState() - val tiles by viewModel.tiles.collectAsState(emptyList()) + val gridLayout by viewModel.gridLayout.collectAsStateWithLifecycle() + val tiles by viewModel.tiles.collectAsStateWithLifecycle(emptyList()) BackHandler { viewModel.stopEditing() } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt index dc43091e3c67..bac0f604e294 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt @@ -53,7 +53,6 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -72,6 +71,7 @@ import androidx.compose.ui.semantics.onClick import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.stateDescription import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.Expandable import com.android.compose.theme.colorAttr import com.android.systemui.common.shared.model.Icon @@ -116,8 +116,8 @@ constructor( tiles.forEach { it.startListening(token) } onDispose { tiles.forEach { it.stopListening(token) } } } - val iconTilesSpecs by iconTilesInteractor.iconTilesSpecs.collectAsState() - val columns by gridSizeInteractor.columns.collectAsState() + val iconTilesSpecs by iconTilesInteractor.iconTilesSpecs.collectAsStateWithLifecycle() + val columns by gridSizeInteractor.columns.collectAsStateWithLifecycle() TileLazyGrid(modifier = modifier, columns = GridCells.Fixed(columns)) { items( @@ -150,7 +150,7 @@ constructor( val state: TileUiState by tile.state .mapLatest { it.toUiState() } - .collectAsState(initial = tile.currentState.toUiState()) + .collectAsStateWithLifecycle(initialValue = tile.currentState.toUiState()) val context = LocalContext.current Expandable( @@ -201,10 +201,13 @@ constructor( val addTileToEnd: (TileSpec) -> Unit by rememberUpdatedState { onAddTile(it, POSITION_AT_END) } - val iconOnlySpecs by iconTilesInteractor.iconTilesSpecs.collectAsState(initial = emptySet()) + val iconOnlySpecs by + iconTilesInteractor.iconTilesSpecs.collectAsStateWithLifecycle( + initialValue = emptySet() + ) val isIconOnly: (TileSpec) -> Boolean = remember(iconOnlySpecs) { { tileSpec: TileSpec -> tileSpec in iconOnlySpecs } } - val columns by gridSizeInteractor.columns.collectAsState() + val columns by gridSizeInteractor.columns.collectAsStateWithLifecycle() TileLazyGrid(modifier = modifier, columns = GridCells.Fixed(columns)) { // These Text are just placeholders to see the different sections. Not final UI. diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileGrid.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileGrid.kt index 2f32d7264350..2dab7c3d61ca 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileGrid.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileGrid.kt @@ -17,15 +17,15 @@ package com.android.systemui.qs.panels.ui.compose import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.systemui.qs.panels.ui.viewmodel.TileGridViewModel @Composable fun TileGrid(viewModel: TileGridViewModel, modifier: Modifier = Modifier) { - val gridLayout by viewModel.gridLayout.collectAsState() - val tiles by viewModel.tileViewModels.collectAsState(emptyList()) + val gridLayout by viewModel.gridLayout.collectAsStateWithLifecycle() + val tiles by viewModel.tileViewModels.collectAsStateWithLifecycle(emptyList()) gridLayout.TileGrid(tiles, modifier) } diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt index 4e290e699ac4..6694878cea74 100644 --- a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt +++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt @@ -20,7 +20,6 @@ import android.app.IActivityManager import android.app.NotificationManager import android.content.Context import android.content.Intent -import android.content.pm.LauncherApps import android.content.res.Resources import android.net.Uri import android.os.Handler @@ -63,7 +62,6 @@ constructor( private val panelInteractor: PanelInteractor, private val issueRecordingState: IssueRecordingState, private val iActivityManager: IActivityManager, - private val launcherApps: LauncherApps, ) : RecordingService( controller, @@ -85,7 +83,7 @@ constructor( when (intent?.action) { ACTION_START -> { TraceUtils.traceStart( - contentResolver, + this, DEFAULT_TRACE_TAGS, DEFAULT_BUFFER_SIZE, DEFAULT_IS_INCLUDING_WINSCOPE, @@ -104,11 +102,7 @@ constructor( } ACTION_STOP, ACTION_STOP_NOTIF -> { - // ViewCapture needs to save it's data before it is disabled, or else the data will - // be lost. This is expected to change in the near future, and when that happens - // this line should be removed. - launcherApps.saveViewCaptureData() - TraceUtils.traceStop(contentResolver) + TraceUtils.traceStop(this) issueRecordingState.isRecording = false } ACTION_SHARE -> { @@ -142,7 +136,7 @@ constructor( private fun shareRecording(screenRecording: Uri?) { val traces = - TraceUtils.traceDump(contentResolver, TRACE_FILE_NAME).getOrElse { + TraceUtils.traceDump(this, TRACE_FILE_NAME).getOrElse { Log.v( TAG, "Traces were not present. This can happen if users double" + diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt index 4eca51d47a36..4ab09185fcdd 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt @@ -33,10 +33,11 @@ import android.view.WindowManager import android.view.WindowManagerGlobal import com.android.app.tracing.coroutines.launch import com.android.internal.infra.ServiceConnector -import com.android.systemui.Flags.screenshotActionDismissSystemWindows +import com.android.systemui.Flags import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.screenshot.proxy.SystemUiProxy import com.android.systemui.settings.DisplayTracker import com.android.systemui.shared.system.ActivityManagerWrapper import com.android.systemui.statusbar.phone.CentralSurfaces @@ -54,8 +55,8 @@ constructor( private val activityManagerWrapper: ActivityManagerWrapper, @Application private val applicationScope: CoroutineScope, @Main private val mainDispatcher: CoroutineDispatcher, + private val systemUiProxy: SystemUiProxy, private val displayTracker: DisplayTracker, - private val keyguardController: ScreenshotKeyguardController, ) { /** * Execute the given intent with startActivity while performing operations for screenshot action @@ -83,14 +84,12 @@ constructor( options: ActivityOptions?, transitionCoordinator: ExitTransitionCoordinator?, ) { - if (screenshotActionDismissSystemWindows()) { - keyguardController.dismiss() + if (Flags.fixScreenshotActionDismissSystemWindows()) { activityManagerWrapper.closeSystemWindows( CentralSurfaces.SYSTEM_DIALOG_REASON_SCREENSHOT ) - } else { - dismissKeyguard() } + systemUiProxy.dismissKeyguard() transitionCoordinator?.startExit() if (user == myUserHandle()) { @@ -110,27 +109,6 @@ constructor( } } - private val proxyConnector: ServiceConnector<IScreenshotProxy> = - ServiceConnector.Impl( - context, - Intent(context, ScreenshotProxyService::class.java), - Context.BIND_AUTO_CREATE or Context.BIND_WAIVE_PRIORITY or Context.BIND_NOT_VISIBLE, - context.userId, - IScreenshotProxy.Stub::asInterface, - ) - - private suspend fun dismissKeyguard() { - val completion = CompletableDeferred<Unit>() - val onDoneBinder = - object : IOnDoneCallback.Stub() { - override fun onDone(success: Boolean) { - completion.complete(Unit) - } - } - proxyConnector.post { it.dismissKeyguard(onDoneBinder) } - completion.await() - } - private fun getCrossProfileConnector(user: UserHandle): ServiceConnector<ICrossProfileService> = ServiceConnector.Impl<ICrossProfileService>( context, diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotKeyguardController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotKeyguardController.kt deleted file mode 100644 index 7696bbe3763e..000000000000 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotKeyguardController.kt +++ /dev/null @@ -1,46 +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.screenshot - -import android.content.Context -import android.content.Intent -import com.android.internal.infra.ServiceConnector -import javax.inject.Inject -import kotlinx.coroutines.CompletableDeferred - -open class ScreenshotKeyguardController @Inject constructor(context: Context) { - private val proxyConnector: ServiceConnector<IScreenshotProxy> = - ServiceConnector.Impl( - context, - Intent(context, ScreenshotProxyService::class.java), - Context.BIND_AUTO_CREATE or Context.BIND_WAIVE_PRIORITY or Context.BIND_NOT_VISIBLE, - context.userId, - IScreenshotProxy.Stub::asInterface - ) - - suspend fun dismiss() { - val completion = CompletableDeferred<Unit>() - val onDoneBinder = - object : IOnDoneCallback.Stub() { - override fun onDone(success: Boolean) { - completion.complete(Unit) - } - } - proxyConnector.post { it.dismissKeyguard(onDoneBinder) } - completion.await() - } -} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt index bd932260848b..969cf482be90 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt @@ -22,6 +22,8 @@ import android.graphics.Insets import android.graphics.Rect import android.graphics.Region import android.util.AttributeSet +import android.view.GestureDetector +import android.view.GestureDetector.SimpleOnGestureListener import android.view.MotionEvent import android.view.View import android.view.ViewGroup @@ -43,13 +45,43 @@ class ScreenshotShelfView(context: Context, attrs: AttributeSet? = null) : private val displayMetrics = context.resources.displayMetrics private val tmpRect = Rect() private lateinit var actionsContainerBackground: View + private lateinit var actionsContainer: View private lateinit var dismissButton: View + // Prepare an internal `GestureDetector` to determine when we can initiate a touch-interception + // session (with the client's provided `onTouchInterceptListener`). We delegate out to their + // listener only for gestures that can't be handled by scrolling our `actionsContainer`. + private val gestureDetector = + GestureDetector( + context, + object : SimpleOnGestureListener() { + override fun onScroll( + ev1: MotionEvent?, + ev2: MotionEvent, + distanceX: Float, + distanceY: Float + ): Boolean { + actionsContainer.getBoundsOnScreen(tmpRect) + val touchedInActionsContainer = + tmpRect.contains(ev2.rawX.toInt(), ev2.rawY.toInt()) + val canHandleInternallyByScrolling = + touchedInActionsContainer + && actionsContainer.canScrollHorizontally(distanceX.toInt()) + return !canHandleInternallyByScrolling + } + } + ) + init { - setOnTouchListener({ _: View, _: MotionEvent -> + + // Delegate to the client-provided `onTouchInterceptListener` if we've already initiated + // touch-interception. + setOnTouchListener({ _: View, ev: MotionEvent -> userInteractionCallback?.invoke() - true + onTouchInterceptListener?.invoke(ev) ?: false }) + + gestureDetector.setIsLongpressEnabled(false) } override fun onFinishInflate() { @@ -60,7 +92,15 @@ class ScreenshotShelfView(context: Context, attrs: AttributeSet? = null) : blurredScreenshotPreview = requireViewById(R.id.screenshot_preview_blur) screenshotStatic = requireViewById(R.id.screenshot_static) actionsContainerBackground = requireViewById(R.id.actions_container_background) + actionsContainer = requireViewById(R.id.actions_container) dismissButton = requireViewById(R.id.screenshot_dismiss_button) + + // Configure to extend the timeout during ongoing gestures (i.e. scrolls) that are already + // being handled by our child views. + actionsContainer.setOnTouchListener({ _: View, ev: MotionEvent -> + userInteractionCallback?.invoke() + false + }) } fun getTouchRegion(gestureInsets: Insets): Region { @@ -171,9 +211,16 @@ class ScreenshotShelfView(context: Context, attrs: AttributeSet? = null) : override fun onInterceptTouchEvent(ev: MotionEvent): Boolean { userInteractionCallback?.invoke() - if (onTouchInterceptListener?.invoke(ev) == true) { - return true + // Let the client-provided listener see all `DOWN` events so that they'll be able to + // interpret the remainder of the gesture, even if interception starts partway-through. + // TODO: is this really necessary? And if we don't go on to start interception, should we + // follow up with `ACTION_CANCEL`? + if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) { + onTouchInterceptListener?.invoke(ev) } - return super.onInterceptTouchEvent(ev) + + // Only allow the client-provided touch interceptor to take over the gesture if our + // top-level `GestureDetector` decides not to scroll the action container. + return gestureDetector.onTouchEvent(ev) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java index d7d373226d5c..5bf2f41ae453 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java @@ -15,6 +15,8 @@ */ package com.android.systemui.statusbar; +import static com.android.systemui.Flags.mediaControlsUserInitiatedDismiss; + import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Notification; @@ -175,14 +177,18 @@ public class NotificationMediaManager implements Dumpable { } @Override - public void onMediaDataRemoved(@NonNull String key) { + public void onMediaDataRemoved(@NonNull String key, boolean userInitiated) { + if (mediaControlsUserInitiatedDismiss() && !userInitiated) { + // Dismissing the notification will send the app's deleteIntent, so ignore if + // this was an automatic removal + Log.d(TAG, "Not dismissing " + key + " because it was removed by the system"); + return; + } mNotifPipeline.getAllNotifs() .stream() .filter(entry -> Objects.equals(entry.getKey(), key)) .findAny() .ifPresent(entry -> { - // TODO(b/160713608): "removing" this notification won't happen and - // won't send the 'deleteIntent' if the notification is ongoing. mNotifCollection.dismissNotification(entry, getDismissedByUserStats(entry)); }); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt index 1a223c110ad5..933eb207e76d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt @@ -18,12 +18,10 @@ package com.android.systemui.statusbar.notification.collection.coordinator -import android.os.SystemProperties import android.os.UserHandle import android.provider.Settings import androidx.annotation.VisibleForTesting import com.android.systemui.Dumpable -import com.android.systemui.Flags.notificationMinimalismPrototype import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dump.DumpManager @@ -41,6 +39,7 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.No import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider +import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype import com.android.systemui.statusbar.policy.HeadsUpManager import com.android.systemui.statusbar.policy.headsUpEvents import com.android.systemui.util.asIndenting @@ -264,7 +263,7 @@ constructor( } private fun unseenFeatureEnabled(): Flow<Boolean> { - if (notificationMinimalismPrototype()) { + if (NotificationMinimalismPrototype.V1.isEnabled) { return flowOf(true) } return secureSettings @@ -342,18 +341,6 @@ constructor( var hasFilteredAnyNotifs = false /** - * the [notificationMinimalismPrototype] will now show seen notifications on the locked - * shade by default, but this property read allows that to be quickly disabled for - * testing - */ - private val minimalismShowOnLockedShade - get() = - SystemProperties.getBoolean( - "persist.notification_minimalism_prototype.show_on_locked_shade", - true - ) - - /** * Encapsulates a definition of "being on the keyguard". Note that these two definitions * are wildly different: [StatusBarState.KEYGUARD] is when on the lock screen and does * not include shade or occluded states, whereas [KeyguardRepository.isKeyguardShowing] @@ -364,7 +351,10 @@ constructor( * allow seen notifications to appear in the locked shade. */ private fun isOnKeyguard(): Boolean = - if (notificationMinimalismPrototype() && minimalismShowOnLockedShade) { + if ( + NotificationMinimalismPrototype.V1.isEnabled && + NotificationMinimalismPrototype.V1.showOnLockedShade + ) { statusBarStateController.state == StatusBarState.KEYGUARD } else { keyguardRepository.isKeyguardShowing() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationMinimalismPrototype.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationMinimalismPrototype.kt new file mode 100644 index 000000000000..8889a10f5ad1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationMinimalismPrototype.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.statusbar.notification.shared + +import android.os.SystemProperties +import com.android.systemui.Flags +import com.android.systemui.flags.FlagToken +import com.android.systemui.flags.RefactorFlagUtils + +/** Helper for reading or using the minimalism prototype flag state. */ +@Suppress("NOTHING_TO_INLINE") +object NotificationMinimalismPrototype { + object V1 { + /** The aconfig flag name */ + const val FLAG_NAME = Flags.FLAG_NOTIFICATION_MINIMALISM_PROTOTYPE + + /** A token used for dependency declaration */ + val token: FlagToken + get() = FlagToken(FLAG_NAME, isEnabled) + + /** Is the heads-up cycling animation enabled */ + @JvmStatic + inline val isEnabled + get() = Flags.notificationMinimalismPrototype() + + /** + * the prototype will now show seen notifications on the locked shade by default, but this + * property read allows that to be quickly disabled for testing + */ + val showOnLockedShade: Boolean + get() = + if (isUnexpectedlyInLegacyMode()) false + else + SystemProperties.getBoolean( + "persist.notification_minimalism_prototype.show_on_locked_shade", + true + ) + + /** gets the configurable max number of notifications */ + val maxNotifs: Int + get() = + if (isUnexpectedlyInLegacyMode()) -1 + else + SystemProperties.getInt( + "persist.notification_minimalism_prototype.lock_screen_max_notifs", + 1 + ) + + /** + * Called to ensure code is only run when the flag is enabled. This protects users from the + * unintended behaviors caused by accidentally running new logic, while also crashing on an + * eng build to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun isUnexpectedlyInLegacyMode() = + RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME) + + /** + * Called to ensure code is only run when the flag is disabled. This will throw an exception + * if the flag is enabled to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt index 5bd4c758d678..a44674542b5c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt @@ -17,11 +17,9 @@ package com.android.systemui.statusbar.notification.stack import android.content.res.Resources -import android.os.SystemProperties import android.util.Log import android.view.View.GONE import androidx.annotation.VisibleForTesting -import com.android.systemui.Flags.notificationMinimalismPrototype import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.media.controls.domain.pipeline.MediaDataManager @@ -31,6 +29,7 @@ import com.android.systemui.statusbar.StatusBarState.KEYGUARD import com.android.systemui.statusbar.SysuiStatusBarStateController import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.ExpandableView +import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype import com.android.systemui.statusbar.policy.SplitShadeStateController import com.android.systemui.util.Compile import com.android.systemui.util.children @@ -381,16 +380,13 @@ constructor( fun updateResources() { maxKeyguardNotifications = infiniteIfNegative( - if (notificationMinimalismPrototype()) { - SystemProperties.getInt( - "persist.notification_minimalism_prototype.lock_screen_max_notifs", - 1 - ) + if (NotificationMinimalismPrototype.V1.isEnabled) { + NotificationMinimalismPrototype.V1.maxNotifs } else { resources.getInteger(R.integer.keyguard_max_notification_count) } ) - maxNotificationsExcludesMedia = notificationMinimalismPrototype() + maxNotificationsExcludesMedia = NotificationMinimalismPrototype.V1.isEnabled dividerHeight = max(1f, resources.getDimensionPixelSize(R.dimen.notification_divider_height).toFloat()) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt index dacafc48fc72..db544ce59aa1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt @@ -38,16 +38,6 @@ class NotificationPlaceholderRepository @Inject constructor() { */ val shadeScrimBounds = MutableStateFlow<ShadeScrimBounds?>(null) - /** - * The y-coordinate in px of top of the contents of the notification stack. This value can be - * negative, if the stack is scrolled such that its top extends beyond the top edge of the - * screen. - */ - val stackTop = MutableStateFlow(0f) - - /** the bottom-most acceptable y-position for the bottom of the stack / shelf */ - val stackBottom = MutableStateFlow(0f) - /** the y position of the top of the HUN area */ val headsUpTop = MutableStateFlow(0f) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt index 365ead699c65..e7acbe3ab0b0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt @@ -72,12 +72,6 @@ constructor( val alphaForBrightnessMirror: StateFlow<Float> = placeholderRepository.alphaForBrightnessMirror.asStateFlow() - /** The y-coordinate in px of top of the contents of the notification stack. */ - val stackTop: StateFlow<Float> = placeholderRepository.stackTop.asStateFlow() - - /** The y-coordinate in px of bottom of the contents of the notification stack. */ - val stackBottom: StateFlow<Float> = placeholderRepository.stackBottom.asStateFlow() - /** The height of the keyguard's available space bounds */ val constrainedAvailableSpace: StateFlow<Int> = placeholderRepository.constrainedAvailableSpace.asStateFlow() @@ -121,16 +115,6 @@ constructor( viewHeightRepository.headsUpHeight.value = height } - /** Sets the y-coord in px of the top of the contents of the notification stack. */ - fun setStackTop(stackTop: Float) { - placeholderRepository.stackTop.value = stackTop - } - - /** Sets the y-coord in px of the bottom of the contents of the notification stack. */ - fun setStackBottom(stackBottom: Float) { - placeholderRepository.stackBottom.value = stackBottom - } - /** Sets whether the notification stack is scrolled to the top. */ fun setScrolledToTop(scrolledToTop: Boolean) { placeholderRepository.scrolledToTop.value = scrolledToTop 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 3c44713a1df5..622d8e7b2307 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 @@ -79,8 +79,6 @@ constructor( } launch { viewModel.maxAlpha.collect { view.setMaxAlpha(it) } } - launch { viewModel.stackTop.collect { view.setStackTop(it) } } - launch { viewModel.stackBottom.collect { view.setStackBottom(it) } } launch { viewModel.scrolledToTop.collect { view.setScrolledToTop(it) } } launch { viewModel.headsUpTop.collect { view.setHeadsUpTop(it) } } launch { viewModel.expandFraction.collect { view.setExpandFraction(it.coerceIn(0f, 1f)) } } 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 082f6b6f11aa..61373815db1c 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 @@ -130,10 +130,6 @@ constructor( val maxAlpha: Flow<Float> = stackAppearanceInteractor.alphaForBrightnessMirror.dumpValue("maxAlpha") - /** The y-coordinate in px of top of the contents of the notification stack. */ - val stackTop: Flow<Float> = stackAppearanceInteractor.stackTop.dumpValue("stackTop") - /** The y-coordinate in px of bottom of the contents of the notification stack. */ - val stackBottom: Flow<Float> = stackAppearanceInteractor.stackBottom.dumpValue("stackBottom") /** * Whether the notification stack is scrolled to the top; i.e., it cannot be scrolled down any * further. 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 736058ac3a78..97b86e3371f5 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,7 +16,6 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel -import com.android.systemui.common.shared.model.NotificationContainerBounds import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FeatureFlagsClassic @@ -57,18 +56,6 @@ constructor( interactor.setShadeScrimBounds(bounds) } - /** Notifies that the bounds of the notification placeholder have changed. */ - fun onStackBoundsChanged( - top: Float, - bottom: Float, - ) { - keyguardInteractor.setNotificationContainerBounds( - NotificationContainerBounds(top = top, bottom = bottom) - ) - interactor.setStackTop(top) - interactor.setStackBottom(bottom) - } - /** Sets the available space */ fun onConstrainedAvailableSpaceChanged(height: Int) { interactor.setConstrainedAvailableSpace(height) diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt index 46ce5f2623de..1ec86a4d1dfc 100644 --- a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt @@ -97,3 +97,12 @@ fun <T> collectFlow( fun <A, B, R> combineFlows(flow1: Flow<A>, flow2: Flow<B>, bifunction: (A, B) -> R): Flow<R> { return combine(flow1, flow2, bifunction) } + +fun <A, B, C, R> combineFlows( + flow1: Flow<A>, + flow2: Flow<B>, + flow3: Flow<C>, + trifunction: (A, B, C) -> R +): Flow<R> { + return combine(flow1, flow2, flow3, trifunction) +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java index bc6c459881be..5361cef2ac64 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java @@ -35,9 +35,13 @@ import androidx.test.filters.SmallTest; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.SysuiTestCase; import com.android.systemui.communal.domain.interactor.CommunalInteractor; +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor; import com.android.systemui.dock.DockManager; import com.android.systemui.dock.DockManagerFake; +import com.android.systemui.flags.DisableSceneContainer; +import com.android.systemui.flags.EnableSceneContainer; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor; import com.android.systemui.shade.domain.interactor.ShadeInteractor; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; @@ -50,6 +54,7 @@ import com.android.systemui.util.sensors.ProximitySensor; import com.android.systemui.util.sensors.ThresholdSensor; import com.android.systemui.util.time.FakeSystemClock; +import kotlinx.coroutines.flow.MutableStateFlow; import kotlinx.coroutines.flow.StateFlowKt; import org.junit.Before; @@ -89,6 +94,14 @@ public class FalsingCollectorImplTest extends SysuiTestCase { private SelectedUserInteractor mSelectedUserInteractor; @Mock private CommunalInteractor mCommunalInteractor; + @Mock + private DeviceEntryInteractor mDeviceEntryInteractor; + private final MutableStateFlow<Boolean> mIsDeviceEntered = + StateFlowKt.MutableStateFlow(false); + @Mock + private SceneContainerOcclusionInteractor mSceneContainerOcclusionInteractor; + private final MutableStateFlow<Boolean> mIsInvisibleDueToOcclusion = + StateFlowKt.MutableStateFlow(false); private final DockManagerFake mDockManager = new DockManagerFake(); private final FakeSystemClock mFakeSystemClock = new FakeSystemClock(); private final FakeExecutor mFakeExecutor = new FakeExecutor(mFakeSystemClock); @@ -99,15 +112,21 @@ public class FalsingCollectorImplTest extends SysuiTestCase { when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD); when(mKeyguardStateController.isShowing()).thenReturn(true); + when(mKeyguardStateController.isOccluded()).thenReturn(false); when(mShadeInteractor.isQsExpanded()).thenReturn(StateFlowKt.MutableStateFlow(false)); + when(mDeviceEntryInteractor.isDeviceEntered()).thenReturn(mIsDeviceEntered); + when(mSceneContainerOcclusionInteractor.getInvisibleDueToOcclusion()).thenReturn( + mIsInvisibleDueToOcclusion); + mFalsingCollector = new FalsingCollectorImpl(mFalsingDataProvider, mFalsingManager, mKeyguardUpdateMonitor, mHistoryTracker, mProximitySensor, mStatusBarStateController, mKeyguardStateController, () -> mShadeInteractor, mBatteryController, mDockManager, mFakeExecutor, mJavaAdapter, mFakeSystemClock, () -> mSelectedUserInteractor, - () -> mCommunalInteractor + () -> mCommunalInteractor, () -> mDeviceEntryInteractor, + () -> mSceneContainerOcclusionInteractor ); mFalsingCollector.init(); } @@ -189,7 +208,8 @@ public class FalsingCollectorImplTest extends SysuiTestCase { } @Test - public void testRegisterSensor_OccludingActivity() { + @DisableSceneContainer + public void testRegisterSensor_OccludingActivity_sceneContainerDisabled() { when(mKeyguardStateController.isOccluded()).thenReturn(true); ArgumentCaptor<StatusBarStateController.StateListener> stateListenerArgumentCaptor = @@ -203,6 +223,21 @@ public class FalsingCollectorImplTest extends SysuiTestCase { } @Test + @EnableSceneContainer + public void testRegisterSensor_OccludingActivity_sceneContainerEnabled() { + mIsInvisibleDueToOcclusion.setValue(true); + + ArgumentCaptor<StatusBarStateController.StateListener> stateListenerArgumentCaptor = + ArgumentCaptor.forClass(StatusBarStateController.StateListener.class); + verify(mStatusBarStateController).addCallback(stateListenerArgumentCaptor.capture()); + + mFalsingCollector.onScreenTurningOn(); + reset(mProximitySensor); + stateListenerArgumentCaptor.getValue().onStateChanged(StatusBarState.SHADE); + verify(mProximitySensor).register(any(ThresholdSensor.Listener.class)); + } + + @Test public void testPassThroughEnterKeyEvent() { KeyEvent enterDown = KeyEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0, 0, 0, 0, 0, 0, ""); @@ -280,7 +315,8 @@ public class FalsingCollectorImplTest extends SysuiTestCase { } @Test - public void testAvoidUnlocked() { + @DisableSceneContainer + public void testAvoidUnlocked_sceneContainerDisabled() { MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0); MotionEvent up = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0); @@ -296,6 +332,23 @@ public class FalsingCollectorImplTest extends SysuiTestCase { } @Test + @EnableSceneContainer + public void testAvoidUnlocked_sceneContainerEnabled() { + MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0); + MotionEvent up = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0); + + mIsDeviceEntered.setValue(true); + + // Nothing passed initially + mFalsingCollector.onTouchEvent(down); + verify(mFalsingDataProvider, never()).onMotionEvent(any(MotionEvent.class)); + + // Up event would normally flush the up event, but doesn't. + mFalsingCollector.onTouchEvent(up); + verify(mFalsingDataProvider, never()).onMotionEvent(any(MotionEvent.class)); + } + + @Test public void testGestureWhenDozing() { // We check the FalsingManager for taps during the transition to AoD (dozing=true, // pulsing=false), so the FalsingCollector needs to continue to analyze events that occur diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImplTest.kt index e56a25345436..265ade3fac0f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImplTest.kt @@ -172,20 +172,20 @@ class LegacyMediaDataFilterImplTest : SysuiTestCase() { fun testOnRemovedForCurrent_callsListener() { // GIVEN a media was removed for main user mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain) - mediaDataFilter.onMediaDataRemoved(KEY) + mediaDataFilter.onMediaDataRemoved(KEY, false) // THEN we should tell the listener - verify(listener).onMediaDataRemoved(eq(KEY)) + verify(listener).onMediaDataRemoved(eq(KEY), eq(false)) } @Test fun testOnRemovedForGuest_doesNotCallListener() { // GIVEN a media was removed for guest user mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest) - mediaDataFilter.onMediaDataRemoved(KEY) + mediaDataFilter.onMediaDataRemoved(KEY, false) // THEN we should NOT tell the listener - verify(listener, never()).onMediaDataRemoved(eq(KEY)) + verify(listener, never()).onMediaDataRemoved(eq(KEY), anyBoolean()) } @Test @@ -197,7 +197,7 @@ class LegacyMediaDataFilterImplTest : SysuiTestCase() { setUser(USER_GUEST) // THEN we should remove the main user's media - verify(listener).onMediaDataRemoved(eq(KEY)) + verify(listener).onMediaDataRemoved(eq(KEY), eq(false)) } @Test @@ -230,7 +230,7 @@ class LegacyMediaDataFilterImplTest : SysuiTestCase() { setPrivateProfileUnavailable() // THEN we should add the private profile media - verify(listener).onMediaDataRemoved(eq(KEY_ALT)) + verify(listener).onMediaDataRemoved(eq(KEY_ALT), eq(false)) } @Test @@ -360,7 +360,7 @@ class LegacyMediaDataFilterImplTest : SysuiTestCase() { @Test fun testOnNotificationRemoved_doesntHaveMedia() { mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = dataMain) - mediaDataFilter.onMediaDataRemoved(KEY) + mediaDataFilter.onMediaDataRemoved(KEY, false) assertThat(mediaDataFilter.hasAnyMediaOrRecommendation()).isFalse() assertThat(mediaDataFilter.hasAnyMedia()).isFalse() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt index 5a2d22d0d503..99bf2db2ff98 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt @@ -346,7 +346,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() { // THEN it is removed and listeners are informed foregroundExecutor.advanceClockToLast() foregroundExecutor.runAllReady() - verify(listener).onMediaDataRemoved(PACKAGE_NAME) + verify(listener).onMediaDataRemoved(PACKAGE_NAME, false) } @Test @@ -532,7 +532,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() { addNotificationAndLoad() val data = mediaDataCaptor.value mediaDataManager.onNotificationRemoved(KEY) - verify(listener).onMediaDataRemoved(eq(KEY)) + verify(listener).onMediaDataRemoved(eq(KEY), eq(false)) verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId)) } @@ -777,7 +777,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() { eq(false) ) assertThat(mediaDataCaptor.value.resumption).isTrue() - verify(listener, never()).onMediaDataRemoved(eq(KEY)) + verify(listener, never()).onMediaDataRemoved(eq(KEY), eq(false)) // WHEN the second is removed mediaDataManager.onNotificationRemoved(KEY_2) // THEN the data is for resumption and the second key is removed @@ -791,7 +791,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() { eq(false) ) assertThat(mediaDataCaptor.value.resumption).isTrue() - verify(listener).onMediaDataRemoved(eq(KEY_2)) + verify(listener).onMediaDataRemoved(eq(KEY_2), eq(false)) } @Test @@ -816,7 +816,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() { mediaDataManager.onNotificationRemoved(KEY) // THEN the media data is removed - verify(listener).onMediaDataRemoved(eq(KEY)) + verify(listener).onMediaDataRemoved(eq(KEY), eq(false)) } @Test @@ -866,7 +866,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() { mediaDataManager.onNotificationRemoved(KEY) // THEN the media data is removed - verify(listener).onMediaDataRemoved(eq(KEY)) + verify(listener).onMediaDataRemoved(eq(KEY), eq(false)) } @Test @@ -905,7 +905,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() { assertThat(mediaDataCaptor.value.isPlaying).isFalse() // And the oldest resume control was removed - verify(listener).onMediaDataRemoved(eq("0:$PACKAGE_NAME")) + verify(listener).onMediaDataRemoved(eq("0:$PACKAGE_NAME"), eq(false)) } fun testOnNotificationRemoved_lockDownMode() { @@ -915,7 +915,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() { val data = mediaDataCaptor.value mediaDataManager.onNotificationRemoved(KEY) - verify(listener, never()).onMediaDataRemoved(eq(KEY)) + verify(listener, never()).onMediaDataRemoved(eq(KEY), anyBoolean()) verify(logger, never()) .logActiveConvertedToResume(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId)) verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId)) @@ -1148,7 +1148,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() { mediaDataManager.setMediaResumptionEnabled(false) // THEN the resume controls are dismissed - verify(listener).onMediaDataRemoved(eq(PACKAGE_NAME)) + verify(listener).onMediaDataRemoved(eq(PACKAGE_NAME), eq(false)) verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId)) } @@ -1156,19 +1156,19 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() { fun testDismissMedia_listenerCalled() { addNotificationAndLoad() val data = mediaDataCaptor.value - val removed = mediaDataManager.dismissMediaData(KEY, 0L) + val removed = mediaDataManager.dismissMediaData(KEY, 0L, true) assertThat(removed).isTrue() foregroundExecutor.advanceClockToLast() foregroundExecutor.runAllReady() - verify(listener).onMediaDataRemoved(eq(KEY)) + verify(listener).onMediaDataRemoved(eq(KEY), eq(true)) verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId)) } @Test fun testDismissMedia_keyDoesNotExist_returnsFalse() { - val removed = mediaDataManager.dismissMediaData(KEY, 0L) + val removed = mediaDataManager.dismissMediaData(KEY, 0L, true) assertThat(removed).isFalse() } @@ -2077,7 +2077,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() { sessionCallbackCaptor.value.invoke(KEY) // It remains as a regular player - verify(listener, never()).onMediaDataRemoved(eq(KEY)) + verify(listener, never()).onMediaDataRemoved(eq(KEY), anyBoolean()) verify(listener, never()) .onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean()) } @@ -2093,7 +2093,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() { mediaDataManager.onNotificationRemoved(KEY) // It is fully removed - verify(listener).onMediaDataRemoved(eq(KEY)) + verify(listener).onMediaDataRemoved(eq(KEY), eq(false)) verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId)) verify(listener, never()) .onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean()) @@ -2146,7 +2146,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() { mediaDataManager.onNotificationRemoved(KEY) // It remains as a regular player - verify(listener, never()).onMediaDataRemoved(eq(KEY)) + verify(listener, never()).onMediaDataRemoved(eq(KEY), anyBoolean()) verify(listener, never()) .onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean()) } @@ -2199,7 +2199,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() { sessionCallbackCaptor.value.invoke(KEY) // It is fully removed - verify(listener).onMediaDataRemoved(eq(KEY)) + verify(listener).onMediaDataRemoved(eq(KEY), eq(false)) verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId)) verify(listener, never()) .onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean()) @@ -2253,7 +2253,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() { sessionCallbackCaptor.value.invoke(KEY) // It is fully removed. - verify(listener).onMediaDataRemoved(eq(KEY)) + verify(listener).onMediaDataRemoved(eq(KEY), eq(false)) verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId)) verify(listener, never()) .onMediaDataLoaded( @@ -2279,7 +2279,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() { sessionCallbackCaptor.value.invoke(KEY) // It is fully removed - verify(listener).onMediaDataRemoved(eq(KEY)) + verify(listener).onMediaDataRemoved(eq(KEY), eq(false)) verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId)) verify(listener, never()) .onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean()) @@ -2329,7 +2329,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() { mediaDataManager.onNotificationRemoved(KEY) // We still make sure to remove it - verify(listener).onMediaDataRemoved(eq(KEY)) + verify(listener).onMediaDataRemoved(eq(KEY), eq(false)) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatestTest.java index bb5b57287a9a..dd05a0d12429 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatestTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatestTest.java @@ -202,24 +202,24 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { @Test public void mediaDataRemoved() { // WHEN media data is removed without first receiving device or data - mManager.onMediaDataRemoved(KEY); + mManager.onMediaDataRemoved(KEY, false); // THEN a removed event isn't emitted - verify(mListener, never()).onMediaDataRemoved(eq(KEY)); + verify(mListener, never()).onMediaDataRemoved(eq(KEY), anyBoolean()); } @Test public void mediaDataRemovedAfterMediaEvent() { mManager.onMediaDataLoaded(KEY, null, mMediaData, true /* immediately */, 0 /* receivedSmartspaceCardLatency */, false /* isSsReactivated */); - mManager.onMediaDataRemoved(KEY); - verify(mListener).onMediaDataRemoved(eq(KEY)); + mManager.onMediaDataRemoved(KEY, false); + verify(mListener).onMediaDataRemoved(eq(KEY), eq(false)); } @Test public void mediaDataRemovedAfterDeviceEvent() { mManager.onMediaDeviceChanged(KEY, null, mDeviceData); - mManager.onMediaDataRemoved(KEY); - verify(mListener).onMediaDataRemoved(eq(KEY)); + mManager.onMediaDataRemoved(KEY, false); + verify(mListener).onMediaDataRemoved(eq(KEY), eq(false)); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt index 77ad263ed64b..35eefd91bc8e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt @@ -205,9 +205,9 @@ class MediaDataFilterImplTest : SysuiTestCase() { assertThat(currentMedia).containsExactly(mediaCommonModel) - mediaDataFilter.onMediaDataRemoved(KEY) + mediaDataFilter.onMediaDataRemoved(KEY, false) - verify(listener).onMediaDataRemoved(eq(KEY)) + verify(listener).onMediaDataRemoved(eq(KEY), eq(false)) assertThat(currentMedia).doesNotContain(mediaCommonModel) } @@ -218,9 +218,9 @@ class MediaDataFilterImplTest : SysuiTestCase() { // GIVEN a media was removed for guest user mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest) - mediaDataFilter.onMediaDataRemoved(KEY) + mediaDataFilter.onMediaDataRemoved(KEY, false) - verify(listener, never()).onMediaDataRemoved(eq(KEY)) + verify(listener, never()).onMediaDataRemoved(eq(KEY), eq(false)) assertThat(currentMedia).isEmpty() } @@ -239,7 +239,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { setUser(USER_GUEST) // THEN we should remove the main user's media - verify(listener).onMediaDataRemoved(eq(KEY)) + verify(listener).onMediaDataRemoved(eq(KEY), eq(false)) assertThat(currentMedia).isEmpty() } @@ -291,7 +291,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { val mediaLoadedStatesModel = MediaDataLoadingModel.Loaded(dataMain.instanceId) // THEN we should remove the private profile media - verify(listener).onMediaDataRemoved(eq(KEY_ALT)) + verify(listener).onMediaDataRemoved(eq(KEY_ALT), eq(false)) assertThat(currentMedia) .containsExactly(MediaCommonModel.MediaControl(mediaLoadedStatesModel)) } @@ -502,7 +502,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData) mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = dataMain) - mediaDataFilter.onMediaDataRemoved(KEY) + mediaDataFilter.onMediaDataRemoved(KEY, false) assertThat(hasAnyMediaOrRecommendation(selectedUserEntries, smartspaceMediaData)) .isFalse() assertThat(hasAnyMedia(selectedUserEntries)).isFalse() diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt index 1de7ee339c3e..5791826ab1f9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt @@ -384,7 +384,7 @@ class MediaDataProcessorTest : SysuiTestCase() { // THEN it is removed and listeners are informed foregroundExecutor.advanceClockToLast() foregroundExecutor.runAllReady() - verify(listener).onMediaDataRemoved(PACKAGE_NAME) + verify(listener).onMediaDataRemoved(PACKAGE_NAME, false) } @Test @@ -567,7 +567,7 @@ class MediaDataProcessorTest : SysuiTestCase() { addNotificationAndLoad() val data = mediaDataCaptor.value mediaDataProcessor.onNotificationRemoved(KEY) - verify(listener).onMediaDataRemoved(eq(KEY)) + verify(listener).onMediaDataRemoved(eq(KEY), eq(false)) verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId)) } @@ -812,7 +812,7 @@ class MediaDataProcessorTest : SysuiTestCase() { eq(false) ) assertThat(mediaDataCaptor.value.resumption).isTrue() - verify(listener, never()).onMediaDataRemoved(eq(KEY)) + verify(listener, never()).onMediaDataRemoved(eq(KEY), anyBoolean()) // WHEN the second is removed mediaDataProcessor.onNotificationRemoved(KEY_2) // THEN the data is for resumption and the second key is removed @@ -826,7 +826,7 @@ class MediaDataProcessorTest : SysuiTestCase() { eq(false) ) assertThat(mediaDataCaptor.value.resumption).isTrue() - verify(listener).onMediaDataRemoved(eq(KEY_2)) + verify(listener).onMediaDataRemoved(eq(KEY_2), eq(false)) } @Test @@ -851,7 +851,7 @@ class MediaDataProcessorTest : SysuiTestCase() { mediaDataProcessor.onNotificationRemoved(KEY) // THEN the media data is removed - verify(listener).onMediaDataRemoved(eq(KEY)) + verify(listener).onMediaDataRemoved(eq(KEY), eq(false)) } @Test @@ -901,7 +901,7 @@ class MediaDataProcessorTest : SysuiTestCase() { mediaDataProcessor.onNotificationRemoved(KEY) // THEN the media data is removed - verify(listener).onMediaDataRemoved(eq(KEY)) + verify(listener).onMediaDataRemoved(eq(KEY), eq(false)) } @Test @@ -940,7 +940,7 @@ class MediaDataProcessorTest : SysuiTestCase() { assertThat(mediaDataCaptor.value.isPlaying).isFalse() // And the oldest resume control was removed - verify(listener).onMediaDataRemoved(eq("0:$PACKAGE_NAME")) + verify(listener).onMediaDataRemoved(eq("0:$PACKAGE_NAME"), eq(false)) } fun testOnNotificationRemoved_lockDownMode() { @@ -950,7 +950,7 @@ class MediaDataProcessorTest : SysuiTestCase() { val data = mediaDataCaptor.value mediaDataProcessor.onNotificationRemoved(KEY) - verify(listener, never()).onMediaDataRemoved(eq(KEY)) + verify(listener, never()).onMediaDataRemoved(eq(KEY), eq(false)) verify(logger, never()) .logActiveConvertedToResume(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId)) verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId)) @@ -1183,7 +1183,7 @@ class MediaDataProcessorTest : SysuiTestCase() { mediaDataProcessor.setMediaResumptionEnabled(false) // THEN the resume controls are dismissed - verify(listener).onMediaDataRemoved(eq(PACKAGE_NAME)) + verify(listener).onMediaDataRemoved(eq(PACKAGE_NAME), eq(false)) verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId)) } @@ -1191,19 +1191,19 @@ class MediaDataProcessorTest : SysuiTestCase() { fun testDismissMedia_listenerCalled() { addNotificationAndLoad() val data = mediaDataCaptor.value - val removed = mediaDataProcessor.dismissMediaData(KEY, 0L) + val removed = mediaDataProcessor.dismissMediaData(KEY, 0L, true) assertThat(removed).isTrue() foregroundExecutor.advanceClockToLast() foregroundExecutor.runAllReady() - verify(listener).onMediaDataRemoved(eq(KEY)) + verify(listener).onMediaDataRemoved(eq(KEY), eq(true)) verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId)) } @Test fun testDismissMedia_keyDoesNotExist_returnsFalse() { - val removed = mediaDataProcessor.dismissMediaData(KEY, 0L) + val removed = mediaDataProcessor.dismissMediaData(KEY, 0L, true) assertThat(removed).isFalse() } @@ -2102,7 +2102,7 @@ class MediaDataProcessorTest : SysuiTestCase() { sessionCallbackCaptor.value.invoke(KEY) // It remains as a regular player - verify(listener, never()).onMediaDataRemoved(eq(KEY)) + verify(listener, never()).onMediaDataRemoved(eq(KEY), anyBoolean()) verify(listener, never()) .onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean()) } @@ -2118,7 +2118,7 @@ class MediaDataProcessorTest : SysuiTestCase() { mediaDataProcessor.onNotificationRemoved(KEY) // It is fully removed - verify(listener).onMediaDataRemoved(eq(KEY)) + verify(listener).onMediaDataRemoved(eq(KEY), eq(false)) verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId)) verify(listener, never()) .onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean()) @@ -2171,7 +2171,7 @@ class MediaDataProcessorTest : SysuiTestCase() { mediaDataProcessor.onNotificationRemoved(KEY) // It remains as a regular player - verify(listener, never()).onMediaDataRemoved(eq(KEY)) + verify(listener, never()).onMediaDataRemoved(eq(KEY), anyBoolean()) verify(listener, never()) .onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean()) } @@ -2224,7 +2224,7 @@ class MediaDataProcessorTest : SysuiTestCase() { sessionCallbackCaptor.value.invoke(KEY) // It is fully removed - verify(listener).onMediaDataRemoved(eq(KEY)) + verify(listener).onMediaDataRemoved(eq(KEY), eq(false)) verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId)) verify(listener, never()) .onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean()) @@ -2278,7 +2278,7 @@ class MediaDataProcessorTest : SysuiTestCase() { sessionCallbackCaptor.value.invoke(KEY) // It is fully removed. - verify(listener).onMediaDataRemoved(eq(KEY)) + verify(listener).onMediaDataRemoved(eq(KEY), eq(false)) verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId)) verify(listener, never()) .onMediaDataLoaded( @@ -2304,7 +2304,7 @@ class MediaDataProcessorTest : SysuiTestCase() { sessionCallbackCaptor.value.invoke(KEY) // It is fully removed - verify(listener).onMediaDataRemoved(eq(KEY)) + verify(listener).onMediaDataRemoved(eq(KEY), eq(false)) verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId)) verify(listener, never()) .onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean()) @@ -2354,7 +2354,7 @@ class MediaDataProcessorTest : SysuiTestCase() { mediaDataProcessor.onNotificationRemoved(KEY) // We still make sure to remove it - verify(listener).onMediaDataRemoved(eq(KEY)) + verify(listener).onMediaDataRemoved(eq(KEY), eq(false)) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt index a447e442a384..befe64c1f411 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt @@ -60,6 +60,7 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock import org.mockito.Mockito.any @@ -158,7 +159,8 @@ public class MediaDeviceManagerTest : SysuiTestCase() { @Test fun removeUnknown() { - manager.onMediaDataRemoved("unknown") + manager.onMediaDataRemoved("unknown", false) + verify(listener, never()).onKeyRemoved(eq(KEY), anyBoolean()) } @Test @@ -170,7 +172,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { @Test fun loadAndRemoveMediaData() { manager.onMediaDataLoaded(KEY, null, mediaData) - manager.onMediaDataRemoved(KEY) + manager.onMediaDataRemoved(KEY, false) fakeBgExecutor.runAllReady() verify(lmm).unregisterCallback(any()) verify(muteAwaitManager).stopListening() @@ -386,9 +388,9 @@ public class MediaDeviceManagerTest : SysuiTestCase() { fun listenerReceivesKeyRemoved() { manager.onMediaDataLoaded(KEY, null, mediaData) // WHEN the notification is removed - manager.onMediaDataRemoved(KEY) + manager.onMediaDataRemoved(KEY, true) // THEN the listener receives key removed event - verify(listener).onKeyRemoved(eq(KEY)) + verify(listener).onKeyRemoved(eq(KEY), eq(true)) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilterTest.kt index 5a3c220b3d23..030bca25c518 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilterTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilterTest.kt @@ -165,10 +165,10 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() { @Test fun noMediaSession_removedEventNotFiltered() { - filter.onMediaDataRemoved(KEY) + filter.onMediaDataRemoved(KEY, false) bgExecutor.runAllReady() fgExecutor.runAllReady() - verify(mediaListener).onMediaDataRemoved(eq(KEY)) + verify(mediaListener).onMediaDataRemoved(eq(KEY), eq(false)) } @Test @@ -193,11 +193,11 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() { whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(controllers) sessionListener.onActiveSessionsChanged(controllers) // WHEN a removed event is received - filter.onMediaDataRemoved(KEY) + filter.onMediaDataRemoved(KEY, false) bgExecutor.runAllReady() fgExecutor.runAllReady() // THEN the event is not filtered - verify(mediaListener).onMediaDataRemoved(eq(KEY)) + verify(mediaListener).onMediaDataRemoved(eq(KEY), eq(false)) } @Test @@ -294,7 +294,7 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() { anyBoolean() ) // AND there should be a removed event for key2 - verify(mediaListener).onMediaDataRemoved(eq(key2)) + verify(mediaListener).onMediaDataRemoved(eq(key2), eq(false)) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt index 3cc65c9524a8..cdbf9d757ec1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt @@ -166,12 +166,12 @@ class MediaTimeoutListenerTest : SysuiTestCase() { @Test fun testOnMediaDataRemoved_unregistersPlaybackListener() { mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData) - mediaTimeoutListener.onMediaDataRemoved(KEY) + mediaTimeoutListener.onMediaDataRemoved(KEY, false) verify(mediaController).unregisterCallback(anyObject()) // Ignores duplicate requests clearInvocations(mediaController) - mediaTimeoutListener.onMediaDataRemoved(KEY) + mediaTimeoutListener.onMediaDataRemoved(KEY, false) verify(mediaController, never()).unregisterCallback(anyObject()) } @@ -181,7 +181,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() { mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData) assertThat(executor.numPending()).isEqualTo(1) // WHEN the media is removed - mediaTimeoutListener.onMediaDataRemoved(KEY) + mediaTimeoutListener.onMediaDataRemoved(KEY, false) // THEN the timeout runnable is cancelled assertThat(executor.numPending()).isEqualTo(0) } @@ -398,7 +398,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() { // WHEN we have a resume control testOnMediaDataLoaded_resumption_registersTimeout() // AND the media is removed - mediaTimeoutListener.onMediaDataRemoved(PACKAGE) + mediaTimeoutListener.onMediaDataRemoved(PACKAGE, false) // THEN the timeout runnable is cancelled assertThat(executor.numPending()).isEqualTo(0) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt index 0a5aace91481..3bb8b8fcd775 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt @@ -33,6 +33,8 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.SysuiTestCase import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager +import com.android.systemui.flags.Flags +import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState @@ -74,12 +76,14 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestDispatcher import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest +import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.Captor import org.mockito.Mock +import org.mockito.Mockito.anyLong import org.mockito.Mockito.floatThat import org.mockito.Mockito.mock import org.mockito.Mockito.never @@ -136,6 +140,9 @@ class MediaCarouselControllerTest : SysuiTestCase() { private lateinit var testDispatcher: TestDispatcher private lateinit var mediaCarouselController: MediaCarouselController + private var originalResumeSetting = + Settings.Secure.getInt(context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, 1) + @Before fun setup() { MockitoAnnotations.initMocks(this) @@ -186,6 +193,15 @@ class MediaCarouselControllerTest : SysuiTestCase() { ) } + @After + fun tearDown() { + Settings.Secure.putInt( + context.contentResolver, + Settings.Secure.MEDIA_CONTROLS_RESUME, + originalResumeSetting + ) + } + @Test fun testPlayerOrdering() { // Test values: key, data, last active time @@ -822,6 +838,7 @@ class MediaCarouselControllerTest : SysuiTestCase() { @Test fun testKeyguardGone_showMediaCarousel() = kosmos.testScope.runTest { + kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false) var updatedVisibility = false mediaCarouselController.updateHostVisibility = { updatedVisibility = true } mediaCarouselController.mediaCarousel = mediaCarousel @@ -844,6 +861,7 @@ class MediaCarouselControllerTest : SysuiTestCase() { @Test fun keyguardShowing_notAllowedOnLockscreen_updateVisibility() { kosmos.testScope.runTest { + kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false) var updatedVisibility = false mediaCarouselController.updateHostVisibility = { updatedVisibility = true } mediaCarouselController.mediaCarousel = mediaCarousel @@ -870,6 +888,7 @@ class MediaCarouselControllerTest : SysuiTestCase() { @Test fun keyguardShowing_allowedOnLockscreen_updateVisibility() { kosmos.testScope.runTest { + kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false) var updatedVisibility = false mediaCarouselController.updateHostVisibility = { updatedVisibility = true } mediaCarouselController.mediaCarousel = mediaCarousel @@ -968,6 +987,45 @@ class MediaCarouselControllerTest : SysuiTestCase() { verify(panel).updateAnimatorDurationScale() } + @Test + fun swipeToDismiss_pausedAndResumeOff_userInitiated() { + // When resumption is disabled, paused media should be dismissed after being swiped away + Settings.Secure.putInt(context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, 0) + + val pausedMedia = DATA.copy(isPlaying = false) + listener.value.onMediaDataLoaded(PAUSED_LOCAL, PAUSED_LOCAL, pausedMedia) + mediaCarouselController.onSwipeToDismiss() + + // When it can be removed immediately on update + whenever(visualStabilityProvider.isReorderingAllowed).thenReturn(true) + val inactiveMedia = pausedMedia.copy(active = false) + listener.value.onMediaDataLoaded(PAUSED_LOCAL, PAUSED_LOCAL, inactiveMedia) + + // This is processed as a user-initiated dismissal + verify(debugLogger).logMediaRemoved(eq(PAUSED_LOCAL), eq(true)) + verify(mediaDataManager).dismissMediaData(eq(PAUSED_LOCAL), anyLong(), eq(true)) + } + + @Test + fun swipeToDismiss_pausedAndResumeOff_delayed_userInitiated() { + // When resumption is disabled, paused media should be dismissed after being swiped away + Settings.Secure.putInt(context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, 0) + mediaCarouselController.updateHostVisibility = {} + + val pausedMedia = DATA.copy(isPlaying = false) + listener.value.onMediaDataLoaded(PAUSED_LOCAL, PAUSED_LOCAL, pausedMedia) + mediaCarouselController.onSwipeToDismiss() + + // When it can't be removed immediately on update + whenever(visualStabilityProvider.isReorderingAllowed).thenReturn(false) + val inactiveMedia = pausedMedia.copy(active = false) + listener.value.onMediaDataLoaded(PAUSED_LOCAL, PAUSED_LOCAL, inactiveMedia) + visualStabilityCallback.value.onReorderingAllowed() + + // This is processed as a user-initiated dismissal + verify(mediaDataManager).dismissMediaData(eq(PAUSED_LOCAL), anyLong(), eq(true)) + } + /** * Helper method when a configuration change occurs. * diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt index 83e4d3130b67..0c9fee9e2741 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt @@ -1344,7 +1344,7 @@ public class MediaControlPanelTest : SysuiTestCase() { assertThat(dismiss.isEnabled).isEqualTo(true) dismiss.callOnClick() verify(logger).logLongPressDismiss(anyInt(), eq(PACKAGE), eq(instanceId)) - verify(mediaDataManager).dismissMediaData(eq(mediaKey), anyLong()) + verify(mediaDataManager).dismissMediaData(eq(mediaKey), anyLong(), eq(true)) } @Test @@ -1360,7 +1360,8 @@ public class MediaControlPanelTest : SysuiTestCase() { @Test fun player_dismissButtonClick_notInManager() { val mediaKey = "key for dismissal" - whenever(mediaDataManager.dismissMediaData(eq(mediaKey), anyLong())).thenReturn(false) + whenever(mediaDataManager.dismissMediaData(eq(mediaKey), anyLong(), eq(true))) + .thenReturn(false) player.attachPlayer(viewHolder) val state = mediaData.copy(notificationKey = KEY) @@ -1369,8 +1370,8 @@ public class MediaControlPanelTest : SysuiTestCase() { assertThat(dismiss.isEnabled).isEqualTo(true) dismiss.callOnClick() - verify(mediaDataManager).dismissMediaData(eq(mediaKey), anyLong()) - verify(mediaCarouselController).removePlayer(eq(mediaKey), eq(false), eq(false)) + verify(mediaDataManager).dismissMediaData(eq(mediaKey), anyLong(), eq(true)) + verify(mediaCarouselController).removePlayer(eq(mediaKey), eq(false), eq(false), eq(true)) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java index ff7c970960e9..8f8630e90694 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java @@ -104,11 +104,11 @@ public class MediaDreamSentinelTest extends SysuiTestCase { listener.onMediaDataLoaded(mKey, mOldKey, mData, /* immediately= */true, /* receivedSmartspaceCardLatency= */0, /* isSsReactived= */ false); - listener.onMediaDataRemoved(mKey); + listener.onMediaDataRemoved(mKey, false); verify(mDreamOverlayStateController, never()).removeComplication(any()); when(mMediaDataManager.hasActiveMedia()).thenReturn(false); - listener.onMediaDataRemoved(mKey); + listener.onMediaDataRemoved(mKey, false); verify(mDreamOverlayStateController).removeComplication(eq(mMediaEntryComplication)); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt index 5e53fe16534d..5cd3f66d2cbf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt @@ -23,17 +23,18 @@ import android.testing.AndroidTestingRunner import android.testing.TestableContext import com.android.systemui.Flags import com.android.systemui.SysuiTestCase +import com.android.systemui.screenshot.proxy.SystemUiProxy import com.android.systemui.settings.DisplayTracker import com.android.systemui.shared.system.ActivityManagerWrapper import com.android.systemui.statusbar.phone.CentralSurfaces -import com.android.systemui.util.mockito.mock import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestCoroutineScheduler import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mockito.verify +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify @RunWith(AndroidTestingRunner::class) class ActionIntentExecutorTest : SysuiTestCase() { @@ -44,8 +45,9 @@ class ActionIntentExecutorTest : SysuiTestCase() { private val testableContext = TestableContext(mContext) private val activityManagerWrapper = mock<ActivityManagerWrapper>() + private val systemUiProxy = mock<SystemUiProxy>() + private val displayTracker = mock<DisplayTracker>() - private val keyguardController = mock<ScreenshotKeyguardController>() private val actionIntentExecutor = ActionIntentExecutor( @@ -53,12 +55,12 @@ class ActionIntentExecutorTest : SysuiTestCase() { activityManagerWrapper, testScope, mainDispatcher, + systemUiProxy, displayTracker, - keyguardController, ) @Test - @EnableFlags(Flags.FLAG_SCREENSHOT_ACTION_DISMISS_SYSTEM_WINDOWS) + @EnableFlags(Flags.FLAG_FIX_SCREENSHOT_ACTION_DISMISS_SYSTEM_WINDOWS) fun launchIntent_callsCloseSystemWindows() = testScope.runTest { val intent = Intent(Intent.ACTION_EDIT).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK } diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp index bc608c5ac0c0..95cbb6b2130a 100644 --- a/ravenwood/Android.bp +++ b/ravenwood/Android.bp @@ -221,7 +221,9 @@ sh_test_host { data: [ ":framework-minus-apex.ravenwood.stats", ":framework-minus-apex.ravenwood.apis", + ":framework-minus-apex.ravenwood.keep_all", ":services.core.ravenwood.stats", ":services.core.ravenwood.apis", + ":services.core.ravenwood.keep_all", ], } diff --git a/ravenwood/scripts/ravenwood-stats-collector.sh b/ravenwood/scripts/ravenwood-stats-collector.sh index cf58bd2f3717..43b61a46b747 100755 --- a/ravenwood/scripts/ravenwood-stats-collector.sh +++ b/ravenwood/scripts/ravenwood-stats-collector.sh @@ -18,8 +18,14 @@ set -e # Output files -stats=/tmp/ravenwood-stats-all.csv -apis=/tmp/ravenwood-apis-all.csv +out_dir=/tmp/ravenwood +stats=$out_dir/ravenwood-stats-all.csv +apis=$out_dir/ravenwood-apis-all.csv +keep_all_dir=$out_dir/ravenwood-keep-all/ + +rm -fr $out_dir +mkdir -p $out_dir +mkdir -p $keep_all_dir # Where the input files are. path=$ANDROID_BUILD_TOP/out/host/linux-x86/testcases/ravenwood-stats-checker/x86_64/ @@ -76,3 +82,7 @@ collect_apis() { collect_stats $stats collect_apis $apis + +cp *keep_all.txt $keep_all_dir +echo "Keep all files created at:" +find $keep_all_dir -type f
\ No newline at end of file diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java index 06a02978701b..71b16c3a18b6 100644 --- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java +++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java @@ -172,8 +172,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku OnCrossProfileWidgetProvidersChangeListener { private static final String TAG = "AppWidgetServiceImpl"; - private static final boolean DEBUG = false; - private static final boolean DEBUG_NULL_PROVIDER_INFO = Build.IS_DEBUGGABLE; + private static final boolean DEBUG = Build.IS_DEBUGGABLE; private static final String OLD_KEYGUARD_HOST_PACKAGE = "android"; private static final String NEW_KEYGUARD_HOST_PACKAGE = "com.android.keyguard"; @@ -1573,9 +1572,36 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku Binder.getCallingUid(), callingPackage); if (widget != null && widget.provider != null && !widget.provider.zombie) { - return cloneIfLocalBinder(widget.provider.getInfoLocked(mContext)); + final AppWidgetProviderInfo info = widget.provider.getInfoLocked(mContext); + if (info == null) { + Slog.e(TAG, "getAppWidgetInfo() returns null because" + + " widget.provider.getInfoLocked() returned null." + + " appWidgetId=" + appWidgetId + " userId=" + userId + + " widget=" + widget); + return null; + } + final AppWidgetProviderInfo ret = cloneIfLocalBinder(info); + if (ret == null) { + Slog.e(TAG, "getAppWidgetInfo() returns null because" + + " cloneIfLocalBinder() returned null." + + " appWidgetId=" + appWidgetId + " userId=" + userId + + " widget=" + widget + " appWidgetProviderInfo=" + info); + } + return ret; + } else { + if (widget == null) { + Slog.e(TAG, "getAppWidgetInfo() returns null because widget is null." + + " appWidgetId=" + appWidgetId + " userId=" + userId); + } else if (widget.provider == null) { + Slog.e(TAG, "getAppWidgetInfo() returns null because widget.provider is null." + + " appWidgetId=" + appWidgetId + " userId=" + userId + + " widget=" + widget); + } else { + Slog.e(TAG, "getAppWidgetInfo() returns null because widget.provider is zombie." + + " appWidgetId=" + appWidgetId + " userId=" + userId + + " widget=" + widget); + } } - return null; } } @@ -2960,7 +2986,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku AppWidgetProviderInfo info = new AppWidgetProviderInfo(); info.provider = providerId.componentName; info.providerInfo = ri.activityInfo; - if (DEBUG_NULL_PROVIDER_INFO) { + if (DEBUG) { Objects.requireNonNull(ri.activityInfo); } return info; @@ -2997,7 +3023,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku AppWidgetProviderInfo info = new AppWidgetProviderInfo(); info.provider = providerId.componentName; info.providerInfo = activityInfo; - if (DEBUG_NULL_PROVIDER_INFO) { + if (DEBUG) { Objects.requireNonNull(activityInfo); } @@ -3575,7 +3601,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku AppWidgetProviderInfo info = new AppWidgetProviderInfo(); info.provider = providerId.componentName; info.providerInfo = providerInfo; - if (DEBUG_NULL_PROVIDER_INFO) { + if (DEBUG) { Objects.requireNonNull(providerInfo); } @@ -3594,7 +3620,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku if (info != null) { info.provider = providerId.componentName; info.providerInfo = providerInfo; - if (DEBUG_NULL_PROVIDER_INFO) { + if (DEBUG) { Objects.requireNonNull(providerInfo); } provider.setInfoLocked(info); @@ -4678,6 +4704,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } if (newInfo != null) { info = newInfo; + if (DEBUG) { + Objects.requireNonNull(info); + } updateGeneratedPreviewCategoriesLocked(); } } @@ -4699,12 +4728,18 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku @GuardedBy("AppWidgetServiceImpl.mLock") public void setPartialInfoLocked(AppWidgetProviderInfo info) { this.info = info; + if (DEBUG) { + Objects.requireNonNull(this.info); + } mInfoParsed = false; } @GuardedBy("AppWidgetServiceImpl.mLock") public void setInfoLocked(AppWidgetProviderInfo info) { this.info = info; + if (DEBUG) { + Objects.requireNonNull(this.info); + } mInfoParsed = true; } diff --git a/services/backup/java/com/android/server/backup/transport/TransportConnection.java b/services/backup/java/com/android/server/backup/transport/TransportConnection.java index 1009787bebe5..67ebb3e6a1ef 100644 --- a/services/backup/java/com/android/server/backup/transport/TransportConnection.java +++ b/services/backup/java/com/android/server/backup/transport/TransportConnection.java @@ -658,11 +658,13 @@ public class TransportConnection { * This class is a proxy to TransportClient methods that doesn't hold a strong reference to the * TransportClient, allowing it to be GC'ed. If the reference was lost it logs a message. */ - private static class TransportConnectionMonitor implements ServiceConnection { + @VisibleForTesting + static class TransportConnectionMonitor implements ServiceConnection { private final Context mContext; private final WeakReference<TransportConnection> mTransportClientRef; - private TransportConnectionMonitor(Context context, + @VisibleForTesting + TransportConnectionMonitor(Context context, TransportConnection transportConnection) { mContext = context; mTransportClientRef = new WeakReference<>(transportConnection); @@ -704,7 +706,13 @@ public class TransportConnection { /** @see TransportConnection#finalize() */ private void referenceLost(String caller) { - mContext.unbindService(this); + try { + mContext.unbindService(this); + } catch (IllegalArgumentException e) { + TransportUtils.log(Priority.WARN, TAG, + caller + " called but unbindService failed: " + e.getMessage()); + return; + } TransportUtils.log( Priority.INFO, TAG, diff --git a/services/core/Android.bp b/services/core/Android.bp index d153c18b54b0..dc1155a484fb 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -232,6 +232,7 @@ java_library_static { "android.hardware.rebootescrow-V1-java", "android.hardware.power.stats-V2-java", "android.hidl.manager-V1.2-java", + "audio-permission-aidl-java", "cbor-java", "com.android.media.audio-aconfig-java", "icu4j_calendar_astronomer", diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index c3108229c265..15c5c1077803 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -4440,7 +4440,8 @@ public class AudioService extends IAudioService.Stub || usage == AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING) { voiceActive = true; } - if (usage == AudioAttributes.USAGE_MEDIA || usage == AudioAttributes.USAGE_GAME) { + if (usage == AudioAttributes.USAGE_MEDIA || usage == AudioAttributes.USAGE_GAME + || usage == AudioAttributes.USAGE_UNKNOWN) { mediaActive = true; } } diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java index e2c4b4638207..cae169550d9a 100644 --- a/services/core/java/com/android/server/audio/SpatializerHelper.java +++ b/services/core/java/com/android/server/audio/SpatializerHelper.java @@ -347,9 +347,6 @@ public class SpatializerHelper { //------------------------------------------------------ // routing monitoring synchronized void onRoutingUpdated() { - if (!mFeatureEnabled) { - return; - } switch (mState) { case STATE_UNINITIALIZED: case STATE_NOT_SUPPORTED: @@ -393,7 +390,7 @@ public class SpatializerHelper { setDispatchAvailableState(false); } - boolean enabled = able && enabledAvailable.first; + boolean enabled = mFeatureEnabled && able && enabledAvailable.first; if (enabled) { loglogi("Enabling Spatial Audio since enabled for media device:" + currentDevice); diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java index d9c3ab806888..30d12e670a6c 100644 --- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java +++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java @@ -1189,7 +1189,11 @@ public class AutomaticBrightnessController { update(); } - void switchMode(@AutomaticBrightnessMode int mode) { + /** + * Responsible for switching the AutomaticBrightnessMode of the associated display. Also takes + * care of resetting the short term model wherever required + */ + public void switchMode(@AutomaticBrightnessMode int mode) { if (!mBrightnessMappingStrategyMap.contains(mode)) { return; } diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 70a1014a0c8c..875fd059d612 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -84,6 +84,7 @@ import com.android.server.display.brightness.BrightnessUtils; import com.android.server.display.brightness.DisplayBrightnessController; import com.android.server.display.brightness.clamper.BrightnessClamperController; import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy2; +import com.android.server.display.brightness.strategy.DisplayBrightnessStrategyConstants; import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceInternal; import com.android.server.display.color.ColorDisplayService.ReduceBrightColorsListener; import com.android.server.display.config.HysteresisLevels; @@ -1333,12 +1334,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mDisplayStateController.shouldPerformScreenOffTransition()); state = mPowerState.getScreenState(); - // Switch to doze auto-brightness mode if needed - if (mFlags.areAutoBrightnessModesEnabled() && mAutomaticBrightnessController != null - && !mAutomaticBrightnessController.isInIdleMode()) { - mAutomaticBrightnessController.switchMode(Display.isDozeState(state) - ? AUTO_BRIGHTNESS_MODE_DOZE : AUTO_BRIGHTNESS_MODE_DEFAULT); - } DisplayBrightnessState displayBrightnessState = mDisplayBrightnessController .updateBrightness(mPowerRequest, state); @@ -1372,6 +1367,13 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call final boolean wasShortTermModelActive = mAutomaticBrightnessStrategy.isShortTermModelActive(); if (!mFlags.isRefactorDisplayPowerControllerEnabled()) { + // Switch to doze auto-brightness mode if needed + if (mFlags.areAutoBrightnessModesEnabled() && mAutomaticBrightnessController != null + && !mAutomaticBrightnessController.isInIdleMode()) { + mAutomaticBrightnessController.switchMode(Display.isDozeState(state) + ? AUTO_BRIGHTNESS_MODE_DOZE : AUTO_BRIGHTNESS_MODE_DEFAULT); + } + mAutomaticBrightnessStrategy.setAutoBrightnessState(state, mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig(), mBrightnessReasonTemp.getReason(), mPowerRequest.policy, @@ -1440,45 +1442,52 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call brightnessState = clampScreenBrightness(brightnessState); } - // If there's an offload session, we need to set the initial doze brightness before - // the offload session starts controlling the brightness. - // During the transition DOZE_SUSPEND -> DOZE -> DOZE_SUSPEND, this brightness strategy - // will be selected again, meaning that no new brightness will be sent to the hardware and - // the display will stay at the brightness level set by the offload session. - if (Float.isNaN(brightnessState) && mFlags.isDisplayOffloadEnabled() - && Display.isDozeState(state) && mDisplayOffloadSession != null) { - if (mAutomaticBrightnessController != null - && mAutomaticBrightnessStrategy.shouldUseAutoBrightness()) { - // Use the auto-brightness curve and the last observed lux - rawBrightnessState = mAutomaticBrightnessController - .getAutomaticScreenBrightnessBasedOnLastUsedLux( - mTempBrightnessEvent); - } else { - rawBrightnessState = getDozeBrightnessForOffload(); - mTempBrightnessEvent.setFlags(mTempBrightnessEvent.getFlags() - | BrightnessEvent.FLAG_DOZE_SCALE); - } - - if (BrightnessUtils.isValidBrightnessValue(rawBrightnessState)) { - brightnessState = clampScreenBrightness(rawBrightnessState); - mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE_INITIAL); - + if (Display.isDozeState(state)) { + // If there's an offload session, we need to set the initial doze brightness before + // the offload session starts controlling the brightness. + // During the transition DOZE_SUSPEND -> DOZE -> DOZE_SUSPEND, this brightness strategy + // will be selected again, meaning that no new brightness will be sent to the hardware + // and the display will stay at the brightness level set by the offload session. + if ((Float.isNaN(brightnessState) + || displayBrightnessState.getDisplayBrightnessStrategyName() + .equals(DisplayBrightnessStrategyConstants.FALLBACK_BRIGHTNESS_STRATEGY_NAME)) + && mFlags.isDisplayOffloadEnabled() + && mDisplayOffloadSession != null) { if (mAutomaticBrightnessController != null && mAutomaticBrightnessStrategy.shouldUseAutoBrightness()) { - // Keep the brightness in the setting so that we can use it after the screen - // turns on, until a lux sample becomes available. We don't do this when - // auto-brightness is disabled - in that situation we still want to use - // the last brightness from when the screen was on. - updateScreenBrightnessSetting = currentBrightnessSetting != brightnessState; + // Use the auto-brightness curve and the last observed lux + rawBrightnessState = mAutomaticBrightnessController + .getAutomaticScreenBrightnessBasedOnLastUsedLux( + mTempBrightnessEvent); + } else { + rawBrightnessState = getDozeBrightnessForOffload(); + mTempBrightnessEvent.setFlags(mTempBrightnessEvent.getFlags() + | BrightnessEvent.FLAG_DOZE_SCALE); + } + + if (BrightnessUtils.isValidBrightnessValue(rawBrightnessState)) { + brightnessState = clampScreenBrightness(rawBrightnessState); + mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE_INITIAL); + + if (mAutomaticBrightnessController != null + && mAutomaticBrightnessStrategy.shouldUseAutoBrightness()) { + // Keep the brightness in the setting so that we can use it after the screen + // turns on, until a lux sample becomes available. We don't do this when + // auto-brightness is disabled - in that situation we still want to use + // the last brightness from when the screen was on. + updateScreenBrightnessSetting = currentBrightnessSetting != brightnessState; + } } } - } - // Use default brightness when dozing unless overridden. - if (Float.isNaN(brightnessState) && Display.isDozeState(state)) { - rawBrightnessState = mScreenBrightnessDozeConfig; - brightnessState = clampScreenBrightness(rawBrightnessState); - mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE_DEFAULT); + // Use default brightness when dozing unless overridden. + if (Float.isNaN(brightnessState) + || displayBrightnessState.getDisplayBrightnessStrategyName() + .equals(DisplayBrightnessStrategyConstants.FALLBACK_BRIGHTNESS_STRATEGY_NAME)) { + rawBrightnessState = mScreenBrightnessDozeConfig; + brightnessState = clampScreenBrightness(rawBrightnessState); + mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE_DEFAULT); + } } if (!mFlags.isRefactorDisplayPowerControllerEnabled()) { @@ -1502,7 +1511,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } // Apply manual brightness. - if (Float.isNaN(brightnessState)) { + if (Float.isNaN(brightnessState) && !mFlags.isRefactorDisplayPowerControllerEnabled()) { rawBrightnessState = currentBrightnessSetting; brightnessState = clampScreenBrightness(rawBrightnessState); if (brightnessState != currentBrightnessSetting) { diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java index 22a21a6c113c..feec4e6b2259 100644 --- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java +++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java @@ -32,6 +32,7 @@ import com.android.server.display.brightness.strategy.AutomaticBrightnessStrateg import com.android.server.display.brightness.strategy.BoostBrightnessStrategy; import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy; import com.android.server.display.brightness.strategy.DozeBrightnessStrategy; +import com.android.server.display.brightness.strategy.FallbackBrightnessStrategy; import com.android.server.display.brightness.strategy.FollowerBrightnessStrategy; import com.android.server.display.brightness.strategy.InvalidBrightnessStrategy; import com.android.server.display.brightness.strategy.OffloadBrightnessStrategy; @@ -85,6 +86,9 @@ public class DisplayBrightnessStrategySelector { @Nullable private final AutoBrightnessFallbackStrategy mAutoBrightnessFallbackStrategy; + @Nullable + private final FallbackBrightnessStrategy mFallbackBrightnessStrategy; + // A collective representation of all the strategies that the selector is aware of. This is // non null, but the strategies this is tracking can be null @NonNull @@ -118,7 +122,8 @@ public class DisplayBrightnessStrategySelector { mInvalidBrightnessStrategy = injector.getInvalidBrightnessStrategy(); mAutomaticBrightnessStrategy1 = (!mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()) ? null - : injector.getAutomaticBrightnessStrategy1(context, displayId); + : injector.getAutomaticBrightnessStrategy1(context, displayId, + mDisplayManagerFlags); mAutomaticBrightnessStrategy2 = (mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()) ? null : injector.getAutomaticBrightnessStrategy2(context, displayId); @@ -134,11 +139,14 @@ public class DisplayBrightnessStrategySelector { } else { mOffloadBrightnessStrategy = null; } + mFallbackBrightnessStrategy = (mDisplayManagerFlags + .isRefactorDisplayPowerControllerEnabled()) + ? injector.getFallbackBrightnessStrategy() : null; mDisplayBrightnessStrategies = new DisplayBrightnessStrategy[]{mInvalidBrightnessStrategy, mScreenOffBrightnessStrategy, mDozeBrightnessStrategy, mFollowerBrightnessStrategy, mBoostBrightnessStrategy, mOverrideBrightnessStrategy, mTemporaryBrightnessStrategy, mAutomaticBrightnessStrategy1, mOffloadBrightnessStrategy, - mAutoBrightnessFallbackStrategy}; + mAutoBrightnessFallbackStrategy, mFallbackBrightnessStrategy}; mAllowAutoBrightnessWhileDozingConfig = context.getResources().getBoolean( R.bool.config_allowAutoBrightnessWhileDozing); mOldBrightnessStrategyName = mInvalidBrightnessStrategy.getName(); @@ -179,6 +187,12 @@ public class DisplayBrightnessStrategySelector { displayBrightnessStrategy = mOffloadBrightnessStrategy; } else if (isAutoBrightnessFallbackStrategyValid()) { displayBrightnessStrategy = mAutoBrightnessFallbackStrategy; + } else { + // This will become the ultimate fallback strategy once the flag has been fully rolled + // out + if (mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()) { + displayBrightnessStrategy = mFallbackBrightnessStrategy; + } } if (mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()) { @@ -330,8 +344,8 @@ public class DisplayBrightnessStrategySelector { } AutomaticBrightnessStrategy getAutomaticBrightnessStrategy1(Context context, - int displayId) { - return new AutomaticBrightnessStrategy(context, displayId); + int displayId, DisplayManagerFlags displayManagerFlags) { + return new AutomaticBrightnessStrategy(context, displayId, displayManagerFlags); } AutomaticBrightnessStrategy2 getAutomaticBrightnessStrategy2(Context context, @@ -347,5 +361,9 @@ public class DisplayBrightnessStrategySelector { AutoBrightnessFallbackStrategy getAutoBrightnessFallbackStrategy() { return new AutoBrightnessFallbackStrategy(/* injector= */ null); } + + FallbackBrightnessStrategy getFallbackBrightnessStrategy() { + return new FallbackBrightnessStrategy(); + } } } diff --git a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java index 23052286d9ec..f809a49fd3d3 100644 --- a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java +++ b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java @@ -17,6 +17,9 @@ package com.android.server.display.brightness.strategy; import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE; +import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT; +import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE; + import android.annotation.Nullable; import android.content.Context; import android.hardware.display.BrightnessConfiguration; @@ -33,6 +36,7 @@ import com.android.server.display.brightness.BrightnessReason; import com.android.server.display.brightness.BrightnessUtils; import com.android.server.display.brightness.StrategyExecutionRequest; import com.android.server.display.brightness.StrategySelectionNotifyRequest; +import com.android.server.display.feature.DisplayManagerFlags; import java.io.PrintWriter; @@ -98,19 +102,24 @@ public class AutomaticBrightnessStrategy extends AutomaticBrightnessStrategy2 private Injector mInjector; + private DisplayManagerFlags mDisplayManagerFlags; + @VisibleForTesting - AutomaticBrightnessStrategy(Context context, int displayId, Injector injector) { + AutomaticBrightnessStrategy(Context context, int displayId, Injector injector, + DisplayManagerFlags displayManagerFlags) { super(context, displayId); mContext = context; mDisplayId = displayId; mAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting(); mPendingAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT; mTemporaryAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT; + mDisplayManagerFlags = displayManagerFlags; mInjector = (injector == null) ? new RealInjector() : injector; } - public AutomaticBrightnessStrategy(Context context, int displayId) { - this(context, displayId, null); + public AutomaticBrightnessStrategy(Context context, int displayId, + DisplayManagerFlags displayManagerFlags) { + this(context, displayId, null, displayManagerFlags); } /** @@ -120,6 +129,7 @@ public class AutomaticBrightnessStrategy extends AutomaticBrightnessStrategy2 public void setAutoBrightnessState(int targetDisplayState, boolean allowAutoBrightnessWhileDozingConfig, int brightnessReason, int policy, float lastUserSetScreenBrightness, boolean userSetBrightnessChanged) { + switchMode(targetDisplayState); final boolean autoBrightnessEnabledInDoze = allowAutoBrightnessWhileDozingConfig && policy == POLICY_DOZE; mIsAutoBrightnessEnabled = shouldUseAutoBrightness() @@ -479,6 +489,16 @@ public class AutomaticBrightnessStrategy extends AutomaticBrightnessStrategy2 } } + + private void switchMode(int state) { + if (mDisplayManagerFlags.areAutoBrightnessModesEnabled() + && mAutomaticBrightnessController != null + && !mAutomaticBrightnessController.isInIdleMode()) { + mAutomaticBrightnessController.switchMode(Display.isDozeState(state) + ? AUTO_BRIGHTNESS_MODE_DOZE : AUTO_BRIGHTNESS_MODE_DEFAULT); + } + } + /** * Evaluates if there are any temporary auto-brightness adjustments which is not applied yet. * Temporary brightness adjustments happen when the user moves the brightness slider in the diff --git a/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategyConstants.java b/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategyConstants.java index 504683a55735..7b2f2b9d307b 100644 --- a/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategyConstants.java +++ b/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategyConstants.java @@ -18,4 +18,5 @@ package com.android.server.display.brightness.strategy; public class DisplayBrightnessStrategyConstants { static final String INVALID_BRIGHTNESS_STRATEGY_NAME = "InvalidBrightnessStrategy"; + public static final String FALLBACK_BRIGHTNESS_STRATEGY_NAME = "FallbackBrightnessStrategy"; } diff --git a/services/core/java/com/android/server/display/brightness/strategy/FallbackBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/FallbackBrightnessStrategy.java new file mode 100644 index 000000000000..3463649aa000 --- /dev/null +++ b/services/core/java/com/android/server/display/brightness/strategy/FallbackBrightnessStrategy.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display.brightness.strategy; + +import android.annotation.NonNull; + +import com.android.server.display.DisplayBrightnessState; +import com.android.server.display.brightness.BrightnessReason; +import com.android.server.display.brightness.StrategyExecutionRequest; +import com.android.server.display.brightness.StrategySelectionNotifyRequest; + +import java.io.PrintWriter; + +/** + * Manages the brightness of the associated display when no other strategy qualifies for + * setting up the brightness state. This strategy is also being used for evaluating the + * display brightness state when we have a manually set brightness. This is a temporary state, and + * the logic for evaluating the manual brightness will be moved to a separate strategy + */ +public class FallbackBrightnessStrategy implements DisplayBrightnessStrategy{ + @Override + public DisplayBrightnessState updateBrightness( + StrategyExecutionRequest strategyExecutionRequest) { + BrightnessReason brightnessReason = new BrightnessReason(); + brightnessReason.setReason(BrightnessReason.REASON_MANUAL); + return new DisplayBrightnessState.Builder() + .setBrightness(strategyExecutionRequest.getCurrentScreenBrightness()) + .setSdrBrightness(strategyExecutionRequest.getCurrentScreenBrightness()) + .setBrightnessReason(brightnessReason) + .setDisplayBrightnessStrategyName(getName()) + // The fallback brightness might change due to clamping. Make sure we tell the rest + // of the system by updating the setting + .setShouldUpdateScreenBrightnessSetting(true) + .build(); + } + + @NonNull + @Override + public String getName() { + return DisplayBrightnessStrategyConstants.FALLBACK_BRIGHTNESS_STRATEGY_NAME; + } + + @Override + public int getReason() { + return BrightnessReason.REASON_MANUAL; + } + + @Override + public void dump(PrintWriter writer) { + + } + + @Override + public void strategySelectionPostProcessor( + StrategySelectionNotifyRequest strategySelectionNotifyRequest) { + + } +} diff --git a/services/core/java/com/android/server/dreams/DreamController.java b/services/core/java/com/android/server/dreams/DreamController.java index 816242df639d..c6aef7fb3540 100644 --- a/services/core/java/com/android/server/dreams/DreamController.java +++ b/services/core/java/com/android/server/dreams/DreamController.java @@ -249,14 +249,14 @@ final class DreamController { mCurrentDream.mAppTask = appTask; } - void setDreamHasFocus(boolean hasFocus) { + void setDreamIsObscured(boolean isObscured) { if (mCurrentDream != null) { - mCurrentDream.mDreamHasFocus = hasFocus; + mCurrentDream.mDreamIsObscured = isObscured; } } - boolean dreamHasFocus() { - return mCurrentDream != null && mCurrentDream.mDreamHasFocus; + boolean dreamIsFrontmost() { + return mCurrentDream != null && mCurrentDream.dreamIsFrontmost(); } /** @@ -451,7 +451,7 @@ final class DreamController { private String mStopReason; private long mDreamStartTime; public boolean mWakingGently; - public boolean mDreamHasFocus; + private boolean mDreamIsObscured; private final Runnable mStopPreviousDreamsIfNeeded = this::stopPreviousDreamsIfNeeded; private final Runnable mReleaseWakeLockIfNeeded = this::releaseWakeLockIfNeeded; @@ -549,5 +549,9 @@ final class DreamController { mHandler.removeCallbacks(mReleaseWakeLockIfNeeded); } } + + boolean dreamIsFrontmost() { + return !mDreamIsObscured; + } } } diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java index 2def5aed2478..18a9986d34ba 100644 --- a/services/core/java/com/android/server/dreams/DreamManagerService.java +++ b/services/core/java/com/android/server/dreams/DreamManagerService.java @@ -20,7 +20,7 @@ import static android.Manifest.permission.BIND_DREAM_SERVICE; import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; -import static android.service.dreams.Flags.dreamTracksFocus; +import static android.service.dreams.Flags.dreamHandlesBeingObscured; import static com.android.server.wm.ActivityInterceptorCallback.DREAM_MANAGER_ORDERED_ID; @@ -428,7 +428,7 @@ public final class DreamManagerService extends SystemService { // Can't start dreaming if we are already dreaming and the dream has focus. If we are // dreaming but the dream does not have focus, then the dream can be brought to the // front so it does have focus. - if (isScreenOn && isDreamingInternal() && dreamHasFocus()) { + if (isScreenOn && isDreamingInternal() && dreamIsFrontmost()) { return false; } @@ -463,9 +463,10 @@ public final class DreamManagerService extends SystemService { } } - private boolean dreamHasFocus() { - // Dreams always had focus before they were able to track it. - return !dreamTracksFocus() || mController.dreamHasFocus(); + private boolean dreamIsFrontmost() { + // Dreams were always considered frontmost before they began tracking whether they are + // obscured. + return !dreamHandlesBeingObscured() || mController.dreamIsFrontmost(); } protected void requestStartDreamFromShell() { @@ -473,7 +474,7 @@ public final class DreamManagerService extends SystemService { } private void requestDreamInternal() { - if (isDreamingInternal() && !dreamHasFocus() && mController.bringDreamToFront()) { + if (isDreamingInternal() && !dreamIsFrontmost() && mController.bringDreamToFront()) { return; } @@ -1159,10 +1160,16 @@ public final class DreamManagerService extends SystemService { } @Override - public void onDreamFocusChanged(boolean hasFocus) { + public void setDreamIsObscured(boolean isObscured) { + if (!dreamHandlesBeingObscured()) { + return; + } + + checkPermission(android.Manifest.permission.WRITE_DREAM_STATE); + final long ident = Binder.clearCallingIdentity(); try { - mController.setDreamHasFocus(hasFocus); + mHandler.post(() -> mController.setDreamIsObscured(isObscured)); } finally { Binder.restoreCallingIdentity(ident); } diff --git a/services/core/java/com/android/server/input/InputManagerInternal.java b/services/core/java/com/android/server/input/InputManagerInternal.java index b47631c35e38..d32a5ed60094 100644 --- a/services/core/java/com/android/server/input/InputManagerInternal.java +++ b/services/core/java/com/android/server/input/InputManagerInternal.java @@ -218,4 +218,13 @@ public abstract class InputManagerInternal { * display, external peripherals, fingerprint sensor, etc. */ public abstract void notifyUserActivity(); + + /** + * Get the device ID of the {@link InputDevice} that used most recently. + * + * @return the last used input device ID, or + * {@link android.os.IInputConstants#INVALID_INPUT_DEVICE_ID} if no device has been used + * since boot. + */ + public abstract int getLastUsedInputDeviceId(); } diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 83179914c746..8685d2c45762 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -3204,6 +3204,11 @@ public class InputManagerService extends IInputManager.Stub public void setStylusButtonMotionEventsEnabled(boolean enabled) { mNative.setStylusButtonMotionEventsEnabled(enabled); } + + @Override + public int getLastUsedInputDeviceId() { + return mNative.getLastUsedInputDeviceId(); + } } @Override diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java index f742360484f5..0208a325a1d5 100644 --- a/services/core/java/com/android/server/input/NativeInputManagerService.java +++ b/services/core/java/com/android/server/input/NativeInputManagerService.java @@ -271,6 +271,15 @@ interface NativeInputManagerService { void setInputMethodConnectionIsActive(boolean isActive); + /** + * Get the device ID of the InputDevice that used most recently. + * + * @return the last used input device ID, or + * {@link android.os.IInputConstants#INVALID_INPUT_DEVICE_ID} if no device has been used + * since boot. + */ + int getLastUsedInputDeviceId(); + /** The native implementation of InputManagerService methods. */ class NativeImpl implements NativeInputManagerService { /** Pointer to native input manager service object, used by native code. */ @@ -544,5 +553,8 @@ interface NativeInputManagerService { @Override public native void setInputMethodConnectionIsActive(boolean isActive); + + @Override + public native int getLastUsedInputDeviceId(); } } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 67df99279242..b9c585b41416 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -533,21 +533,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. EditorInfo mCurEditorInfo; /** - * Id obtained with {@link InputMethodInfo#getId()} for the input method that we are currently - * connected to or in the process of connecting to. - * - * <p>This can be {@code null} when no input method is connected.</p> - * - * @see #getSelectedMethodIdLocked() - */ - @GuardedBy("ImfLock.class") - @Nullable - private String getCurIdLocked() { - final var userData = mUserDataRepository.getOrCreate(mCurrentUserId); - return userData.mBindingController.getCurId(); - } - - /** * The current subtype of the current input method. */ @MultiUserUnawareField @@ -2011,8 +1996,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. final var userData = mUserDataRepository.getOrCreate(mCurrentUserId); final StartInputInfo info = new StartInputInfo(mCurrentUserId, getCurTokenLocked(), - mCurTokenDisplayId, getCurIdLocked(), startInputReason, restarting, - UserHandle.getUserId(mCurClient.mUid), + mCurTokenDisplayId, userData.mBindingController.getCurId(), startInputReason, + restarting, UserHandle.getUserId(mCurClient.mUid), mCurClient.mSelfReportedDisplayId, mImeBindingState.mFocusedWindow, mCurEditorInfo, mImeBindingState.mFocusedWindowSoftInputMode, userData.mBindingController.getSequenceNumber()); @@ -2048,7 +2033,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. null /* resultReceiver */, SoftInputShowHideReason.ATTACH_NEW_INPUT); } - final var curId = getCurIdLocked(); + final var curId = userData.mBindingController.getCurId(); final InputMethodInfo curInputMethodInfo = InputMethodSettingsRepository.get(mCurrentUserId) .getMethodMap().get(curId); final boolean suppressesSpellChecker = @@ -2337,7 +2322,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. requestClientSessionForAccessibilityLocked(cs); return new InputBindResult( InputBindResult.ResultCode.SUCCESS_WAITING_IME_SESSION, - null, null, null, getCurIdLocked(), + null, null, null, + userData.mBindingController.getCurId(), userData.mBindingController.getSequenceNumber(), false); } else { final long lastBindTime = userData.mBindingController.getLastBindTime(); @@ -2352,7 +2338,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // to see if we can get back in touch with the service. return new InputBindResult( InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING, - null, null, null, getCurIdLocked(), + null, null, null, + userData.mBindingController.getCurId(), userData.mBindingController.getSequenceNumber(), false); } else { EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, @@ -2707,7 +2694,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // When the IME switcher dialog is shown, the IME switcher button should be hidden. if (mMenuController.getSwitchingDialogLocked() != null) return false; // When we are switching IMEs, the IME switcher button should be hidden. - if (!Objects.equals(getCurIdLocked(), getSelectedMethodIdLocked())) { + final var userData = mUserDataRepository.getOrCreate(mCurrentUserId); + if (!Objects.equals(userData.mBindingController.getCurId(), getSelectedMethodIdLocked())) { return false; } if (mWindowManagerInternal.isKeyguardShowingAndNotOccluded() @@ -2869,8 +2857,10 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } else { vis &= ~InputMethodService.IME_VISIBLE_IMPERCEPTIBLE; } + final var userData = mUserDataRepository.getOrCreate(mCurrentUserId); + final var curId = userData.mBindingController.getCurId(); if (mMenuController.getSwitchingDialogLocked() != null - || !Objects.equals(getCurIdLocked(), getSelectedMethodIdLocked())) { + || !Objects.equals(curId, getSelectedMethodIdLocked())) { // When the IME switcher dialog is shown, or we are switching IMEs, // the back button should be in the default state (as if the IME is not shown). backDisposition = InputMethodService.BACK_DISPOSITION_ADJUST_NOTHING; @@ -4438,7 +4428,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. if (mCurEditorInfo != null) { mCurEditorInfo.dumpDebug(proto, CUR_ATTRIBUTE); } - proto.write(CUR_ID, getCurIdLocked()); + proto.write(CUR_ID, userData.mBindingController.getCurId()); mVisibilityStateComputer.dumpDebug(proto, fieldId); proto.write(IN_FULLSCREEN_MODE, mInFullscreenMode); proto.write(CUR_TOKEN, Objects.toString(getCurTokenLocked())); @@ -5648,7 +5638,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. final InputBindResult res = new InputBindResult( InputBindResult.ResultCode.SUCCESS_WITH_ACCESSIBILITY_SESSION, imeSession, accessibilityInputMethodSessions, /* channel= */ null, - getCurIdLocked(), + userData.mBindingController.getCurId(), userData.mBindingController.getSequenceNumber(), /* isInputMethodSuppressingSpellChecker= */ false); mCurClient.mClient.onBindAccessibilityService(res, accessibilityConnectionId); @@ -5901,7 +5891,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. p.println(" mFocusedWindowPerceptible=" + mFocusedWindowPerceptible); mImeBindingState.dump(/* prefix= */ " ", p); - p.println(" mCurId=" + getCurIdLocked() + p.println(" mCurId=" + userData.mBindingController.getCurId() + " mHaveConnection=" + userData.mBindingController.hasMainConnection() + " mBoundToMethod=" + mBoundToMethod + " mVisibleBound=" + userData.mBindingController.isVisibleBound()); diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 61054a9d4de5..4f87c83bb0d7 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -593,6 +593,8 @@ public class NotificationManagerService extends SystemService { static final long NOTIFICATION_TTL = Duration.ofDays(3).toMillis(); + static final long NOTIFICATION_MAX_AGE_AT_POST = Duration.ofDays(14).toMillis(); + private IActivityManager mAm; private ActivityTaskManagerInternal mAtm; private ActivityManager mActivityManager; @@ -2637,27 +2639,48 @@ public class NotificationManagerService extends SystemService { * Cleanup broadcast receivers change listeners. */ public void onDestroy() { - getContext().unregisterReceiver(mIntentReceiver); - getContext().unregisterReceiver(mPackageIntentReceiver); + if (mIntentReceiver != null) { + getContext().unregisterReceiver(mIntentReceiver); + } + if (mPackageIntentReceiver != null) { + getContext().unregisterReceiver(mPackageIntentReceiver); + } if (Flags.allNotifsNeedTtl()) { - mTtlHelper.destroy(); + if (mTtlHelper != null) { + mTtlHelper.destroy(); + } } else { - getContext().unregisterReceiver(mNotificationTimeoutReceiver); + if (mNotificationTimeoutReceiver != null) { + getContext().unregisterReceiver(mNotificationTimeoutReceiver); + } + } + if (mRestoreReceiver != null) { + getContext().unregisterReceiver(mRestoreReceiver); + } + if (mLocaleChangeReceiver != null) { + getContext().unregisterReceiver(mLocaleChangeReceiver); + } + if (mSettingsObserver != null) { + mSettingsObserver.destroy(); + } + if (mRoleObserver != null) { + mRoleObserver.destroy(); } - getContext().unregisterReceiver(mRestoreReceiver); - getContext().unregisterReceiver(mLocaleChangeReceiver); - - mSettingsObserver.destroy(); - mRoleObserver.destroy(); if (mShortcutHelper != null) { mShortcutHelper.destroy(); } - mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_PREFERENCES); - mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES); - mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES); - mStatsManager.clearPullAtomCallback(DND_MODE_RULE); - mAppOps.stopWatchingMode(mAppOpsListener); - mAlarmManager.cancelAll(); + if (mStatsManager != null) { + mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_PREFERENCES); + mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES); + mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES); + mStatsManager.clearPullAtomCallback(DND_MODE_RULE); + } + if (mAppOps != null) { + mAppOps.stopWatchingMode(mAppOpsListener); + } + if (mAlarmManager != null) { + mAlarmManager.cancelAll(); + } } protected String[] getStringArrayResource(int key) { @@ -7722,6 +7745,9 @@ public class NotificationManagerService extends SystemService { return true; } // Check if an app has been given system exemption + if (ai.uid == Process.SYSTEM_UID) { + return false; + } return mAppOps.checkOpNoThrow( AppOpsManager.OP_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS, ai.uid, ai.packageName) == MODE_ALLOWED; @@ -8016,6 +8042,13 @@ public class NotificationManagerService extends SystemService { return false; } + if (Flags.rejectOldNotifications() && n.hasAppProvidedWhen() && n.getWhen() > 0 + && (System.currentTimeMillis() - n.getWhen()) > NOTIFICATION_MAX_AGE_AT_POST) { + Slog.d(TAG, "Ignored enqueue for old " + n.getWhen() + " notification " + r.getKey()); + mUsageStats.registerTooOldBlocked(r); + return false; + } + return true; } diff --git a/services/core/java/com/android/server/notification/NotificationUsageStats.java b/services/core/java/com/android/server/notification/NotificationUsageStats.java index e960f4ba11fd..c09077e349fd 100644 --- a/services/core/java/com/android/server/notification/NotificationUsageStats.java +++ b/services/core/java/com/android/server/notification/NotificationUsageStats.java @@ -257,6 +257,14 @@ public class NotificationUsageStats { } } + public synchronized void registerTooOldBlocked(NotificationRecord notification) { + AggregatedStats[] aggregatedStatsArray = getAggregatedStatsLocked(notification); + for (AggregatedStats stats : aggregatedStatsArray) { + stats.numTooOld++; + } + releaseAggregatedStatsLocked(aggregatedStatsArray); + } + @GuardedBy("this") private AggregatedStats[] getAggregatedStatsLocked(NotificationRecord record) { return getAggregatedStatsLocked(record.getSbn().getPackageName()); @@ -405,6 +413,7 @@ public class NotificationUsageStats { public int numUndecoratedRemoteViews; public long mLastAccessTime; public int numImagesRemoved; + public int numTooOld; public AggregatedStats(Context context, String key) { this.key = key; @@ -535,6 +544,7 @@ public class NotificationUsageStats { maybeCount("note_over_alert_rate", (numAlertViolations - previous.numAlertViolations)); maybeCount("note_over_quota", (numQuotaViolations - previous.numQuotaViolations)); maybeCount("note_images_removed", (numImagesRemoved - previous.numImagesRemoved)); + maybeCount("not_too_old", (numTooOld - previous.numTooOld)); noisyImportance.maybeCount(previous.noisyImportance); quietImportance.maybeCount(previous.quietImportance); finalImportance.maybeCount(previous.finalImportance); @@ -570,6 +580,7 @@ public class NotificationUsageStats { previous.numAlertViolations = numAlertViolations; previous.numQuotaViolations = numQuotaViolations; previous.numImagesRemoved = numImagesRemoved; + previous.numTooOld = numTooOld; noisyImportance.update(previous.noisyImportance); quietImportance.update(previous.quietImportance); finalImportance.update(previous.finalImportance); @@ -679,6 +690,8 @@ public class NotificationUsageStats { output.append("numQuotaViolations=").append(numQuotaViolations).append("\n"); output.append(indentPlusTwo); output.append("numImagesRemoved=").append(numImagesRemoved).append("\n"); + output.append(indentPlusTwo); + output.append("numTooOld=").append(numTooOld).append("\n"); output.append(indentPlusTwo).append(noisyImportance.toString()).append("\n"); output.append(indentPlusTwo).append(quietImportance.toString()).append("\n"); output.append(indentPlusTwo).append(finalImportance.toString()).append("\n"); @@ -725,6 +738,7 @@ public class NotificationUsageStats { maybePut(dump, "notificationEnqueueRate", getEnqueueRate()); maybePut(dump, "numAlertViolations", numAlertViolations); maybePut(dump, "numImagesRemoved", numImagesRemoved); + maybePut(dump, "numTooOld", numTooOld); noisyImportance.maybePut(dump, previous.noisyImportance); quietImportance.maybePut(dump, previous.quietImportance); finalImportance.maybePut(dump, previous.finalImportance); diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig index 9dcca494ca24..bf6b6521c19a 100644 --- a/services/core/java/com/android/server/notification/flags.aconfig +++ b/services/core/java/com/android/server/notification/flags.aconfig @@ -135,3 +135,10 @@ flag { description: "This flag controls which signal is used to handle a user switch system event" bug: "337077643" } + +flag { + name: "reject_old_notifications" + namespace: "systemui" + description: "This flag does not allow notifications older than 2 weeks old to be posted" + bug: "339833083" +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java index 99401a17f83c..235e3cd7c9d2 100644 --- a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java +++ b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java @@ -16,6 +16,10 @@ package com.android.server.ondeviceintelligence; +import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.MODEL_LOADED_BUNDLE_KEY; +import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.MODEL_UNLOADED_BUNDLE_KEY; +import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.REGISTER_MODEL_UPDATE_CALLBACK_BUNDLE_KEY; + import static com.android.server.ondeviceintelligence.BundleUtil.sanitizeInferenceParams; import static com.android.server.ondeviceintelligence.BundleUtil.validatePfdReadOnly; import static com.android.server.ondeviceintelligence.BundleUtil.sanitizeStateParams; @@ -41,6 +45,7 @@ import android.app.ondeviceintelligence.ITokenInfoCallback; import android.app.ondeviceintelligence.OnDeviceIntelligenceException; import android.content.ComponentName; import android.content.Context; +import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; import android.content.res.Resources; @@ -105,12 +110,20 @@ public class OnDeviceIntelligenceManagerService extends SystemService { /** Handler message to {@link #resetTemporaryServices()} */ private static final int MSG_RESET_TEMPORARY_SERVICE = 0; + /** Handler message to clean up temporary broadcast keys. */ + private static final int MSG_RESET_BROADCAST_KEYS = 1; + /** Default value in absence of {@link DeviceConfig} override. */ private static final boolean DEFAULT_SERVICE_ENABLED = true; private static final String NAMESPACE_ON_DEVICE_INTELLIGENCE = "ondeviceintelligence"; + private static final String SYSTEM_PACKAGE = "android"; + + private final Executor resourceClosingExecutor = Executors.newCachedThreadPool(); private final Executor callbackExecutor = Executors.newCachedThreadPool(); + private final Executor broadcastExecutor = Executors.newCachedThreadPool(); + private final Context mContext; protected final Object mLock = new Object(); @@ -123,10 +136,14 @@ public class OnDeviceIntelligenceManagerService extends SystemService { @GuardedBy("mLock") private String[] mTemporaryServiceNames; + @GuardedBy("mLock") + private String[] mTemporaryBroadcastKeys; + @GuardedBy("mLock") + private String mBroadcastPackageName; + /** * Handler used to reset the temporary service names. */ - @GuardedBy("mLock") private Handler mTemporaryHandler; public OnDeviceIntelligenceManagerService(Context context) { @@ -482,6 +499,8 @@ public class OnDeviceIntelligenceManagerService extends SystemService { ensureRemoteIntelligenceServiceInitialized(); mRemoteOnDeviceIntelligenceService.run( IOnDeviceIntelligenceService::notifyInferenceServiceConnected); + broadcastExecutor.execute( + () -> registerModelLoadingBroadcasts(service)); service.registerRemoteStorageService( getIRemoteStorageService()); } catch (RemoteException ex) { @@ -493,6 +512,56 @@ public class OnDeviceIntelligenceManagerService extends SystemService { } } + private void registerModelLoadingBroadcasts(IOnDeviceSandboxedInferenceService service) { + String[] modelBroadcastKeys; + try { + modelBroadcastKeys = getBroadcastKeys(); + } catch (Resources.NotFoundException e) { + Slog.d(TAG, "Skipping model broadcasts as broadcast intents configured."); + return; + } + + Bundle bundle = new Bundle(); + bundle.putBoolean(REGISTER_MODEL_UPDATE_CALLBACK_BUNDLE_KEY, true); + try { + service.updateProcessingState(bundle, new IProcessingUpdateStatusCallback.Stub() { + @Override + public void onSuccess(PersistableBundle statusParams) { + Binder.clearCallingIdentity(); + synchronized (mLock) { + if (statusParams.containsKey(MODEL_LOADED_BUNDLE_KEY)) { + String modelLoadedBroadcastKey = modelBroadcastKeys[0]; + if (modelLoadedBroadcastKey != null + && !modelLoadedBroadcastKey.isEmpty()) { + final Intent intent = new Intent(modelLoadedBroadcastKey); + intent.setPackage(mBroadcastPackageName); + mContext.sendBroadcast(intent, + Manifest.permission.USE_ON_DEVICE_INTELLIGENCE); + } + } else if (statusParams.containsKey(MODEL_UNLOADED_BUNDLE_KEY)) { + String modelUnloadedBroadcastKey = modelBroadcastKeys[1]; + if (modelUnloadedBroadcastKey != null + && !modelUnloadedBroadcastKey.isEmpty()) { + final Intent intent = new Intent(modelUnloadedBroadcastKey); + intent.setPackage(mBroadcastPackageName); + mContext.sendBroadcast(intent, + Manifest.permission.USE_ON_DEVICE_INTELLIGENCE); + } + } + } + } + + @Override + public void onFailure(int errorCode, String errorMessage) { + Slog.e(TAG, "Failed to register model loading callback with status code", + new OnDeviceIntelligenceException(errorCode, errorMessage)); + } + }); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to register model loading callback with status code", e); + } + } + @NonNull private IRemoteStorageService.Stub getIRemoteStorageService() { return new IRemoteStorageService.Stub() { @@ -629,6 +698,20 @@ public class OnDeviceIntelligenceManagerService extends SystemService { R.string.config_defaultOnDeviceSandboxedInferenceService)}; } + protected String[] getBroadcastKeys() throws Resources.NotFoundException { + // TODO 329240495 : Consider a small class with explicit field names for the two services + synchronized (mLock) { + if (mTemporaryBroadcastKeys != null && mTemporaryBroadcastKeys.length == 2) { + return mTemporaryBroadcastKeys; + } + } + + return new String[]{mContext.getResources().getString( + R.string.config_onDeviceIntelligenceModelLoadedBroadcastKey), + mContext.getResources().getString( + R.string.config_onDeviceIntelligenceModelUnloadedBroadcastKey)}; + } + @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void setTemporaryServices(@NonNull String[] componentNames, int durationMs) { Objects.requireNonNull(componentNames); @@ -645,25 +728,26 @@ public class OnDeviceIntelligenceManagerService extends SystemService { mRemoteOnDeviceIntelligenceService.unbind(); mRemoteOnDeviceIntelligenceService = null; } - if (mTemporaryHandler == null) { - mTemporaryHandler = new Handler(Looper.getMainLooper(), null, true) { - @Override - public void handleMessage(Message msg) { - if (msg.what == MSG_RESET_TEMPORARY_SERVICE) { - synchronized (mLock) { - resetTemporaryServices(); - } - } else { - Slog.wtf(TAG, "invalid handler msg: " + msg); - } - } - }; - } else { - mTemporaryHandler.removeMessages(MSG_RESET_TEMPORARY_SERVICE); + + if (durationMs != -1) { + getTemporaryHandler().sendEmptyMessageDelayed(MSG_RESET_TEMPORARY_SERVICE, + durationMs); } + } + } + @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) + public void setModelBroadcastKeys(@NonNull String[] broadcastKeys, String receiverPackageName, + int durationMs) { + Objects.requireNonNull(broadcastKeys); + enforceShellOnly(Binder.getCallingUid(), "setModelBroadcastKeys"); + mContext.enforceCallingPermission( + Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG); + synchronized (mLock) { + mTemporaryBroadcastKeys = broadcastKeys; + mBroadcastPackageName = receiverPackageName; if (durationMs != -1) { - mTemporaryHandler.sendEmptyMessageDelayed(MSG_RESET_TEMPORARY_SERVICE, durationMs); + getTemporaryHandler().sendEmptyMessageDelayed(MSG_RESET_BROADCAST_KEYS, durationMs); } } } @@ -751,4 +835,28 @@ public class OnDeviceIntelligenceManagerService extends SystemService { } } } + + private synchronized Handler getTemporaryHandler() { + if (mTemporaryHandler == null) { + mTemporaryHandler = new Handler(Looper.getMainLooper(), null, true) { + @Override + public void handleMessage(Message msg) { + if (msg.what == MSG_RESET_TEMPORARY_SERVICE) { + synchronized (mLock) { + resetTemporaryServices(); + } + } else if (msg.what == MSG_RESET_BROADCAST_KEYS) { + synchronized (mLock) { + mTemporaryBroadcastKeys = null; + mBroadcastPackageName = SYSTEM_PACKAGE; + } + } else { + Slog.wtf(TAG, "invalid handler msg: " + msg); + } + } + }; + } + + return mTemporaryHandler; + } } diff --git a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java index a76d8a31405d..5744b5c3c2c4 100644 --- a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java +++ b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java @@ -43,6 +43,8 @@ final class OnDeviceIntelligenceShellCommand extends ShellCommand { return setTemporaryServices(); case "get-services": return getConfiguredServices(); + case "set-model-broadcasts": + return setBroadcastKeys(); default: return handleDefaultCommands(cmd); } @@ -62,12 +64,18 @@ final class OnDeviceIntelligenceShellCommand extends ShellCommand { pw.println(" To reset, call without any arguments."); pw.println(" get-services To get the names of services that are currently being used."); + pw.println( + " set-model-broadcasts [ModelLoadedBroadcastKey] [ModelUnloadedBroadcastKey] " + + "[ReceiverPackageName] " + + "[DURATION] To set the names of broadcast intent keys that are to be " + + "emitted for cts tests."); } private int setTemporaryServices() { final PrintWriter out = getOutPrintWriter(); final String intelligenceServiceName = getNextArg(); final String inferenceServiceName = getNextArg(); + if (getRemainingArgsCount() == 0 && intelligenceServiceName == null && inferenceServiceName == null) { mService.resetTemporaryServices(); @@ -79,7 +87,8 @@ final class OnDeviceIntelligenceShellCommand extends ShellCommand { Objects.requireNonNull(inferenceServiceName); final int duration = Integer.parseInt(getNextArgRequired()); mService.setTemporaryServices( - new String[]{intelligenceServiceName, inferenceServiceName}, duration); + new String[]{intelligenceServiceName, inferenceServiceName}, + duration); out.println("OnDeviceIntelligenceService temporarily set to " + intelligenceServiceName + " \n and \n OnDeviceTrustedInferenceService set to " + inferenceServiceName + " for " + duration + "ms"); @@ -93,4 +102,22 @@ final class OnDeviceIntelligenceShellCommand extends ShellCommand { + " \n and \n OnDeviceTrustedInferenceService set to : " + services[1]); return 0; } + + private int setBroadcastKeys() { + final PrintWriter out = getOutPrintWriter(); + final String modelLoadedKey = getNextArgRequired(); + final String modelUnloadedKey = getNextArgRequired(); + final String receiverPackageName = getNextArg(); + + final int duration = Integer.parseInt(getNextArgRequired()); + mService.setModelBroadcastKeys( + new String[]{modelLoadedKey, modelUnloadedKey}, receiverPackageName, duration); + out.println("OnDeviceIntelligence Model Loading broadcast keys temporarily set to " + + modelLoadedKey + + " \n and \n OnDeviceTrustedInferenceService set to " + modelUnloadedKey + + "\n and Package name set to : " + receiverPackageName + + " for " + duration + "ms"); + return 0; + } + }
\ No newline at end of file diff --git a/services/core/java/com/android/server/pm/AppDataHelper.java b/services/core/java/com/android/server/pm/AppDataHelper.java index 9ba88aa18ce6..fe774aa75efc 100644 --- a/services/core/java/com/android/server/pm/AppDataHelper.java +++ b/services/core/java/com/android/server/pm/AppDataHelper.java @@ -504,9 +504,12 @@ public class AppDataHelper { } else { storageFlags = StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE; } - List<String> deferPackages = reconcileAppsDataLI(StorageManager.UUID_PRIVATE_INTERNAL, - UserHandle.USER_SYSTEM, storageFlags, true /* migrateAppData */, - true /* onlyCoreApps */); + final List<String> deferPackages; + synchronized (mPm.mInstallLock) { + deferPackages = reconcileAppsDataLI(StorageManager.UUID_PRIVATE_INTERNAL, + UserHandle.USER_SYSTEM, storageFlags, true /* migrateAppData */, + true /* onlyCoreApps */); + } Future<?> prepareAppDataFuture = SystemServerInitThreadPool.submit(() -> { TimingsTraceLog traceLog = new TimingsTraceLog("SystemServerTimingAsync", Trace.TRACE_TAG_PACKAGE_MANAGER); diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 19a0ba796343..908b47df35e1 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -985,13 +985,13 @@ final class InstallPackageHelper { } void installPackagesTraced(List<InstallRequest> requests) { - synchronized (mPm.mInstallLock) { - try { - Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installPackages"); - installPackagesLI(requests); - } finally { - Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); - } + mPm.mInstallLock.lock(); + try { + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installPackages"); + installPackagesLI(requests); + } finally { + Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); + mPm.mInstallLock.unlock(); } } @@ -2590,22 +2590,30 @@ final class InstallPackageHelper { final boolean performDexopt = DexOptHelper.shouldPerformDexopt(installRequest, dexoptOptions, mContext); if (performDexopt) { - Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt"); + // dexopt can take long, and ArtService doesn't require installd, so we release + // the lock here and re-acquire the lock after dexopt is finished. + mPm.mInstallLock.unlock(); + try { + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt"); - // This mirrors logic from commitReconciledScanResultLocked, where the library files - // needed for dexopt are assigned. - PackageSetting realPkgSetting = installRequest.getRealPackageSetting(); + // This mirrors logic from commitReconciledScanResultLocked, where the library + // files needed for dexopt are assigned. + PackageSetting realPkgSetting = installRequest.getRealPackageSetting(); - // Unfortunately, the updated system app flag is only tracked on this PackageSetting - boolean isUpdatedSystemApp = - installRequest.getScannedPackageSetting().isUpdatedSystemApp(); + // Unfortunately, the updated system app flag is only tracked on this + // PackageSetting + boolean isUpdatedSystemApp = + installRequest.getScannedPackageSetting().isUpdatedSystemApp(); - realPkgSetting.getPkgState().setUpdatedSystemApp(isUpdatedSystemApp); + realPkgSetting.getPkgState().setUpdatedSystemApp(isUpdatedSystemApp); - DexoptResult dexOptResult = - DexOptHelper.dexoptPackageUsingArtService(installRequest, dexoptOptions); - installRequest.onDexoptFinished(dexOptResult); - Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); + DexoptResult dexOptResult = DexOptHelper.dexoptPackageUsingArtService( + installRequest, dexoptOptions); + installRequest.onDexoptFinished(dexOptResult); + Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); + } finally { + mPm.mInstallLock.lock(); + } } } PackageManagerServiceUtils.waitForNativeBinariesExtractionForIncremental( diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index ae485ede1bec..121cf3f231b0 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -626,7 +626,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService // Lock for state used when installing and doing other long running // operations. Methods that must be called with this lock held have // the suffix "LI". - final Object mInstallLock; + final PackageManagerTracedLock mInstallLock; // ---------------------------------------------------------------- @@ -1692,8 +1692,8 @@ public class PackageManagerService implements PackageSender, TestUtilityService final TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG + "Timing", Trace.TRACE_TAG_PACKAGE_MANAGER); t.traceBegin("create package manager"); - final PackageManagerTracedLock lock = new PackageManagerTracedLock(); - final Object installLock = new Object(); + final PackageManagerTracedLock lock = new PackageManagerTracedLock("mLock"); + final PackageManagerTracedLock installLock = new PackageManagerTracedLock("mInstallLock"); HandlerThread backgroundThread = new ServiceThread("PackageManagerBg", Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/); diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java index 83f3b16b31d1..ae2eaeb31f81 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java @@ -86,7 +86,7 @@ public class PackageManagerServiceInjector { private final Context mContext; private final PackageManagerTracedLock mLock; private final Installer mInstaller; - private final Object mInstallLock; + private final PackageManagerTracedLock mInstallLock; private final Handler mBackgroundHandler; private final Executor mBackgroundExecutor; private final List<ScanPartition> mSystemPartitions; @@ -144,7 +144,7 @@ public class PackageManagerServiceInjector { private final Singleton<PackageMonitorCallbackHelper> mPackageMonitorCallbackHelper; PackageManagerServiceInjector(Context context, PackageManagerTracedLock lock, - Installer installer, Object installLock, PackageAbiHelper abiHelper, + Installer installer, PackageManagerTracedLock installLock, PackageAbiHelper abiHelper, Handler backgroundHandler, List<ScanPartition> systemPartitions, Producer<ComponentResolver> componentResolverProducer, @@ -254,7 +254,7 @@ public class PackageManagerServiceInjector { return mAbiHelper; } - public Object getInstallLock() { + public PackageManagerTracedLock getInstallLock() { return mInstallLock; } diff --git a/services/core/java/com/android/server/pm/PackageManagerTracedLock.java b/services/core/java/com/android/server/pm/PackageManagerTracedLock.java index 75e1803f1695..303b8b9dd817 100644 --- a/services/core/java/com/android/server/pm/PackageManagerTracedLock.java +++ b/services/core/java/com/android/server/pm/PackageManagerTracedLock.java @@ -16,6 +16,9 @@ package com.android.server.pm; +import android.annotation.Nullable; +import android.util.Slog; + import java.util.concurrent.locks.ReentrantLock; /** @@ -23,4 +26,31 @@ import java.util.concurrent.locks.ReentrantLock; * injection, similar to {@link ActivityManagerGlobalLock}. */ public class PackageManagerTracedLock extends ReentrantLock { + private static final String TAG = "PackageManagerTracedLock"; + private static final boolean DEBUG = false; + @Nullable private final String mLockName; + + public PackageManagerTracedLock(@Nullable String lockName) { + mLockName = lockName; + } + + public PackageManagerTracedLock() { + this(null); + } + + @Override + public void lock() { + super.lock(); + if (DEBUG && mLockName != null) { + Slog.i(TAG, "locked " + mLockName); + } + } + + @Override + public void unlock() { + super.unlock(); + if (DEBUG && mLockName != null) { + Slog.i(TAG, "unlocked " + mLockName); + } + } } diff --git a/services/core/java/com/android/server/pm/UserDataPreparer.java b/services/core/java/com/android/server/pm/UserDataPreparer.java index 1d414011cff3..ef32485567f8 100644 --- a/services/core/java/com/android/server/pm/UserDataPreparer.java +++ b/services/core/java/com/android/server/pm/UserDataPreparer.java @@ -52,11 +52,11 @@ class UserDataPreparer { private static final String TAG = "UserDataPreparer"; private static final String XATTR_SERIAL = "user.serial"; - private final Object mInstallLock; + private final PackageManagerTracedLock mInstallLock; private final Context mContext; private final Installer mInstaller; - UserDataPreparer(Installer installer, Object installLock, Context context) { + UserDataPreparer(Installer installer, PackageManagerTracedLock installLock, Context context) { mInstallLock = installLock; mContext = context; mInstaller = installer; diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index 9e16b8abe0de..57827c567b5b 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -989,6 +989,10 @@ final class LetterboxUiController { } } + boolean isLetterboxEducationEnabled() { + return mLetterboxConfiguration.getIsEducationEnabled(); + } + /** * Whether we use split screen aspect ratio for the activity when camera compat treatment * is active because the corresponding config is enabled and activity supports resizing. diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 8bd7b5f78cf4..8defec3dbeab 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -3448,6 +3448,8 @@ class Task extends TaskFragment { // Whether the direct top activity is eligible for letterbox education. appCompatTaskInfo.topActivityEligibleForLetterboxEducation = isTopActivityResumed && top.isEligibleForLetterboxEducation(); + appCompatTaskInfo.isLetterboxEducationEnabled = top != null + && top.mLetterboxUiController.isLetterboxEducationEnabled(); // Whether the direct top activity requested showing camera compat control. appCompatTaskInfo.cameraCompatTaskInfo.cameraCompatControlState = isTopActivityResumed ? top.getCameraCompatControlState() diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index a01c1231b373..74ca9ad687ea 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -423,7 +423,7 @@ private: std::set<int32_t> disabledInputDevices{}; // Associated Pointer controller display. - ui::LogicalDisplayId pointerDisplayId{ui::ADISPLAY_ID_DEFAULT}; + ui::LogicalDisplayId pointerDisplayId{ui::LogicalDisplayId::DEFAULT}; // True if stylus button reporting through motion events is enabled. bool stylusButtonMotionEventsEnabled{true}; @@ -1886,7 +1886,7 @@ static jobject nativeCreateInputMonitor(JNIEnv* env, jobject nativeImplObj, jint jstring nameObj, jint pid) { NativeInputManager* im = getNativeInputManager(env, nativeImplObj); - if (displayId == ui::ADISPLAY_ID_NONE.val()) { + if (ui::LogicalDisplayId{displayId} == ui::LogicalDisplayId::INVALID) { std::string message = "InputChannel used as a monitor must be associated with a display"; jniThrowRuntimeException(env, message.c_str()); return nullptr; @@ -2727,6 +2727,11 @@ static void nativeSetInputMethodConnectionIsActive(JNIEnv* env, jobject nativeIm im->setInputMethodConnectionIsActive(isActive); } +static jint nativeGetLastUsedInputDeviceId(JNIEnv* env, jobject nativeImplObj) { + NativeInputManager* im = getNativeInputManager(env, nativeImplObj); + return static_cast<jint>(im->getInputManager()->getReader().getLastUsedInputDeviceId()); +} + // ---------------------------------------------------------------------------- static const JNINativeMethod gInputManagerMethods[] = { @@ -2835,6 +2840,7 @@ static const JNINativeMethod gInputManagerMethods[] = { {"setAccessibilityStickyKeysEnabled", "(Z)V", (void*)nativeSetAccessibilityStickyKeysEnabled}, {"setInputMethodConnectionIsActive", "(Z)V", (void*)nativeSetInputMethodConnectionIsActive}, + {"getLastUsedInputDeviceId", "()I", (void*)nativeGetLastUsedInputDeviceId}, }; #define FIND_CLASS(var, className) \ diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index f75803f00f0b..2b93d2132a8f 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -339,6 +339,7 @@ import android.app.admin.ManagedProfileProvisioningParams; import android.app.admin.ManagedSubscriptionsPolicy; import android.app.admin.NetworkEvent; import android.app.admin.PackagePolicy; +import android.app.admin.PackageSetPolicyValue; import android.app.admin.ParcelableGranteeMap; import android.app.admin.ParcelableResource; import android.app.admin.PasswordMetrics; @@ -349,7 +350,6 @@ import android.app.admin.PolicyValue; import android.app.admin.PreferentialNetworkServiceConfig; import android.app.admin.SecurityLog; import android.app.admin.SecurityLog.SecurityEvent; -import android.app.admin.PackageSetPolicyValue; import android.app.admin.StartInstallingUpdateCallback; import android.app.admin.SystemUpdateInfo; import android.app.admin.SystemUpdatePolicy; @@ -2718,6 +2718,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { mDevicePolicyEngine.getResolvedPolicy( PolicyDefinition.SECURITY_LOGGING, UserHandle.USER_ALL)); setLoggingConfiguration(securityLoggingEnabled, auditLoggingEnabled); + mInjector.runCryptoSelfTest(); } else { synchronized (getLockObject()) { mSecurityLogMonitor.start(getSecurityLoggingEnabledUser()); diff --git a/services/people/java/com/android/server/people/TEST_MAPPING b/services/people/java/com/android/server/people/TEST_MAPPING new file mode 100644 index 000000000000..55b355cbc991 --- /dev/null +++ b/services/people/java/com/android/server/people/TEST_MAPPING @@ -0,0 +1,12 @@ +{ + "presubmit": [ + { + "name": "FrameworksServicesTests", + "options": [ + { + "include-filter": "com.android.server.people.data" + } + ] + } + ] +}
\ No newline at end of file diff --git a/services/robotests/backup/src/com/android/server/backup/transport/TransportConnectionTest.java b/services/robotests/backup/src/com/android/server/backup/transport/TransportConnectionTest.java index 6a82f1656414..3e87c6fe7be7 100644 --- a/services/robotests/backup/src/com/android/server/backup/transport/TransportConnectionTest.java +++ b/services/robotests/backup/src/com/android/server/backup/transport/TransportConnectionTest.java @@ -28,6 +28,7 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -543,6 +544,18 @@ public class TransportConnectionTest { return future.get(); } + @Test + public void onBindingDied_referenceLost_doesNotThrow() { + TransportConnection.TransportConnectionMonitor transportConnectionMonitor = + new TransportConnection.TransportConnectionMonitor( + mContext, /* transportConnection= */ null); + doThrow(new IllegalArgumentException("Service not registered")).when( + mContext).unbindService(any()); + + // Test no exception is thrown + transportConnectionMonitor.onBindingDied(mTransportComponent); + } + private ServiceConnection verifyBindServiceAsUserAndCaptureServiceConnection(Context context) { ArgumentCaptor<ServiceConnection> connectionCaptor = ArgumentCaptor.forClass(ServiceConnection.class); diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java index 9e11fa2f0bdf..e545a49d3139 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java @@ -71,7 +71,7 @@ public class UserDataPreparerTest { @Mock private Installer mInstaller; - private Object mInstallLock; + private PackageManagerTracedLock mInstallLock; @Before public void setup() { @@ -79,7 +79,7 @@ public class UserDataPreparerTest { TEST_USER.serialNumber = TEST_USER_SERIAL; Context ctx = InstrumentationRegistry.getContext(); FileUtils.deleteContents(ctx.getCacheDir()); - mInstallLock = new Object(); + mInstallLock = new PackageManagerTracedLock(); MockitoAnnotations.initMocks(this); mUserDataPreparer = new TestUserDataPreparer(mInstaller, mInstallLock, mContextMock, ctx.getCacheDir()); @@ -238,8 +238,8 @@ public class UserDataPreparerTest { private static class TestUserDataPreparer extends UserDataPreparer { File testDir; - TestUserDataPreparer(Installer installer, Object installLock, Context context, - File testDir) { + TestUserDataPreparer(Installer installer, PackageManagerTracedLock installLock, + Context context, File testDir) { super(installer, installLock, context); this.testDir = testDir; } diff --git a/services/tests/apexsystemservices/OWNERS b/services/tests/apexsystemservices/OWNERS index 0295b9e99326..8b6675ad22d7 100644 --- a/services/tests/apexsystemservices/OWNERS +++ b/services/tests/apexsystemservices/OWNERS @@ -1,4 +1 @@ -omakoto@google.com -satayev@google.com - include platform/packages/modules/common:/OWNERS diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java index ae6361bdd556..df9671235071 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java @@ -46,6 +46,7 @@ import com.android.server.display.brightness.strategy.AutomaticBrightnessStrateg import com.android.server.display.brightness.strategy.BoostBrightnessStrategy; import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy; import com.android.server.display.brightness.strategy.DozeBrightnessStrategy; +import com.android.server.display.brightness.strategy.FallbackBrightnessStrategy; import com.android.server.display.brightness.strategy.FollowerBrightnessStrategy; import com.android.server.display.brightness.strategy.InvalidBrightnessStrategy; import com.android.server.display.brightness.strategy.OffloadBrightnessStrategy; @@ -90,6 +91,8 @@ public final class DisplayBrightnessStrategySelectorTest { @Mock private AutoBrightnessFallbackStrategy mAutoBrightnessFallbackStrategy; @Mock + private FallbackBrightnessStrategy mFallbackBrightnessStrategy; + @Mock private Resources mResources; @Mock private DisplayManagerFlags mDisplayManagerFlags; @@ -135,7 +138,7 @@ public final class DisplayBrightnessStrategySelectorTest { @Override AutomaticBrightnessStrategy getAutomaticBrightnessStrategy1(Context context, - int displayId) { + int displayId, DisplayManagerFlags displayManagerFlags) { return mAutomaticBrightnessStrategy; } @@ -155,6 +158,11 @@ public final class DisplayBrightnessStrategySelectorTest { AutoBrightnessFallbackStrategy getAutoBrightnessFallbackStrategy() { return mAutoBrightnessFallbackStrategy; } + + @Override + FallbackBrightnessStrategy getFallbackBrightnessStrategy() { + return mFallbackBrightnessStrategy; + } }; @Rule @@ -355,6 +363,25 @@ public final class DisplayBrightnessStrategySelectorTest { } @Test + public void selectStrategy_selectsFallbackStrategyAsAnUltimateFallback() { + when(mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()).thenReturn(true); + mDisplayBrightnessStrategySelector = new DisplayBrightnessStrategySelector(mContext, + mInjector, DISPLAY_ID, mDisplayManagerFlags); + DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock( + DisplayManagerInternal.DisplayPowerRequest.class); + displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT; + displayPowerRequest.screenBrightnessOverride = Float.NaN; + when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN); + when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(Float.NaN); + when(mAutomaticBrightnessStrategy.shouldUseAutoBrightness()).thenReturn(false); + when(mAutomaticBrightnessStrategy.isAutoBrightnessValid()).thenReturn(false); + assertEquals(mDisplayBrightnessStrategySelector.selectStrategy( + new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON, + 0.1f, false)), + mFallbackBrightnessStrategy); + } + + @Test public void selectStrategyCallsPostProcessorForAllStrategies() { when(mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()).thenReturn(true); mDisplayBrightnessStrategySelector = new DisplayBrightnessStrategySelector(mContext, diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java index 3e78118cd5df..19bff56a4b29 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java @@ -18,8 +18,10 @@ package com.android.server.display.brightness.strategy; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -45,6 +47,7 @@ import com.android.server.display.DisplayBrightnessState; import com.android.server.display.brightness.BrightnessEvent; import com.android.server.display.brightness.BrightnessReason; import com.android.server.display.brightness.StrategyExecutionRequest; +import com.android.server.display.feature.DisplayManagerFlags; import org.junit.After; import org.junit.Before; @@ -64,6 +67,9 @@ public class AutomaticBrightnessStrategyTest { @Mock private AutomaticBrightnessController mAutomaticBrightnessController; + @Mock + private DisplayManagerFlags mDisplayManagerFlags; + private BrightnessConfiguration mBrightnessConfiguration; private float mDefaultScreenAutoBrightnessAdjustment; private Context mContext; @@ -80,7 +86,8 @@ public class AutomaticBrightnessStrategyTest { Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, Float.NaN); Settings.System.putFloat(mContext.getContentResolver(), Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.5f); - mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy(mContext, DISPLAY_ID); + mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy(mContext, DISPLAY_ID, + mDisplayManagerFlags); mBrightnessConfiguration = new BrightnessConfiguration.Builder( new float[]{0f, 1f}, new float[]{0, PowerManager.BRIGHTNESS_ON}).build(); @@ -247,6 +254,46 @@ public class AutomaticBrightnessStrategyTest { } @Test + public void testAutoBrightnessState_modeSwitch() { + // Setup the test + when(mDisplayManagerFlags.areAutoBrightnessModesEnabled()).thenReturn(true); + mAutomaticBrightnessStrategy.setUseAutoBrightness(true); + boolean allowAutoBrightnessWhileDozing = false; + int brightnessReason = BrightnessReason.REASON_UNKNOWN; + float lastUserSetBrightness = 0.2f; + boolean userSetBrightnessChanged = true; + int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT; + float pendingBrightnessAdjustment = 0.1f; + Settings.System.putFloat(mContext.getContentResolver(), + Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, pendingBrightnessAdjustment); + mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(); + + // Validate no interaction when automaticBrightnessController is in idle mode + when(mAutomaticBrightnessController.isInIdleMode()).thenReturn(true); + mAutomaticBrightnessStrategy.setAutoBrightnessState(Display.STATE_ON, + allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness, + userSetBrightnessChanged); + verify(mAutomaticBrightnessController, never()).switchMode(anyInt()); + + // Validate interaction when automaticBrightnessController is in non-idle mode, and display + // state is ON + when(mAutomaticBrightnessController.isInIdleMode()).thenReturn(false); + mAutomaticBrightnessStrategy.setAutoBrightnessState(Display.STATE_ON, + allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness, + userSetBrightnessChanged); + verify(mAutomaticBrightnessController).switchMode( + AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT); + + // Validate interaction when automaticBrightnessController is in non-idle mode, and display + // state is DOZE + mAutomaticBrightnessStrategy.setAutoBrightnessState(Display.STATE_DOZE, + allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness, + userSetBrightnessChanged); + verify(mAutomaticBrightnessController).switchMode( + AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE); + } + + @Test public void accommodateUserBrightnessChangesWorksAsExpected() { // Verify the state if automaticBrightnessController is configured. assertFalse(mAutomaticBrightnessStrategy.isShortTermModelActive()); @@ -390,7 +437,8 @@ public class AutomaticBrightnessStrategyTest { @Test public void testVerifyNoAutoBrightnessAdjustmentsArePopulatedForNonDefaultDisplay() { int newDisplayId = 1; - mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy(mContext, newDisplayId); + mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy(mContext, newDisplayId, + mDisplayManagerFlags); mAutomaticBrightnessStrategy.putAutoBrightnessAdjustmentSetting(0.3f); assertEquals(0.5f, mAutomaticBrightnessStrategy.getAutoBrightnessAdjustment(), 0.0f); @@ -429,8 +477,7 @@ public class AutomaticBrightnessStrategyTest { updateBrightness_constructsDisplayBrightnessState_withAdjustmentAutoAdjustmentFlag() { BrightnessEvent brightnessEvent = new BrightnessEvent(DISPLAY_ID); mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy( - mContext, DISPLAY_ID, displayId -> brightnessEvent); - new AutomaticBrightnessStrategy(mContext, DISPLAY_ID); + mContext, DISPLAY_ID, displayId -> brightnessEvent, mDisplayManagerFlags); mAutomaticBrightnessStrategy.setAutomaticBrightnessController( mAutomaticBrightnessController); float brightness = 0.4f; @@ -461,8 +508,7 @@ public class AutomaticBrightnessStrategyTest { updateBrightness_constructsDisplayBrightnessState_withAdjustmentTempAdjustmentFlag() { BrightnessEvent brightnessEvent = new BrightnessEvent(DISPLAY_ID); mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy( - mContext, DISPLAY_ID, displayId -> brightnessEvent); - new AutomaticBrightnessStrategy(mContext, DISPLAY_ID); + mContext, DISPLAY_ID, displayId -> brightnessEvent, mDisplayManagerFlags); mAutomaticBrightnessStrategy.setAutomaticBrightnessController( mAutomaticBrightnessController); float brightness = 0.4f; diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FallbackBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FallbackBrightnessStrategyTest.java new file mode 100644 index 000000000000..c4767ae5172b --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FallbackBrightnessStrategyTest.java @@ -0,0 +1,66 @@ +/* + * 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.display.brightness.strategy; + + +import static org.junit.Assert.assertEquals; + +import android.hardware.display.DisplayManagerInternal; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.display.DisplayBrightnessState; +import com.android.server.display.brightness.BrightnessReason; +import com.android.server.display.brightness.StrategyExecutionRequest; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidJUnit4.class) + +public class FallbackBrightnessStrategyTest { + private FallbackBrightnessStrategy mFallbackBrightnessStrategy; + + @Before + public void before() { + mFallbackBrightnessStrategy = new FallbackBrightnessStrategy(); + } + + @Test + public void updateBrightness_currentBrightnessIsSet() { + DisplayManagerInternal.DisplayPowerRequest + displayPowerRequest = new DisplayManagerInternal.DisplayPowerRequest(); + float currentBrightness = 0.2f; + BrightnessReason brightnessReason = new BrightnessReason(); + brightnessReason.setReason(BrightnessReason.REASON_MANUAL); + DisplayBrightnessState expectedDisplayBrightnessState = + new DisplayBrightnessState.Builder() + .setBrightness(currentBrightness) + .setBrightnessReason(brightnessReason) + .setSdrBrightness(currentBrightness) + .setDisplayBrightnessStrategyName(mFallbackBrightnessStrategy.getName()) + .setShouldUpdateScreenBrightnessSetting(true) + .build(); + DisplayBrightnessState updatedDisplayBrightnessState = + mFallbackBrightnessStrategy.updateBrightness( + new StrategyExecutionRequest(displayPowerRequest, currentBrightness)); + assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState); + } +} diff --git a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamControllerTest.java b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamControllerTest.java index 88ab871529ee..874e99173c63 100644 --- a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamControllerTest.java +++ b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamControllerTest.java @@ -273,28 +273,36 @@ public class DreamControllerTest { } @Test - public void setDreamHasFocus_true_dreamHasFocus() { + public void setDreamIsObscured_true_dreamIsNotFrontmost() { mDreamController.startDream(mToken, mDreamName, false /*isPreview*/, false /*doze*/, 0 /*userId*/, null /*wakeLock*/, mOverlayName, "test" /*reason*/); - mDreamController.setDreamHasFocus(true); - assertTrue(mDreamController.dreamHasFocus()); + mDreamController.setDreamIsObscured(true); + assertFalse(mDreamController.dreamIsFrontmost()); } @Test - public void setDreamHasFocus_false_dreamDoesNotHaveFocus() { + public void setDreamIsObscured_false_dreamIsFrontmost() { mDreamController.startDream(mToken, mDreamName, false /*isPreview*/, false /*doze*/, 0 /*userId*/, null /*wakeLock*/, mOverlayName, "test" /*reason*/); - mDreamController.setDreamHasFocus(false); - assertFalse(mDreamController.dreamHasFocus()); + mDreamController.setDreamIsObscured(false); + assertTrue(mDreamController.dreamIsFrontmost()); } @Test - public void setDreamHasFocus_notDreaming_dreamDoesNotHaveFocus() { - mDreamController.setDreamHasFocus(true); - // Dream still doesn't have focus because it was never started. - assertFalse(mDreamController.dreamHasFocus()); + public void setDreamIsObscured_notDreaming_dreamIsNotFrontmost() { + mDreamController.setDreamIsObscured(true); + // Dream still isn't frontmost because it was never started. + assertFalse(mDreamController.dreamIsFrontmost()); + } + + @Test + public void startDream_dreamIsFrontmost() { + mDreamController.startDream(mToken, mDreamName, false /*isPreview*/, false /*doze*/, + 0 /*userId*/, null /*wakeLock*/, mOverlayName, "test" /*reason*/); + + assertTrue(mDreamController.dreamIsFrontmost()); } private ServiceConnection captureServiceConnection() { 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 c9aab5318840..396edae2f672 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt @@ -186,7 +186,7 @@ class MockSystem(withSession: (StaticMockitoSessionBuilder) -> Unit = {}) { class Mocks { val lock = PackageManagerTracedLock() - val installLock = Any() + val installLock = PackageManagerTracedLock() val injector: PackageManagerServiceInjector = mock() val systemWrapper: PackageManagerServiceInjector.SystemWrapper = mock() val context: Context = mock() diff --git a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java index 9fc46c563841..2f3bca031f46 100644 --- a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java @@ -1786,10 +1786,10 @@ public final class DataManagerTest { /* matchesInterruptionFilter= */ false, /* visibilityOverride= */ 0, /* suppressedVisualEffects= */ 0, - mParentNotificationChannel.getImportance(), + mNotificationChannel.getImportance(), /* explanation= */ null, /* overrideGroupKey= */ null, - mParentNotificationChannel, + mNotificationChannel, /* overridePeople= */ null, /* snoozeCriteria= */ null, /* showBadge= */ true, diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 2d672b89662f..200952c05610 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -112,6 +112,7 @@ import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER; import static com.android.server.am.PendingIntentRecord.FLAG_BROADCAST_SENDER; import static com.android.server.am.PendingIntentRecord.FLAG_SERVICE_SENDER; import static com.android.server.notification.Flags.FLAG_ALL_NOTIFS_NEED_TTL; +import static com.android.server.notification.Flags.FLAG_REJECT_OLD_NOTIFICATIONS; import static com.android.server.notification.NotificationManagerService.BITMAP_DURATION; import static com.android.server.notification.NotificationManagerService.DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE; import static com.android.server.notification.NotificationManagerService.NOTIFICATION_TTL; @@ -339,6 +340,7 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; +import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -909,7 +911,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } mService.clearNotifications(); - TestableLooper.get(this).processAllMessages(); + if (mTestableLooper != null) { + mTestableLooper.processAllMessages(); + } try { mService.onDestroy(); @@ -920,14 +924,16 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { InstrumentationRegistry.getInstrumentation() .getUiAutomation().dropShellPermissionIdentity(); - // Remove scheduled messages that would be processed when the test is already done, and - // could cause issues, for example, messages that remove/cancel shown toasts (this causes - // problematic interactions with mocks when they're no longer working as expected). - mWorkerHandler.removeCallbacksAndMessages(null); + if (mWorkerHandler != null) { + // Remove scheduled messages that would be processed when the test is already done, and + // could cause issues, for example, messages that remove/cancel shown toasts (this causes + // problematic interactions with mocks when they're no longer working as expected). + mWorkerHandler.removeCallbacksAndMessages(null); + } - if (TestableLooper.get(this) != null) { + if (mTestableLooper != null) { // Must remove static reference to this test object to prevent leak (b/261039202) - TestableLooper.remove(this); + mTestableLooper.remove(this); } } @@ -1009,7 +1015,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } public void waitForIdle() { - mTestableLooper.processAllMessages(); + if (mTestableLooper != null) { + mTestableLooper.processAllMessages(); + } } private void setUpPrefsForBubbles(String pkg, int uid, boolean globalEnabled, @@ -1302,6 +1310,106 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { return nrSummary; } + private NotificationRecord createAndPostCallStyleNotification(String packageName, + UserHandle userHandle, String testName) throws Exception { + Person person = new Person.Builder().setName("caller").build(); + Notification.Builder nb = new Notification.Builder(mContext, + mTestNotificationChannel.getId()) + .setFlag(FLAG_USER_INITIATED_JOB, true) + .setStyle(Notification.CallStyle.forOngoingCall(person, mActivityIntent)) + .setSmallIcon(android.R.drawable.sym_def_app_icon); + StatusBarNotification sbn = new StatusBarNotification(packageName, packageName, 1, + testName, mUid, 0, nb.build(), userHandle, null, 0); + NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + + mService.addEnqueuedNotification(r); + mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), + r.getUid(), mPostNotificationTrackerFactory.newTracker(null)).run(); + waitForIdle(); + + return mService.findNotificationLocked( + packageName, r.getSbn().getTag(), r.getSbn().getId(), r.getSbn().getUserId()); + } + + private NotificationRecord createAndPostNotification(Notification.Builder nb, String testName) + throws RemoteException { + StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 1, testName, mUid, 0, + nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0); + NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + + mBinderService.enqueueNotificationWithTag(mPkg, mPkg, sbn.getTag(), + nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); + waitForIdle(); + + return mService.findNotificationLocked( + mPkg, nr.getSbn().getTag(), nr.getSbn().getId(), nr.getSbn().getUserId()); + } + + private static <T extends Parcelable> T parcelAndUnparcel(T source, + Parcelable.Creator<T> creator) { + Parcel parcel = Parcel.obtain(); + source.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + return creator.createFromParcel(parcel); + } + + private PendingIntent createPendingIntent(String action) { + return PendingIntent.getActivity(mContext, 0, + new Intent(action).setPackage(mContext.getPackageName()), + PendingIntent.FLAG_MUTABLE); + } + + private void allowTestPackageToToast() throws Exception { + assertWithMessage("toast queue").that(mService.mToastQueue).isEmpty(); + mService.isSystemUid = false; + mService.isSystemAppId = false; + setToastRateIsWithinQuota(true); + setIfPackageHasPermissionToAvoidToastRateLimiting(TEST_PACKAGE, false); + // package is not suspended + when(mPackageManager.isPackageSuspendedForUser(TEST_PACKAGE, mUserId)) + .thenReturn(false); + } + + private boolean enqueueToast(String testPackage, ITransientNotification callback) + throws RemoteException { + return enqueueToast((INotificationManager) mService.mService, testPackage, new Binder(), + callback); + } + + private boolean enqueueToast(INotificationManager service, String testPackage, + IBinder token, ITransientNotification callback) throws RemoteException { + return service.enqueueToast(testPackage, token, callback, TOAST_DURATION, /* isUiContext= */ + true, DEFAULT_DISPLAY); + } + + private boolean enqueueTextToast(String testPackage, CharSequence text) throws RemoteException { + return enqueueTextToast(testPackage, text, /* isUiContext= */ true, DEFAULT_DISPLAY); + } + + private boolean enqueueTextToast(String testPackage, CharSequence text, boolean isUiContext, + int displayId) throws RemoteException { + return ((INotificationManager) mService.mService).enqueueTextToast(testPackage, + new Binder(), text, TOAST_DURATION, isUiContext, displayId, + /* textCallback= */ null); + } + + private void mockIsVisibleBackgroundUsersSupported(boolean supported) { + when(mUm.isVisibleBackgroundUsersSupported()).thenReturn(supported); + } + + private void mockIsUserVisible(int displayId, boolean visible) { + when(mUmInternal.isUserVisible(mUserId, displayId)).thenReturn(visible); + } + + private void mockDisplayAssignedToUser(int displayId) { + when(mUmInternal.getMainDisplayAssignedToUser(mUserId)).thenReturn(displayId); + } + + private void verifyToastShownForTestPackage(String text, int displayId) { + verify(mStatusBar).showToast(eq(mUid), eq(TEST_PACKAGE), any(), eq(text), any(), + eq(TOAST_DURATION), any(), eq(displayId)); + } + @Test @DisableFlags(FLAG_ALL_NOTIFS_NEED_TTL) public void testLimitTimeOutBroadcast() { @@ -14069,11 +14177,12 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void enqueueUpdate_whenBelowMaxEnqueueRate_accepts() throws Exception { // Post the first version. Notification original = generateNotificationRecord(null).getNotification(); - original.when = 111; + original.when = System.currentTimeMillis(); mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 0, original, mUserId); waitForIdle(); assertThat(mService.mNotificationList).hasSize(1); - assertThat(mService.mNotificationList.get(0).getNotification().when).isEqualTo(111); + assertThat(mService.mNotificationList.get(0).getNotification().when) + .isEqualTo(original.when); reset(mUsageStats); when(mUsageStats.getAppEnqueueRate(eq(mPkg))) @@ -14081,7 +14190,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // Post the update. Notification update = generateNotificationRecord(null).getNotification(); - update.when = 222; + update.when = System.currentTimeMillis() + 111; mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 0, update, mUserId); waitForIdle(); @@ -14090,18 +14199,19 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { verify(mUsageStats, never()).registerPostedByApp(any()); verify(mUsageStats).registerUpdatedByApp(any(), any()); assertThat(mService.mNotificationList).hasSize(1); - assertThat(mService.mNotificationList.get(0).getNotification().when).isEqualTo(222); + assertThat(mService.mNotificationList.get(0).getNotification().when).isEqualTo(update.when); } @Test public void enqueueUpdate_whenAboveMaxEnqueueRate_rejects() throws Exception { // Post the first version. Notification original = generateNotificationRecord(null).getNotification(); - original.when = 111; + original.when = System.currentTimeMillis(); mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 0, original, mUserId); waitForIdle(); assertThat(mService.mNotificationList).hasSize(1); - assertThat(mService.mNotificationList.get(0).getNotification().when).isEqualTo(111); + assertThat(mService.mNotificationList.get(0).getNotification().when) + .isEqualTo(original.when); reset(mUsageStats); when(mUsageStats.getAppEnqueueRate(eq(mPkg))) @@ -14109,7 +14219,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // Post the update. Notification update = generateNotificationRecord(null).getNotification(); - update.when = 222; + update.when = System.currentTimeMillis() + 111; mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 0, update, mUserId); waitForIdle(); @@ -14118,7 +14228,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { verify(mUsageStats, never()).registerPostedByApp(any()); verify(mUsageStats, never()).registerUpdatedByApp(any(), any()); assertThat(mService.mNotificationList).hasSize(1); - assertThat(mService.mNotificationList.get(0).getNotification().when).isEqualTo(111); // old + assertThat(mService.mNotificationList.get(0).getNotification().when) + .isEqualTo(original.when); // old } @Test @@ -15483,103 +15594,48 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertThat(n.getTimeoutAfter()).isEqualTo(20); } - private NotificationRecord createAndPostCallStyleNotification(String packageName, - UserHandle userHandle, String testName) throws Exception { - Person person = new Person.Builder().setName("caller").build(); - Notification.Builder nb = new Notification.Builder(mContext, - mTestNotificationChannel.getId()) - .setFlag(FLAG_USER_INITIATED_JOB, true) - .setStyle(Notification.CallStyle.forOngoingCall(person, mActivityIntent)) - .setSmallIcon(android.R.drawable.sym_def_app_icon); - StatusBarNotification sbn = new StatusBarNotification(packageName, packageName, 1, - testName, mUid, 0, nb.build(), userHandle, null, 0); + @Test + @EnableFlags(FLAG_REJECT_OLD_NOTIFICATIONS) + public void testRejectOldNotification_oldWhen() throws Exception { + Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId()) + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setWhen(System.currentTimeMillis() - Duration.ofDays(15).toMillis()) + .build(); + StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 8, null, mUid, 0, + n, UserHandle.getUserHandleForUid(mUid), null, 0); NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); - mService.addEnqueuedNotification(r); - mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), - r.getUid(), mPostNotificationTrackerFactory.newTracker(null)).run(); - waitForIdle(); - - return mService.findNotificationLocked( - packageName, r.getSbn().getTag(), r.getSbn().getId(), r.getSbn().getUserId()); - } - - private NotificationRecord createAndPostNotification(Notification.Builder nb, String testName) - throws RemoteException { - StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 1, testName, mUid, 0, - nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0); - NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); - - mBinderService.enqueueNotificationWithTag(mPkg, mPkg, sbn.getTag(), - nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); - waitForIdle(); - - return mService.findNotificationLocked( - mPkg, nr.getSbn().getTag(), nr.getSbn().getId(), nr.getSbn().getUserId()); - } - - private static <T extends Parcelable> T parcelAndUnparcel(T source, - Parcelable.Creator<T> creator) { - Parcel parcel = Parcel.obtain(); - source.writeToParcel(parcel, 0); - parcel.setDataPosition(0); - return creator.createFromParcel(parcel); - } - - private PendingIntent createPendingIntent(String action) { - return PendingIntent.getActivity(mContext, 0, - new Intent(action).setPackage(mContext.getPackageName()), - PendingIntent.FLAG_MUTABLE); - } - - private void allowTestPackageToToast() throws Exception { - assertWithMessage("toast queue").that(mService.mToastQueue).isEmpty(); - mService.isSystemUid = false; - mService.isSystemAppId = false; - setToastRateIsWithinQuota(true); - setIfPackageHasPermissionToAvoidToastRateLimiting(TEST_PACKAGE, false); - // package is not suspended - when(mPackageManager.isPackageSuspendedForUser(TEST_PACKAGE, mUserId)) - .thenReturn(false); - } - - private boolean enqueueToast(String testPackage, ITransientNotification callback) - throws RemoteException { - return enqueueToast((INotificationManager) mService.mService, testPackage, new Binder(), - callback); - } - - private boolean enqueueToast(INotificationManager service, String testPackage, - IBinder token, ITransientNotification callback) throws RemoteException { - return service.enqueueToast(testPackage, token, callback, TOAST_DURATION, /* isUiContext= */ - true, DEFAULT_DISPLAY); - } - - private boolean enqueueTextToast(String testPackage, CharSequence text) throws RemoteException { - return enqueueTextToast(testPackage, text, /* isUiContext= */ true, DEFAULT_DISPLAY); - } - - private boolean enqueueTextToast(String testPackage, CharSequence text, boolean isUiContext, - int displayId) throws RemoteException { - return ((INotificationManager) mService.mService).enqueueTextToast(testPackage, - new Binder(), text, TOAST_DURATION, isUiContext, displayId, - /* textCallback= */ null); + assertThat(mService.checkDisqualifyingFeatures(mUserId, mUid, 0, null, r, false, false)) + .isFalse(); } - private void mockIsVisibleBackgroundUsersSupported(boolean supported) { - when(mUm.isVisibleBackgroundUsersSupported()).thenReturn(supported); - } + @Test + @EnableFlags(FLAG_REJECT_OLD_NOTIFICATIONS) + public void testRejectOldNotification_mediumOldWhen() throws Exception { + Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId()) + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setWhen(System.currentTimeMillis() - Duration.ofDays(13).toMillis()) + .build(); + StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 8, null, mUid, 0, + n, UserHandle.getUserHandleForUid(mUid), null, 0); + NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); - private void mockIsUserVisible(int displayId, boolean visible) { - when(mUmInternal.isUserVisible(mUserId, displayId)).thenReturn(visible); + assertThat(mService.checkDisqualifyingFeatures(mUserId, mUid, 0, null, r, false, false)) + .isTrue(); } - private void mockDisplayAssignedToUser(int displayId) { - when(mUmInternal.getMainDisplayAssignedToUser(mUserId)).thenReturn(displayId); - } + @Test + @EnableFlags(FLAG_REJECT_OLD_NOTIFICATIONS) + public void testRejectOldNotification_zeroWhen() throws Exception { + Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId()) + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setWhen(0) + .build(); + StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 8, null, mUid, 0, + n, UserHandle.getUserHandleForUid(mUid), null, 0); + NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); - private void verifyToastShownForTestPackage(String text, int displayId) { - verify(mStatusBar).showToast(eq(mUid), eq(TEST_PACKAGE), any(), eq(text), any(), - eq(TOAST_DURATION), any(), eq(displayId)); + assertThat(mService.checkDisqualifyingFeatures(mUserId, mUid, 0, null, r, false, false)) + .isTrue(); } } 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 a60d243c9dad..1195c934a6f7 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java @@ -1623,6 +1623,12 @@ public class LetterboxUiControllerTest extends WindowTestsBase { assertTrue(mController.allowHorizontalReachabilityForThinLetterbox()); } + @Test + public void testIsLetterboxEducationEnabled() { + mController.isLetterboxEducationEnabled(); + verify(mLetterboxConfiguration).getIsEducationEnabled(); + } + private void mockThatProperty(String propertyName, boolean value) throws Exception { Property property = new Property(propertyName, /* value */ value, /* packageName */ "", /* className */ ""); diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt index 2b6ddcb43f18..da8368f3cedf 100644 --- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt +++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt @@ -32,9 +32,6 @@ import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized -/** - * To run this test: `atest FlickerTestsIme1:CloseImeOnDismissPopupDialogTest` - */ @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt index 0344197c1425..2f3ec6301215 100644 --- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt +++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt @@ -33,8 +33,8 @@ import org.junit.runners.MethodSorters import org.junit.runners.Parameterized /** - * Test IME window closing to home transitions. - * To run this test: `atest FlickerTestsIme1:CloseImeOnGoHomeTest` + * Test IME window closing to home transitions. To run this test: `atest + * FlickerTests:CloseImeWindowToHomeTest` */ @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTest.kt index fde1373b032b..8821b69cdb3e 100644 --- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTest.kt +++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTest.kt @@ -42,7 +42,7 @@ import org.junit.runners.Parameterized * * More details on b/190352379 * - * To run this test: `atest FlickerTestsIme1:CloseImeShownOnAppStartOnGoHomeTest` + * To run this test: `atest FlickerTests:CloseImeAutoOpenWindowToHomeTest` */ @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt index dc5013519dbf..d75eba68c7cc 100644 --- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt +++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt @@ -42,7 +42,7 @@ import org.junit.runners.Parameterized * * More details on b/190352379 * - * To run this test: `atest FlickerTestsIme1:CloseImeShownOnAppStartToAppOnPressBackTest` + * To run this test: `atest FlickerTests:CloseImeAutoOpenWindowToAppTest` */ @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) 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..41d9e30a17ee 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 @@ -34,8 +34,8 @@ import org.junit.runners.MethodSorters import org.junit.runners.Parameterized /** - * Test IME window closing back to app window transitions. - * To run this test: `atest FlickerTestsIme1:CloseImeToAppOnPressBackTest` + * Test IME window closing back to app window transitions. To run this test: `atest + * FlickerTests:CloseImeWindowToAppTest` */ @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt index 05771e88fc83..0e7fb7975df8 100644 --- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt +++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt @@ -40,7 +40,7 @@ import org.junit.runners.Parameterized * Unlike {@link OpenImeWindowTest} testing IME window opening transitions, this test also verify * there is no flickering when back to the simple activity without requesting IME to show. * - * To run this test: `atest FlickerTestsIme1:CloseImeToHomeOnFinishActivityTest` + * To run this test: `atest FlickerTests:OpenImeWindowAndCloseTest` */ @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt index 336fe6f991ca..47a7e1b65b2d 100644 --- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt +++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt @@ -36,8 +36,8 @@ import org.junit.runners.MethodSorters import org.junit.runners.Parameterized /** - * Test IME window shown on the app with fixing portrait orientation. - * To run this test: `atest FlickerTestsIme2:OpenImeWindowToFixedPortraitAppTest` + * Test IME window shown on the app with fixing portrait orientation. To run this test: `atest + * FlickerTests:OpenImeWindowToFixedPortraitAppTest` */ @RequiresDevice @RunWith(Parameterized::class) diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt index b8f11dcf8970..48ec4d1fed2c 100644 --- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt +++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt @@ -38,9 +38,8 @@ import org.junit.runners.Parameterized /** * Test IME window layer will become visible when switching from the fixed orientation activity - * (e.g. Launcher activity). - * To run this test: - * `atest FlickerTestsIme2:ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest` + * (e.g. Launcher activity). To run this test: `atest + * FlickerTests:ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest` */ @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt index 34a708578396..03f3a68a573f 100644 --- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt +++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt @@ -33,8 +33,7 @@ import org.junit.runners.MethodSorters import org.junit.runners.Parameterized /** - * Test IME window opening transitions. - * To run this test: `atest FlickerTestsIme2:ShowImeOnAppStartWhenLaunchingAppFromOverviewTest` + * Test IME window opening transitions. To run this test: `atest FlickerTests:ReOpenImeWindowTest` */ @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt index 7c72c3187a7f..7b62c8967628 100644 --- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt +++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt @@ -35,8 +35,8 @@ import org.junit.runners.MethodSorters import org.junit.runners.Parameterized /** - * Test IME windows switching with 2-Buttons or gestural navigation. - * To run this test: `atest FlickerTestsIme2:ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest` + * Test IME windows switching with 2-Buttons or gestural navigation. To run this test: `atest + * FlickerTests:SwitchImeWindowsFromGestureNavTest` */ @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt index fe5320cd1a46..53bfb4ecf66f 100644 --- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt +++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt @@ -36,7 +36,7 @@ import org.junit.runners.Parameterized /** * Launch an app that automatically displays the IME * - * To run this test: `atest FlickerTestsIme2:ShowImeOnAppStartWhenLaunchingAppTest` + * To run this test: `atest FlickerTests:LaunchAppShowImeOnStartTest` * * Actions: * ``` diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnUnlockScreenTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnUnlockScreenTest.kt index 92b6b934874f..d22bdcf25529 100644 --- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnUnlockScreenTest.kt +++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnUnlockScreenTest.kt @@ -35,8 +35,8 @@ import org.junit.runners.MethodSorters import org.junit.runners.Parameterized /** - * Test IME window closing on lock and opening on screen unlock. - * To run this test: `atest FlickerTestsIme2:ShowImeOnUnlockScreenTest` + * Test IME window closing on lock and opening on screen unlock. To run this test: `atest + * FlickerTests:CloseImeWindowToHomeTest` */ @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt index 9eaf998ed63f..12290af8fd46 100644 --- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt +++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt @@ -31,10 +31,7 @@ import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized -/** - * Test IME window opening transitions. - * To run this test: `atest FlickerTestsIme2:ShowImeWhenFocusingOnInputFieldTest` - */ +/** Test IME window opening transitions. To run this test: `atest FlickerTests:OpenImeWindowTest` */ @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt index 7186a2c48c4c..0948351ac65b 100644 --- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt +++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt @@ -41,7 +41,7 @@ import org.junit.runners.Parameterized /** * Test IME snapshot mechanism won't apply when transitioning from non-IME focused dialog activity. - * To run this test: `atest FlickerTestsIme2:ShowImeWhileDismissingThemedPopupDialogTest` + * To run this test: `atest FlickerTests:LaunchAppShowImeAndDialogThemeAppTest` */ @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) 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..7aa525fcccef 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 @@ -37,8 +37,8 @@ import org.junit.runners.MethodSorters import org.junit.runners.Parameterized /** - * Test IME window layer will be associated with the app task when going to the overview screen. - * To run this test: `atest FlickerTestsIme2:ShowImeWhileEnteringOverviewTest` + * Test IME window layer will be associated with the app task when going to the overview screen. To + * run this test: `atest FlickerTests:OpenImeWindowToOverViewTest` */ @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) diff --git a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationColdTest.kt b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationColdTest.kt index 8c9ab9aadb8e..ffaeeadb1042 100644 --- a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationColdTest.kt +++ b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationColdTest.kt @@ -40,7 +40,7 @@ import org.junit.runners.Parameterized * * This test assumes the device doesn't have AOD enabled * - * To run this test: `atest FlickerTestsNotification:OpenAppFromLockscreenNotificationColdTest` + * To run this test: `atest FlickerTests:OpenAppFromLockNotificationCold` */ @RequiresDevice @RunWith(Parameterized::class) diff --git a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWarmTest.kt b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWarmTest.kt index e595100a2cbe..6e67e193ed8c 100644 --- a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWarmTest.kt +++ b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWarmTest.kt @@ -40,7 +40,7 @@ import org.junit.runners.Parameterized * * This test assumes the device doesn't have AOD enabled * - * To run this test: `atest FlickerTestsNotification:OpenAppFromLockscreenNotificationWarmTest` + * To run this test: `atest FlickerTests:OpenAppFromLockNotificationWarm` */ @RequiresDevice @RunWith(Parameterized::class) diff --git a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt index fbe1d34272c9..f1df8a68fb63 100644 --- a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt +++ b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt @@ -40,8 +40,7 @@ import org.junit.runners.Parameterized * * This test assumes the device doesn't have AOD enabled * - * To run this test: - * `atest FlickerTestsNotification:OpenAppFromLockscreenNotificationWithOverlayAppTest` + * To run this test: `atest FlickerTests:OpenAppFromLockNotificationWithLockOverlayApp` */ @RequiresDevice @RunWith(Parameterized::class) diff --git a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationColdTest.kt b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationColdTest.kt index c8ca644dde90..b6d09d0bf3bb 100644 --- a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationColdTest.kt +++ b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationColdTest.kt @@ -36,7 +36,7 @@ import org.junit.runners.Parameterized /** * Test cold launching an app from a notification. * - * To run this test: `atest FlickerTestsNotification:OpenAppFromNotificationColdTest` + * To run this test: `atest FlickerTests:OpenAppFromNotificationCold` */ @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) 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 c29e71ce4c79..1e607bfb2f49 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 @@ -47,7 +47,7 @@ import org.junit.runners.Parameterized /** * Test cold launching an app from a notification. * - * To run this test: `atest FlickerTestsNotification:OpenAppFromNotificationWarmTest` + * To run this test: `atest FlickerTests:OpenAppFromNotificationWarm` */ @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml index 96d4e023fb80..9198ae184b18 100644 --- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml +++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml @@ -17,10 +17,8 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.server.wm.flicker.testapp"> - <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> - <uses-sdk android:minSdkVersion="29" - android:targetSdkVersion="35"/> + android:targetSdkVersion="29"/> <application android:allowBackup="false" android:supportsRtl="true"> <uses-library android:name="androidx.window.extensions" android:required="false"/> @@ -109,7 +107,7 @@ android:immersive="true" android:resizeableActivity="true" android:screenOrientation="portrait" - android:theme="@style/OptOutEdgeToEdge.NoTitleBar" + android:theme="@android:style/Theme.NoTitleBar" android:configChanges="screenSize" android:label="PortraitImmersiveActivity" android:exported="true"> @@ -121,7 +119,7 @@ <activity android:name=".LaunchTransparentActivity" android:resizeableActivity="false" android:screenOrientation="portrait" - android:theme="@style/OptOutEdgeToEdge" + android:theme="@android:style/Theme" android:taskAffinity="com.android.server.wm.flicker.testapp.LaunchTransparentActivity" android:label="LaunchTransparentActivity" android:exported="true"> @@ -284,7 +282,7 @@ <activity android:name=".GameActivity" android:taskAffinity="com.android.server.wm.flicker.testapp.GameActivity" android:immersive="true" - android:theme="@style/OptOutEdgeToEdge.NoTitleBar" + android:theme="@android:style/Theme.NoTitleBar" android:configChanges="screenSize" android:label="GameActivity" android:exported="true"> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml index 120077cbe570..9b742d96e35b 100644 --- a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml +++ b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml @@ -16,15 +16,7 @@ --> <resources> - <style name="OptOutEdgeToEdge" parent="@android:style/Theme.DeviceDefault"> - <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item> - </style> - - <style name="OptOutEdgeToEdge.NoTitleBar" parent="@android:style/Theme.NoTitleBar"> - <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item> - </style> - - <style name="DefaultTheme" parent="@style/OptOutEdgeToEdge"> + <style name="DefaultTheme" parent="@android:style/Theme.DeviceDefault"> <item name="android:windowBackground">@android:color/darker_gray</item> </style> @@ -40,7 +32,7 @@ <item name="android:windowLayoutInDisplayCutoutMode">never</item> </style> - <style name="DialogTheme" parent="@style/OptOutEdgeToEdge"> + <style name="DialogTheme" parent="@android:style/Theme.DeviceDefault"> <item name="android:windowAnimationStyle">@null</item> <item name="android:windowIsTranslucent">true</item> <item name="android:windowBackground">@null</item> @@ -51,18 +43,18 @@ <item name="android:windowSoftInputMode">stateUnchanged</item> </style> - <style name="TransparentTheme" parent="@style/OptOutEdgeToEdge"> + <style name="TransparentTheme" parent="@android:style/Theme.DeviceDefault"> <item name="android:windowIsTranslucent">true</item> <item name="android:windowBackground">@android:color/transparent</item> <item name="android:windowContentOverlay">@null</item> <item name="android:backgroundDimEnabled">false</item> </style> - <style name="no_starting_window" parent="@style/OptOutEdgeToEdge"> + <style name="no_starting_window" parent="@android:style/Theme.DeviceDefault"> <item name="android:windowDisablePreview">true</item> </style> - <style name="SplashscreenAppTheme" parent="@style/OptOutEdgeToEdge"> + <style name="SplashscreenAppTheme" parent="@android:style/Theme.DeviceDefault"> <!-- Splashscreen Attributes --> <item name="android:windowSplashScreenAnimatedIcon">@drawable/avd_anim</item> <!-- Here we want to match the duration of our AVD --> diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BubbleHelper.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BubbleHelper.java index a86ba5f76374..c92b82b896f2 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BubbleHelper.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BubbleHelper.java @@ -125,7 +125,7 @@ public class BubbleHelper { .setContentTitle("BubbleChat") .setContentIntent(PendingIntent.getActivity(mContext, 0, new Intent(mContext, LaunchBubbleActivity.class), - PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE)) + PendingIntent.FLAG_UPDATE_CURRENT)) .setStyle(new Notification.MessagingStyle(chatBot) .setConversationTitle("BubbleChat") .addMessage("BubbleChat", @@ -140,7 +140,7 @@ public class BubbleHelper { Intent target = new Intent(mContext, BubbleActivity.class); target.putExtra(EXTRA_BUBBLE_NOTIF_ID, info.id); PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, info.id, target, - PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE); + PendingIntent.FLAG_UPDATE_CURRENT); return new Notification.BubbleMetadata.Builder() .setIntent(bubbleIntent) diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchBubbleActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchBubbleActivity.java index f7888109f5c1..dea34442464d 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchBubbleActivity.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchBubbleActivity.java @@ -17,9 +17,6 @@ package com.android.server.wm.flicker.testapp; -import static android.Manifest.permission.POST_NOTIFICATIONS; -import static android.content.pm.PackageManager.PERMISSION_GRANTED; - import android.app.Activity; import android.app.Person; import android.content.Context; @@ -27,7 +24,6 @@ import android.content.Intent; import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutManager; import android.graphics.drawable.Icon; -import android.os.Build; import android.os.Bundle; import android.view.View; @@ -40,13 +36,6 @@ public class LaunchBubbleActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU - && checkSelfPermission(POST_NOTIFICATIONS) != PERMISSION_GRANTED) { - // POST_NOTIFICATIONS permission required for Bubble post sdk 33. - requestPermissions(new String[] { POST_NOTIFICATIONS }, 0); - } - addInboxShortcut(getApplicationContext()); mBubbleHelper = BubbleHelper.getInstance(this); setContentView(R.layout.activity_main); diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NotificationActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NotificationActivity.java index d6427abcc65a..a4dd5753539d 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NotificationActivity.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NotificationActivity.java @@ -16,9 +16,6 @@ package com.android.server.wm.flicker.testapp; -import static android.Manifest.permission.POST_NOTIFICATIONS; -import static android.content.pm.PackageManager.PERMISSION_GRANTED; - import android.app.Activity; import android.app.Notification; import android.app.NotificationChannel; @@ -26,7 +23,6 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.app.TaskStackBuilder; import android.content.Intent; -import android.os.Build; import android.os.Bundle; import android.view.WindowManager; import android.widget.Button; @@ -38,13 +34,6 @@ public class NotificationActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU - && checkSelfPermission(POST_NOTIFICATIONS) != PERMISSION_GRANTED) { - // POST_NOTIFICATIONS permission required for notification post sdk 33. - requestPermissions(new String[] { POST_NOTIFICATIONS }, 0); - } - WindowManager.LayoutParams p = getWindow().getAttributes(); p.layoutInDisplayCutoutMode = WindowManager.LayoutParams .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java index 27eb5a06451a..1ab8ddbe20e2 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java @@ -198,7 +198,7 @@ public class PipActivity extends Activity { filter.addAction(ACTION_SET_REQUESTED_ORIENTATION); filter.addAction(ACTION_ENTER_PIP); filter.addAction(ACTION_ASPECT_RATIO); - registerReceiver(mBroadcastReceiver, filter, Context.RECEIVER_EXPORTED); + registerReceiver(mBroadcastReceiver, filter); handleIntentExtra(getIntent()); } @@ -222,8 +222,8 @@ public class PipActivity extends Activity { private RemoteAction buildRemoteAction(Icon icon, String label, String action) { final Intent intent = new Intent(action); - final PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, - PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE); + final PendingIntent pendingIntent = + PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); return new RemoteAction(icon, label, label, pendingIntent); } diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt index 16785d1d9598..6b360b79c327 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt @@ -25,6 +25,7 @@ class AndroidHeuristicsFilter( val aidlPolicy: FilterPolicyWithReason?, val featureFlagsPolicy: FilterPolicyWithReason?, val syspropsPolicy: FilterPolicyWithReason?, + val rFilePolicy: FilterPolicyWithReason?, fallback: OutputFilter ) : DelegatingFilter(fallback) { override fun getPolicyForClass(className: String): FilterPolicyWithReason { @@ -37,6 +38,9 @@ class AndroidHeuristicsFilter( if (syspropsPolicy != null && classes.isSyspropsClass(className)) { return syspropsPolicy } + if (rFilePolicy != null && classes.isRClass(className)) { + return rFilePolicy + } return super.getPolicyForClass(className) } } @@ -74,3 +78,10 @@ private fun ClassNodes.isSyspropsClass(className: String): Boolean { return className.startsWith("android/sysprop/") && className.endsWith("Properties") } + +/** + * @return if a given class "seems like" an R class or its nested classes. + */ +private fun ClassNodes.isRClass(className: String): Boolean { + return className.endsWith("/R") || className.contains("/R$") +} diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt index 75b5fc8f77ea..c5acd81f1cf2 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt @@ -17,6 +17,7 @@ package com.android.hoststubgen.filters import com.android.hoststubgen.ParseException import com.android.hoststubgen.asm.ClassNodes +import com.android.hoststubgen.asm.toHumanReadableClassName import com.android.hoststubgen.log import com.android.hoststubgen.normalizeTextLine import com.android.hoststubgen.whitespaceRegex @@ -31,13 +32,17 @@ import java.util.Objects * Print a class node as a "keep" policy. */ fun printAsTextPolicy(pw: PrintWriter, cn: ClassNode) { - pw.printf("class %s\t%s\n", cn.name, "keep") + pw.printf("class %s %s\n", cn.name.toHumanReadableClassName(), "keep") - for (f in cn.fields ?: emptyList()) { - pw.printf(" field %s\t%s\n", f.name, "keep") + cn.fields?.let { + for (f in it.sortedWith(compareBy({ it.name }))) { + pw.printf(" field %s %s\n", f.name, "keep") + } } - for (m in cn.methods ?: emptyList()) { - pw.printf(" method %s\t%s\t%s\n", m.name, m.desc, "keep") + cn.methods?.let { + for (m in it.sortedWith(compareBy({ it.name }, { it.desc }))) { + pw.printf(" method %s %s %s\n", m.name, m.desc, "keep") + } } } @@ -66,6 +71,7 @@ fun createFilterFromTextPolicyFile( var aidlPolicy: FilterPolicyWithReason? = null var featureFlagsPolicy: FilterPolicyWithReason? = null var syspropsPolicy: FilterPolicyWithReason? = null + var rFilePolicy: FilterPolicyWithReason? = null try { BufferedReader(FileReader(filename)).use { reader -> @@ -162,6 +168,14 @@ fun createFilterFromTextPolicyFile( syspropsPolicy = policy.withReason( "$FILTER_REASON (special-class sysprops)") } + SpecialClass.RFile -> { + if (rFilePolicy != null) { + throw ParseException( + "Policy for R file already defined") + } + rFilePolicy = policy.withReason( + "$FILTER_REASON (special-class R file)") + } } } } @@ -225,13 +239,9 @@ fun createFilterFromTextPolicyFile( throw e.withSourceInfo(filename, lineNo) } - var ret: OutputFilter = imf - if (aidlPolicy != null || featureFlagsPolicy != null || syspropsPolicy != null) { - log.d("AndroidHeuristicsFilter enabled") - ret = AndroidHeuristicsFilter( - classes, aidlPolicy, featureFlagsPolicy, syspropsPolicy, imf) - } - return ret + // Wrap the in-memory-filter with AHF. + return AndroidHeuristicsFilter( + classes, aidlPolicy, featureFlagsPolicy, syspropsPolicy, rFilePolicy, imf) } } @@ -240,6 +250,7 @@ private enum class SpecialClass { Aidl, FeatureFlags, Sysprops, + RFile, } private fun resolveSpecialClass(className: String): SpecialClass { @@ -250,6 +261,7 @@ private fun resolveSpecialClass(className: String): SpecialClass { ":aidl" -> return SpecialClass.Aidl ":feature_flags" -> return SpecialClass.FeatureFlags ":sysprops" -> return SpecialClass.Sysprops + ":r" -> return SpecialClass.RFile } throw ParseException("Invalid special class name \"$className\"") } diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt index fa8fe6cd384f..931f0c5fa793 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt @@ -322,6 +322,78 @@ NestMembers: InnerClasses: public static #x= #x of #x; // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl public static #x= #x of #x; // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub +## Class: com/android/hoststubgen/test/tinyframework/R$Nested.class + Compiled from "R.java" +public class com.android.hoststubgen.test.tinyframework.R$Nested + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/R$Nested + super_class: #x // java/lang/Object + interfaces: 0, fields: 1, methods: 2, attributes: 3 + public static int[] ARRAY; + descriptor: [I + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + + public com.android.hoststubgen.test.tinyframework.R$Nested(); + descriptor: ()V + flags: (0x0001) ACC_PUBLIC + Code: + stack=1, locals=1, args_size=1 + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 0 5 0 this Lcom/android/hoststubgen/test/tinyframework/R$Nested; + + static {}; + descriptor: ()V + flags: (0x0008) ACC_STATIC + Code: + stack=4, locals=0, args_size=0 + x: iconst_1 + x: newarray int + x: dup + x: iconst_0 + x: iconst_1 + x: iastore + x: putstatic #x // Field ARRAY:[I + x: return + LineNumberTable: +} +SourceFile: "R.java" +NestHost: class com/android/hoststubgen/test/tinyframework/R +InnerClasses: + public static #x= #x of #x; // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R +## Class: com/android/hoststubgen/test/tinyframework/R.class + Compiled from "R.java" +public class com.android.hoststubgen.test.tinyframework.R + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/R + super_class: #x // java/lang/Object + interfaces: 0, fields: 0, methods: 1, attributes: 3 + public com.android.hoststubgen.test.tinyframework.R(); + descriptor: ()V + flags: (0x0001) ACC_PUBLIC + Code: + stack=1, locals=1, args_size=1 + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 0 5 0 this Lcom/android/hoststubgen/test/tinyframework/R; +} +SourceFile: "R.java" +NestMembers: + com/android/hoststubgen/test/tinyframework/R$Nested +InnerClasses: + public static #x= #x of #x; // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class Compiled from "TinyFrameworkCallerCheck.java" class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt index c605f767f527..906a81cf45e3 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt @@ -122,6 +122,100 @@ RuntimeVisibleAnnotations: NestMembers: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy +## Class: com/android/hoststubgen/test/tinyframework/R$Nested.class + Compiled from "R.java" +public class com.android.hoststubgen.test.tinyframework.R$Nested + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/R$Nested + super_class: #x // java/lang/Object + interfaces: 0, fields: 1, methods: 2, attributes: 4 + public static int[] ARRAY; + descriptor: [I + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl + + public com.android.hoststubgen.test.tinyframework.R$Nested(); + descriptor: ()V + flags: (0x0001) ACC_PUBLIC + Code: + stack=3, locals=1, args_size=1 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow + RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl + + static {}; + descriptor: ()V + flags: (0x0008) ACC_STATIC + Code: + stack=3, locals=0, args_size=0 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow + RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl +} +InnerClasses: + public static #x= #x of #x; // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R +SourceFile: "R.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl +NestHost: class com/android/hoststubgen/test/tinyframework/R +## Class: com/android/hoststubgen/test/tinyframework/R.class + Compiled from "R.java" +public class com.android.hoststubgen.test.tinyframework.R + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/R + super_class: #x // java/lang/Object + interfaces: 0, fields: 0, methods: 1, attributes: 4 + public com.android.hoststubgen.test.tinyframework.R(); + descriptor: ()V + flags: (0x0001) ACC_PUBLIC + Code: + stack=3, locals=1, args_size=1 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow + RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl +} +InnerClasses: + public static #x= #x of #x; // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R +SourceFile: "R.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl +NestMembers: + com/android/hoststubgen/test/tinyframework/R$Nested ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class Compiled from "TinyFrameworkCallerCheck.java" class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt index 11d5939b7917..10bc91da2544 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt @@ -348,6 +348,108 @@ RuntimeVisibleAnnotations: NestMembers: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy +## Class: com/android/hoststubgen/test/tinyframework/R$Nested.class + Compiled from "R.java" +public class com.android.hoststubgen.test.tinyframework.R$Nested + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/R$Nested + super_class: #x // java/lang/Object + interfaces: 0, fields: 1, methods: 2, attributes: 4 + public static int[] ARRAY; + descriptor: [I + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl + + public com.android.hoststubgen.test.tinyframework.R$Nested(); + descriptor: ()V + flags: (0x0001) ACC_PUBLIC + Code: + stack=1, locals=1, args_size=1 + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 0 5 0 this Lcom/android/hoststubgen/test/tinyframework/R$Nested; + RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl + + static {}; + descriptor: ()V + flags: (0x0008) ACC_STATIC + Code: + stack=4, locals=0, args_size=0 + x: iconst_1 + x: newarray int + x: dup + x: iconst_0 + x: iconst_1 + x: iastore + x: putstatic #x // Field ARRAY:[I + x: return + LineNumberTable: + RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl +} +InnerClasses: + public static #x= #x of #x; // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R +SourceFile: "R.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl +NestHost: class com/android/hoststubgen/test/tinyframework/R +## Class: com/android/hoststubgen/test/tinyframework/R.class + Compiled from "R.java" +public class com.android.hoststubgen.test.tinyframework.R + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/R + super_class: #x // java/lang/Object + interfaces: 0, fields: 0, methods: 1, attributes: 4 + public com.android.hoststubgen.test.tinyframework.R(); + descriptor: ()V + flags: (0x0001) ACC_PUBLIC + Code: + stack=1, locals=1, args_size=1 + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 0 5 0 this Lcom/android/hoststubgen/test/tinyframework/R; + RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl +} +InnerClasses: + public static #x= #x of #x; // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R +SourceFile: "R.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl +NestMembers: + com/android/hoststubgen/test/tinyframework/R$Nested ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class Compiled from "TinyFrameworkCallerCheck.java" class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt index c605f767f527..906a81cf45e3 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt @@ -122,6 +122,100 @@ RuntimeVisibleAnnotations: NestMembers: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy +## Class: com/android/hoststubgen/test/tinyframework/R$Nested.class + Compiled from "R.java" +public class com.android.hoststubgen.test.tinyframework.R$Nested + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/R$Nested + super_class: #x // java/lang/Object + interfaces: 0, fields: 1, methods: 2, attributes: 4 + public static int[] ARRAY; + descriptor: [I + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl + + public com.android.hoststubgen.test.tinyframework.R$Nested(); + descriptor: ()V + flags: (0x0001) ACC_PUBLIC + Code: + stack=3, locals=1, args_size=1 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow + RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl + + static {}; + descriptor: ()V + flags: (0x0008) ACC_STATIC + Code: + stack=3, locals=0, args_size=0 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow + RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl +} +InnerClasses: + public static #x= #x of #x; // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R +SourceFile: "R.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl +NestHost: class com/android/hoststubgen/test/tinyframework/R +## Class: com/android/hoststubgen/test/tinyframework/R.class + Compiled from "R.java" +public class com.android.hoststubgen.test.tinyframework.R + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/R + super_class: #x // java/lang/Object + interfaces: 0, fields: 0, methods: 1, attributes: 4 + public com.android.hoststubgen.test.tinyframework.R(); + descriptor: ()V + flags: (0x0001) ACC_PUBLIC + Code: + stack=3, locals=1, args_size=1 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow + RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl +} +InnerClasses: + public static #x= #x of #x; // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R +SourceFile: "R.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl +NestMembers: + com/android/hoststubgen/test/tinyframework/R$Nested ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class Compiled from "TinyFrameworkCallerCheck.java" class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt index 088bc80e11c5..fcf9a8c663ad 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt @@ -481,6 +481,136 @@ RuntimeVisibleAnnotations: NestMembers: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy +## Class: com/android/hoststubgen/test/tinyframework/R$Nested.class + Compiled from "R.java" +public class com.android.hoststubgen.test.tinyframework.R$Nested + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/R$Nested + super_class: #x // java/lang/Object + interfaces: 0, fields: 1, methods: 2, attributes: 4 + public static int[] ARRAY; + descriptor: [I + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl + + public com.android.hoststubgen.test.tinyframework.R$Nested(); + descriptor: ()V + flags: (0x0001) ACC_PUBLIC + Code: + stack=4, locals=1, args_size=1 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/R$Nested + x: ldc #x // String <init> + x: ldc #x // String ()V + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 11 5 0 this Lcom/android/hoststubgen/test/tinyframework/R$Nested; + RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl + + static {}; + descriptor: ()V + flags: (0x0008) ACC_STATIC + Code: + stack=4, locals=0, args_size=0 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/R$Nested + x: ldc #x // String <clinit> + x: ldc #x // String ()V + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: ldc #x // class com/android/hoststubgen/test/tinyframework/R$Nested + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V + x: iconst_1 + x: newarray int + x: dup + x: iconst_0 + x: iconst_1 + x: iastore + x: putstatic #x // Field ARRAY:[I + x: return + LineNumberTable: + RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl +} +InnerClasses: + public static #x= #x of #x; // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R +SourceFile: "R.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl +NestHost: class com/android/hoststubgen/test/tinyframework/R +## Class: com/android/hoststubgen/test/tinyframework/R.class + Compiled from "R.java" +public class com.android.hoststubgen.test.tinyframework.R + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/R + super_class: #x // java/lang/Object + interfaces: 0, fields: 0, methods: 2, attributes: 4 + private static {}; + descriptor: ()V + flags: (0x000a) ACC_PRIVATE, ACC_STATIC + Code: + stack=2, locals=0, args_size=0 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/R + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V + x: return + + public com.android.hoststubgen.test.tinyframework.R(); + descriptor: ()V + flags: (0x0001) ACC_PUBLIC + Code: + stack=4, locals=1, args_size=1 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/R + x: ldc #x // String <init> + x: ldc #x // String ()V + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 11 5 0 this Lcom/android/hoststubgen/test/tinyframework/R; + RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl +} +InnerClasses: + public static #x= #x of #x; // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R +SourceFile: "R.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl +NestMembers: + com/android/hoststubgen/test/tinyframework/R$Nested ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class Compiled from "TinyFrameworkCallerCheck.java" class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt index d30208452a40..696b6d009dc2 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt @@ -19,6 +19,9 @@ class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy ~com # Heuristics rule: Stub all the AIDL classes. class :aidl stubclass +# Heuristics rule: Stub all the R classes. +class :r stubclass + # Default is "remove", so let's put all the base classes / interfaces in the stub first. class com.android.hoststubgen.test.tinyframework.subclasstest.C1 stub class com.android.hoststubgen.test.tinyframework.subclasstest.C2 stub diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/R.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/R.java new file mode 100644 index 000000000000..b1bedf4b6853 --- /dev/null +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/R.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.hoststubgen.test.tinyframework; + +public class R { + public static class Nested { + public static int[] ARRAY = new int[] {1}; + } +} diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java index 762180dcf74b..37925e82bdb6 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java @@ -19,6 +19,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; +import com.android.hoststubgen.test.tinyframework.R.Nested; import com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses.SubClass; import org.junit.Rule; @@ -328,4 +329,9 @@ public class TinyFrameworkClassTest { assertThat(IPretendingAidl.Stub.addOne(1)).isEqualTo(2); assertThat(IPretendingAidl.Stub.Proxy.addTwo(1)).isEqualTo(3); } + + @Test + public void testRFileHeuristics() { + assertThat(Nested.ARRAY.length).isEqualTo(1); + } } |