Merge "Add API to move contacts to Cloud DCA" into main
diff --git a/Android.bp b/Android.bp
index 516fc9c..811755d 100644
--- a/Android.bp
+++ b/Android.bp
@@ -368,6 +368,7 @@
jarjar_rules: ":framework-jarjar-rules",
javac_shard_size: 150,
plugins: [
+ "cached-property-annotation-processor",
"view-inspector-annotation-processor",
"staledataclass-annotation-processor",
"error_prone_android_framework",
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index c2f6e30..87c9fa4 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -106,7 +106,6 @@
static const int TEXT_MISSING_VALUE = INT_MIN;
static const char EXIT_PROP_NAME[] = "service.bootanim.exit";
static const char PROGRESS_PROP_NAME[] = "service.bootanim.progress";
-static const char DISPLAYS_PROP_NAME[] = "persist.service.bootanim.displays";
static const char CLOCK_ENABLED_PROP_NAME[] = "persist.sys.bootanim.clock.enabled";
static const int ANIM_ENTRY_NAME_MAX = ANIM_PATH_MAX + 1;
static const int MAX_CHECK_EXIT_INTERVAL_US = 50000;
@@ -514,50 +513,9 @@
return NAME_NOT_FOUND;
}
- // this system property specifies multi-display IDs to show the boot animation
- // multiple ids can be set with comma (,) as separator, for example:
- // setprop persist.boot.animation.displays 19260422155234049,19261083906282754
- Vector<PhysicalDisplayId> physicalDisplayIds;
- char displayValue[PROPERTY_VALUE_MAX] = "";
- property_get(DISPLAYS_PROP_NAME, displayValue, "");
- bool isValid = displayValue[0] != '\0';
- if (isValid) {
- char *p = displayValue;
- while (*p) {
- if (!isdigit(*p) && *p != ',') {
- isValid = false;
- break;
- }
- p ++;
- }
- if (!isValid)
- SLOGE("Invalid syntax for the value of system prop: %s", DISPLAYS_PROP_NAME);
- }
- if (isValid) {
- std::istringstream stream(displayValue);
- for (PhysicalDisplayId id; stream >> id.value; ) {
- physicalDisplayIds.add(id);
- if (stream.peek() == ',')
- stream.ignore();
- }
-
- // the first specified display id is used to retrieve mDisplayToken
- for (const auto id : physicalDisplayIds) {
- if (std::find(ids.begin(), ids.end(), id) != ids.end()) {
- if (const auto token = SurfaceComposerClient::getPhysicalDisplayToken(id)) {
- mDisplayToken = token;
- break;
- }
- }
- }
- }
-
- // If the system property is not present or invalid, display 0 is used
+ mDisplayToken = SurfaceComposerClient::getPhysicalDisplayToken(ids.front());
if (mDisplayToken == nullptr) {
- mDisplayToken = SurfaceComposerClient::getPhysicalDisplayToken(ids.front());
- if (mDisplayToken == nullptr) {
- return NAME_NOT_FOUND;
- }
+ return NAME_NOT_FOUND;
}
DisplayMode displayMode;
@@ -577,17 +535,8 @@
ISurfaceComposerClient::eOpaque);
SurfaceComposerClient::Transaction t;
- if (isValid) {
- // In the case of multi-display, boot animation shows on the specified displays
- for (const auto id : physicalDisplayIds) {
- if (std::find(ids.begin(), ids.end(), id) != ids.end()) {
- if (const auto token = SurfaceComposerClient::getPhysicalDisplayToken(id)) {
- t.setDisplayLayerStack(token, ui::DEFAULT_LAYER_STACK);
- }
- }
- }
- t.setLayerStack(control, ui::DEFAULT_LAYER_STACK);
- }
+ t.setDisplayLayerStack(mDisplayToken, ui::DEFAULT_LAYER_STACK);
+ t.setLayerStack(control, ui::DEFAULT_LAYER_STACK);
t.setLayer(control, 0x40000000)
.apply();
diff --git a/core/api/current.txt b/core/api/current.txt
index c3c41f8..efede83 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -4947,8 +4947,10 @@
method public void update(android.app.ActivityOptions);
field public static final String EXTRA_USAGE_TIME_REPORT = "android.activity.usage_time";
field public static final String EXTRA_USAGE_TIME_REPORT_PACKAGES = "android.usage_time_packages";
- field public static final int MODE_BACKGROUND_ACTIVITY_START_ALLOWED = 1; // 0x1
- field public static final int MODE_BACKGROUND_ACTIVITY_START_DENIED = 2; // 0x2
+ field @Deprecated @FlaggedApi("com.android.window.flags.bal_additional_start_modes") public static final int MODE_BACKGROUND_ACTIVITY_START_ALLOWED = 1; // 0x1
+ field @FlaggedApi("com.android.window.flags.bal_additional_start_modes") public static final int MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS = 3; // 0x3
+ field @FlaggedApi("com.android.window.flags.bal_additional_start_modes") public static final int MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE = 4; // 0x4
+ field @FlaggedApi("com.android.window.flags.bal_additional_start_modes") public static final int MODE_BACKGROUND_ACTIVITY_START_DENIED = 2; // 0x2
field public static final int MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED = 0; // 0x0
}
@@ -8673,6 +8675,8 @@
field public static final int TAG_MAX_SCREEN_LOCK_TIMEOUT_SET = 210019; // 0x33463
field public static final int TAG_MEDIA_MOUNT = 210013; // 0x3345d
field public static final int TAG_MEDIA_UNMOUNT = 210014; // 0x3345e
+ field @FlaggedApi("android.nfc.nfc_state_change_security_log_event_enabled") public static final int TAG_NFC_DISABLED = 210046; // 0x3347e
+ field @FlaggedApi("android.nfc.nfc_state_change_security_log_event_enabled") public static final int TAG_NFC_ENABLED = 210045; // 0x3347d
field public static final int TAG_OS_SHUTDOWN = 210010; // 0x3345a
field public static final int TAG_OS_STARTUP = 210009; // 0x33459
field public static final int TAG_PACKAGE_INSTALLED = 210041; // 0x33479
@@ -26914,7 +26918,6 @@
field public static final int STATE_FAST_FORWARDING = 4; // 0x4
field public static final int STATE_NONE = 0; // 0x0
field public static final int STATE_PAUSED = 2; // 0x2
- field @FlaggedApi("com.android.media.flags.enable_notifying_activity_manager_with_media_session_status_change") public static final int STATE_PLAYBACK_SUPPRESSED = 12; // 0xc
field public static final int STATE_PLAYING = 3; // 0x3
field public static final int STATE_REWINDING = 5; // 0x5
field public static final int STATE_SKIPPING_TO_NEXT = 10; // 0xa
@@ -37432,6 +37435,7 @@
field public static final String ACTION_APPLICATION_SETTINGS = "android.settings.APPLICATION_SETTINGS";
field public static final String ACTION_APP_LOCALE_SETTINGS = "android.settings.APP_LOCALE_SETTINGS";
field public static final String ACTION_APP_NOTIFICATION_BUBBLE_SETTINGS = "android.settings.APP_NOTIFICATION_BUBBLE_SETTINGS";
+ field @FlaggedApi("android.app.api_rich_ongoing") public static final String ACTION_APP_NOTIFICATION_PROMOTION_SETTINGS = "android.settings.APP_NOTIFICATION_PROMOTION_SETTINGS";
field public static final String ACTION_APP_NOTIFICATION_SETTINGS = "android.settings.APP_NOTIFICATION_SETTINGS";
field public static final String ACTION_APP_OPEN_BY_DEFAULT_SETTINGS = "android.settings.APP_OPEN_BY_DEFAULT_SETTINGS";
field public static final String ACTION_APP_SEARCH_SETTINGS = "android.settings.APP_SEARCH_SETTINGS";
@@ -61655,6 +61659,7 @@
method public void unregisterOnBackInvokedCallback(@NonNull android.window.OnBackInvokedCallback);
field public static final int PRIORITY_DEFAULT = 0; // 0x0
field public static final int PRIORITY_OVERLAY = 1000000; // 0xf4240
+ field @FlaggedApi("com.android.window.flags.predictive_back_priority_system_navigation_observer") public static final int PRIORITY_SYSTEM_NAVIGATION_OBSERVER = -2; // 0xfffffffe
}
public interface SplashScreen {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 0bc3338..8a0b1ba 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3495,8 +3495,8 @@
method @NonNull public android.content.Context createContext();
method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.companion.virtual.audio.VirtualAudioDevice createVirtualAudioDevice(@NonNull android.hardware.display.VirtualDisplay, @Nullable java.util.concurrent.Executor, @Nullable android.companion.virtual.audio.VirtualAudioDevice.AudioConfigurationChangeCallback);
method @FlaggedApi("android.companion.virtual.flags.virtual_camera") @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.companion.virtual.camera.VirtualCamera createVirtualCamera(@NonNull android.companion.virtual.camera.VirtualCameraConfig);
- method @Deprecated @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(@IntRange(from=1) int, @IntRange(from=1) int, @IntRange(from=1) int, @Nullable android.view.Surface, int, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.display.VirtualDisplay.Callback);
- method @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull android.hardware.display.VirtualDisplayConfig, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.display.VirtualDisplay.Callback);
+ method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.display.VirtualDisplay createVirtualDisplay(@IntRange(from=1) int, @IntRange(from=1) int, @IntRange(from=1) int, @Nullable android.view.Surface, int, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.display.VirtualDisplay.Callback);
+ method @Nullable @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull android.hardware.display.VirtualDisplayConfig, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.display.VirtualDisplay.Callback);
method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualDpad createVirtualDpad(@NonNull android.hardware.input.VirtualDpadConfig);
method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualKeyboard createVirtualKeyboard(@NonNull android.hardware.input.VirtualKeyboardConfig);
method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualKeyboard createVirtualKeyboard(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
@@ -3509,8 +3509,9 @@
method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualTouchscreen createVirtualTouchscreen(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
method public int getDeviceId();
method @FlaggedApi("android.companion.virtual.flags.vdm_public_apis") @Nullable public String getPersistentDeviceId();
- method @NonNull public java.util.List<android.companion.virtual.sensor.VirtualSensor> getVirtualSensorList();
- method public void launchPendingIntent(int, @NonNull android.app.PendingIntent, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
+ method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public java.util.List<android.companion.virtual.sensor.VirtualSensor> getVirtualSensorList();
+ method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void goToSleep();
+ method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void launchPendingIntent(int, @NonNull android.app.PendingIntent, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void registerIntentInterceptor(@NonNull android.content.IntentFilter, @NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.IntentInterceptorCallback);
method public void removeActivityListener(@NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener);
method @FlaggedApi("android.companion.virtual.flags.dynamic_policy") @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void removeActivityPolicyExemption(@NonNull android.content.ComponentName);
@@ -3521,6 +3522,7 @@
method @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime") @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setDisplayImePolicy(int, int);
method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setShowPointerIcon(boolean);
method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void unregisterIntentInterceptor(@NonNull android.companion.virtual.VirtualDeviceManager.IntentInterceptorCallback);
+ method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void wakeUp();
}
public final class VirtualDeviceParams implements android.os.Parcelable {
@@ -8540,11 +8542,14 @@
method public long getAudioHandle();
method @NonNull public java.util.List<android.media.AudioPresentation> getAudioPresentations();
method public long getAvDataId();
+ method @FlaggedApi("android.media.tv.flags.tuner_w_apis") public int getDataGroupId();
method public long getDataLength();
method public long getDts();
method @Nullable public android.media.tv.tuner.filter.AudioDescriptor getExtraMetaData();
+ method @FlaggedApi("android.media.tv.flags.tuner_w_apis") @IntRange(from=0) public int getIndexInDataGroup();
method @Nullable public android.media.MediaCodec.LinearBlock getLinearBlock();
method @IntRange(from=0) public int getMpuSequenceNumber();
+ method @FlaggedApi("android.media.tv.flags.tuner_w_apis") @IntRange(from=0) public int getNumDataPieces();
method public long getOffset();
method public long getPts();
method public int getScIndexMask();
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index d318812..3bd121a 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -1323,4 +1323,12 @@
*/
public abstract void killApplicationSync(String pkgName, int appId, int userId,
String reason, int exitInfoReason);
+
+ /**
+ * Add a creator token for all embedded intents (stored as extra) of the given intent.
+ *
+ * @param intent The given intent
+ * @hide
+ */
+ public abstract void addCreatorToken(Intent intent);
}
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 0d183c7..6ab39b0 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -68,6 +68,8 @@
import android.window.SplashScreen;
import android.window.WindowContainerToken;
+import com.android.window.flags.Flags;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -109,35 +111,64 @@
MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS,
MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE})
public @interface BackgroundActivityStartMode {}
+
/**
- * No explicit value chosen. The system will decide whether to grant privileges.
+ * The system determines whether to grant background activity start privileges. This is the
+ * default behavior if no explicit mode is specified.
*/
public static final int MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED = 0;
/**
- * Allow the {@link PendingIntent} to use the background activity start privileges.
+ * Grants the {@link PendingIntent} background activity start privileges.
+ *
+ * This behaves the same as {@link #MODE_BACKGROUND_ACTIVITY_START_ALLOWED_ALWAYS}, except it
+ * does not grant background activity launch permissions based on the privileged permission
+ * <code>START_ACTIVITIES_FROM_BACKGROUND</code>.
+ *
+ * @deprecated Use {@link #MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE} to allow starts
+ * only when the app is visible or {@link #MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS} to
+ * allow starts at any time (see <a
+ * href="https://developer.android.com/guide/components/activities/background-starts">
+ * Restrictions on starting activities from the background</a>).
*/
+ @Deprecated
+ @FlaggedApi(Flags.FLAG_BAL_ADDITIONAL_START_MODES)
public static final int MODE_BACKGROUND_ACTIVITY_START_ALLOWED = 1;
/**
- * Deny the {@link PendingIntent} to use the background activity start privileges.
+ * Denies the {@link PendingIntent} any background activity start privileges.
*/
+ @FlaggedApi(Flags.FLAG_BAL_ADDITIONAL_START_MODES)
public static final int MODE_BACKGROUND_ACTIVITY_START_DENIED = 2;
/**
- * Allow the {@link PendingIntent} to use ALL background activity start privileges, including
- * special permissions that will allow starts at any time.
+ * Grants the {@link PendingIntent} all background activity start privileges, including
+ * those normally reserved for privileged contexts (e.g., companion apps or those with the
+ * {@code START_ACTIVITIES_FROM_BACKGROUND} permission).
*
- * @hide
+ * <p><b>Caution:</b> This mode should be used sparingly. Most apps should use
+ * {@link #MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE} instead, relying on notifications
+ * or foreground services for background interactions to minimize user disruption. However,
+ * this mode is necessary for specific use cases, such as companion apps responding to
+ * prompts from a connected device.
+ *
+ * <p>For more information on background activity start restrictions, see:
+ * <a href="https://developer.android.com/guide/components/activities/background-starts">
+ * Restrictions on starting activities from the background</a>
*/
+ @FlaggedApi(Flags.FLAG_BAL_ADDITIONAL_START_MODES)
public static final int MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS = 3;
/**
- * Allow the {@link PendingIntent} to use background activity start privileges based on
- * visibility of the app.
+ * Grants the {@link PendingIntent} background activity start privileges only when the app
+ * has a visible window (i.e., is visible to the user). This is the recommended mode for most
+ * apps to minimize disruption to the user experience.
*
- * @hide
+ * <p>For more information on background activity start restrictions, see:
+ * <a href="https://developer.android.com/guide/components/activities/background-starts">
+ * Restrictions on starting activities from the background</a>
*/
+ @FlaggedApi(Flags.FLAG_BAL_ADDITIONAL_START_MODES)
public static final int MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE = 4;
/**
- * Special behavior for compatibility.
- * Similar to {@link #MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED}
+ * Provides compatibility with previous Android versions regarding background activity starts.
+ * Similar to {@link #MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED}.
*
* @hide
*/
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 95d3ea5..0c02ba4 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -6285,7 +6285,7 @@
}
r.activity.mConfigChangeFlags |= configChanges;
- r.mPreserveWindow = tmp.mPreserveWindow;
+ r.mPreserveWindow = r.activity.mWindowAdded && tmp.mPreserveWindow;
r.activity.mChangingConfigurations = true;
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 0812a13..38632bd 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -809,6 +809,10 @@
return false;
}
+ private static boolean isStandardLayout(int layoutId) {
+ return STANDARD_LAYOUTS.contains(layoutId);
+ }
+
/** @hide */
@IntDef(flag = true, prefix = {"FLAG_"}, value = {
FLAG_SHOW_LIGHTS,
@@ -5983,9 +5987,9 @@
}
}
boolean contentViewUsesHeader = mN.contentView == null
- || STANDARD_LAYOUTS.contains(mN.contentView.getLayoutId());
+ || isStandardLayout(mN.contentView.getLayoutId());
boolean bigContentViewUsesHeader = mN.bigContentView == null
- || STANDARD_LAYOUTS.contains(mN.bigContentView.getLayoutId());
+ || isStandardLayout(mN.bigContentView.getLayoutId());
return contentViewUsesHeader && bigContentViewUsesHeader;
}
@@ -6781,7 +6785,7 @@
return false;
}
if (fullyCustomViewRequiresDecoration(false)
- && STANDARD_LAYOUTS.contains(customContent.getLayoutId())) {
+ && isStandardLayout(customContent.getLayoutId())) {
// If the app's custom views are objects returned from Builder.create*ContentView()
// then the app is most likely attempting to spoof the user. Even if they are not,
// the result would be broken (b/189189308) so we will ignore it.
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index dfed1f7..41abd68 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -958,6 +958,9 @@
* Returns whether the calling app's properly formatted notifications can appear in a promoted
* format, which may result in higher ranking, appearances on additional surfaces, and richer
* presentation.
+ *
+ * Apps can request this permission by sending the user to the activity that matches the system
+ * intent action {@link android.provider.Settings#ACTION_APP_NOTIFICATION_PROMOTION_SETTINGS}.
*/
@FlaggedApi(android.app.Flags.FLAG_API_RICH_ONGOING)
public boolean canPostPromotedNotifications() {
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index 3714e5d..393ec8c 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -1094,6 +1094,9 @@
@Nullable String requiredPermission, @Nullable Bundle options)
throws CanceledException {
try {
+ if (intent != null) {
+ intent.collectExtraIntentKeys();
+ }
String resolvedType = intent != null ?
intent.resolveTypeIfNeeded(context.getContentResolver())
: null;
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index 84a4eb4..a458b4e 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -431,16 +431,19 @@
}
/**
- * Protected so that tests can override and returns something a fixed value.
+ * public so that tests can access and override
*/
@VisibleForTesting
- protected @NonNull DisplayMetrics getDisplayMetrics(int displayId, DisplayAdjustments da) {
+ public @NonNull DisplayMetrics getDisplayMetrics(int displayId, DisplayAdjustments da) {
final DisplayManagerGlobal displayManagerGlobal = DisplayManagerGlobal.getInstance();
final DisplayMetrics dm = new DisplayMetrics();
final DisplayInfo displayInfo = displayManagerGlobal != null
? displayManagerGlobal.getDisplayInfo(displayId) : null;
if (displayInfo != null) {
- displayInfo.getAppMetrics(dm, da);
+ final Configuration dajConfig = da.getConfiguration();
+ displayInfo.getAppMetrics(dm, da.getCompatibilityInfo(),
+ (mResDisplayId == displayId && Configuration.EMPTY.equals(dajConfig))
+ ? mResConfiguration : dajConfig);
} else {
dm.setToDefaults();
}
@@ -1977,6 +1980,7 @@
public void registerAllResourcesReference(@NonNull Resources resources) {
if (android.content.res.Flags.registerResourcePaths()) {
synchronized (mLock) {
+ cleanupReferences(mAllResourceReferences, mAllResourceReferencesQueue);
mAllResourceReferences.add(
new WeakReference<>(resources, mAllResourceReferencesQueue));
}
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index af242dd..e882bb5 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -275,7 +275,10 @@
public int parentTaskId;
/**
- * Whether this task is focused.
+ * Whether this task is focused on the display. This means the task receives input events that
+ * target the display.
+ * CAUTION: This can be true for multiple tasks especially when multiple displays are connected
+ * in the system.
* @hide
*/
public boolean isFocused;
diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java
index 7903f1c..2e6f3e1 100644
--- a/core/java/android/app/UiModeManager.java
+++ b/core/java/android/app/UiModeManager.java
@@ -16,6 +16,7 @@
package android.app;
+import static android.app.Flags.enableCurrentModeTypeBinderCache;
import static android.app.Flags.enableNightModeBinderCache;
import android.annotation.CallbackExecutor;
@@ -682,6 +683,53 @@
}
}
+ private Integer getCurrentModeTypeFromServer() {
+ try {
+ if (sGlobals != null) {
+ return sGlobals.mService.getCurrentModeType();
+ }
+ return Configuration.UI_MODE_TYPE_NORMAL;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+
+ /**
+ * Retrieve the current running mode type for the user.
+ */
+ private final IpcDataCache.QueryHandler<Void, Integer> mCurrentModeTypeQuery =
+ new IpcDataCache.QueryHandler<>() {
+
+ @Override
+ @NonNull
+ public Integer apply(Void query) {
+ return getCurrentModeTypeFromServer();
+ }
+ };
+
+ private static final String CURRENT_MODE_TYPE_API = "getCurrentModeType";
+
+ /**
+ * Cache the current running mode type for a user.
+ */
+ private final IpcDataCache<Void, Integer> mCurrentModeTypeCache =
+ new IpcDataCache<>(1, IpcDataCache.MODULE_SYSTEM,
+ CURRENT_MODE_TYPE_API, /* cacheName= */ "CurrentModeTypeCache",
+ mCurrentModeTypeQuery);
+
+ /**
+ * Invalidate the current mode type cache.
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_CURRENT_MODE_TYPE_BINDER_CACHE)
+ public static void invalidateCurrentModeTypeCache() {
+ IpcDataCache.invalidateCache(IpcDataCache.MODULE_SYSTEM,
+ CURRENT_MODE_TYPE_API);
+ }
+
+
/**
* Return the current running mode type. May be one of
* {@link Configuration#UI_MODE_TYPE_NORMAL Configuration.UI_MODE_TYPE_NORMAL},
@@ -693,14 +741,11 @@
* {@link Configuration#UI_MODE_TYPE_VR_HEADSET Configuration.UI_MODE_TYPE_VR_HEADSET}.
*/
public int getCurrentModeType() {
- if (sGlobals != null) {
- try {
- return sGlobals.mService.getCurrentModeType();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ if (enableCurrentModeTypeBinderCache()) {
+ return mCurrentModeTypeCache.query(null);
+ } else {
+ return getCurrentModeTypeFromServer();
}
- return Configuration.UI_MODE_TYPE_NORMAL;
}
/**
diff --git a/core/java/android/app/admin/SecurityLog.java b/core/java/android/app/admin/SecurityLog.java
index beb93fd..eb0ea1e 100644
--- a/core/java/android/app/admin/SecurityLog.java
+++ b/core/java/android/app/admin/SecurityLog.java
@@ -16,7 +16,10 @@
package android.app.admin;
+import static android.nfc.Flags.FLAG_NFC_STATE_CHANGE_SECURITY_LOG_EVENT_ENABLED;
+
import android.Manifest;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -100,6 +103,8 @@
TAG_PACKAGE_UPDATED,
TAG_PACKAGE_UNINSTALLED,
TAG_BACKUP_SERVICE_TOGGLED,
+ TAG_NFC_ENABLED,
+ TAG_NFC_DISABLED,
})
public @interface SecurityLogTag {}
@@ -610,6 +615,18 @@
*/
public static final int TAG_BACKUP_SERVICE_TOGGLED =
SecurityLogTags.SECURITY_BACKUP_SERVICE_TOGGLED;
+
+ /**
+ * Indicates that NFC service is enabled. There is no extra payload in the log event.
+ */
+ @FlaggedApi(FLAG_NFC_STATE_CHANGE_SECURITY_LOG_EVENT_ENABLED)
+ public static final int TAG_NFC_ENABLED = SecurityLogTags.SECURITY_NFC_ENABLED;
+
+ /**
+ * Indicates that NFC service is disabled. There is no extra payload in the log event.
+ */
+ @FlaggedApi(FLAG_NFC_STATE_CHANGE_SECURITY_LOG_EVENT_ENABLED)
+ public static final int TAG_NFC_DISABLED = SecurityLogTags.SECURITY_NFC_DISABLED;
/**
* Event severity level indicating that the event corresponds to normal workflow.
*/
diff --git a/core/java/android/app/admin/SecurityLogTags.logtags b/core/java/android/app/admin/SecurityLogTags.logtags
index 7b3aa7b..8f22c76 100644
--- a/core/java/android/app/admin/SecurityLogTags.logtags
+++ b/core/java/android/app/admin/SecurityLogTags.logtags
@@ -48,4 +48,6 @@
210041 security_package_installed (package_name|3),(version_code|1),(user_id|1)
210042 security_package_updated (package_name|3),(version_code|1),(user_id|1)
210043 security_package_uninstalled (package_name|3),(version_code|1),(user_id|1)
-210044 security_backup_service_toggled (package|3),(admin_user|1),(enabled|1)
\ No newline at end of file
+210044 security_backup_service_toggled (package|3),(admin_user|1),(enabled|1)
+210045 security_nfc_enabled
+210046 security_nfc_disabled
\ No newline at end of file
diff --git a/core/java/android/app/appfunctions/AppFunctionManagerHelper.java b/core/java/android/app/appfunctions/AppFunctionManagerHelper.java
index fe2db49..64dece9 100644
--- a/core/java/android/app/appfunctions/AppFunctionManagerHelper.java
+++ b/core/java/android/app/appfunctions/AppFunctionManagerHelper.java
@@ -16,6 +16,8 @@
package android.app.appfunctions;
+import static android.app.appfunctions.AppFunctionManager.APP_FUNCTION_STATE_DEFAULT;
+import static android.app.appfunctions.AppFunctionManager.APP_FUNCTION_STATE_ENABLED;
import static android.app.appfunctions.AppFunctionRuntimeMetadata.PROPERTY_APP_FUNCTION_STATIC_METADATA_QUALIFIED_ID;
import static android.app.appfunctions.AppFunctionRuntimeMetadata.PROPERTY_ENABLED;
import static android.app.appfunctions.AppFunctionStaticMetadataHelper.APP_FUNCTION_INDEXER_PACKAGE;
@@ -166,15 +168,18 @@
if (runtimeMetadataResults.isEmpty()) {
throw new IllegalArgumentException("App function not found.");
}
- boolean[] enabled =
+ long enabled =
runtimeMetadataResults
.getFirst()
.getGenericDocument()
- .getPropertyBooleanArray(PROPERTY_ENABLED);
- if (enabled != null && enabled.length != 0) {
- return enabled[0];
+ .getPropertyLong(PROPERTY_ENABLED);
+ // If enabled is not equal to APP_FUNCTION_STATE_DEFAULT, it means it IS overridden and
+ // we should return the overridden value.
+ if (enabled != APP_FUNCTION_STATE_DEFAULT) {
+ return enabled == APP_FUNCTION_STATE_ENABLED;
}
- // Runtime metadata not found. Using the default value in the static metadata.
+ // Runtime metadata not found or enabled is equal to APP_FUNCTION_STATE_DEFAULT.
+ // Using the default value in the static metadata.
return joinedStaticRuntimeResults
.getFirst()
.getGenericDocument()
diff --git a/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java b/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java
index 8b7f326..08ecced 100644
--- a/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java
+++ b/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java
@@ -16,11 +16,15 @@
package android.app.appfunctions;
+import static android.app.appfunctions.AppFunctionManager.APP_FUNCTION_STATE_DEFAULT;
+import static android.app.appfunctions.AppFunctionManager.APP_FUNCTION_STATE_DISABLED;
+import static android.app.appfunctions.AppFunctionManager.APP_FUNCTION_STATE_ENABLED;
import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.appfunctions.AppFunctionManager.EnabledState;
import android.app.appsearch.AppSearchSchema;
import android.app.appsearch.GenericDocument;
@@ -162,15 +166,13 @@
* Returns if the function is set to be enabled or not. If not set, the {@link
* AppFunctionStaticMetadataHelper#STATIC_PROPERTY_ENABLED_BY_DEFAULT} value would be used.
*/
- @Nullable
- public Boolean getEnabled() {
- // We can't use getPropertyBoolean here. getPropertyBoolean returns false instead of null
- // if the value is missing.
- boolean[] enabled = getPropertyBooleanArray(PROPERTY_ENABLED);
- if (enabled == null || enabled.length == 0) {
- return null;
- }
- return enabled[0];
+ @EnabledState
+ public int getEnabled() {
+ // getPropertyLong returns the first long associated with the given path or default value 0
+ // if there is no such value or the value is of a different type.
+ // APP_FUNCTION_STATE_DEFAULT also equals 0 which means the returned value will be 0 when an
+ // app as either never changed the enabled bit at runtime or has reset it to the default.
+ return (int) getPropertyLong(PROPERTY_ENABLED);
}
/** Returns the qualified id linking to the static metadata of the app function. */
@@ -217,12 +219,14 @@
* TODO(369683073) Replace the tristate Boolean with IntDef EnabledState.
*/
@NonNull
- public Builder setEnabled(@Nullable Boolean enabled) {
- if (enabled == null) {
- setPropertyBoolean(PROPERTY_ENABLED);
- } else {
- setPropertyBoolean(PROPERTY_ENABLED, enabled);
+ public Builder setEnabled(@EnabledState int enabledState) {
+ if (enabledState != APP_FUNCTION_STATE_DEFAULT
+ && enabledState != APP_FUNCTION_STATE_ENABLED
+ && enabledState != APP_FUNCTION_STATE_DISABLED) {
+ throw new IllegalArgumentException(
+ "Value of EnabledState is unsupported.");
}
+ setPropertyLong(PROPERTY_ENABLED, enabledState);
return this;
}
diff --git a/core/java/android/app/appfunctions/OWNERS b/core/java/android/app/appfunctions/OWNERS
index c6827cc..6a69e15 100644
--- a/core/java/android/app/appfunctions/OWNERS
+++ b/core/java/android/app/appfunctions/OWNERS
@@ -4,3 +4,4 @@
tonymak@google.com
mingweiliao@google.com
anothermark@google.com
+utkarshnigam@google.com
diff --git a/core/java/android/app/appfunctions/TEST_MAPPING b/core/java/android/app/appfunctions/TEST_MAPPING
index 91e82ec..27517c8 100644
--- a/core/java/android/app/appfunctions/TEST_MAPPING
+++ b/core/java/android/app/appfunctions/TEST_MAPPING
@@ -1,10 +1,7 @@
{
- "postsubmit": [
+ "imports": [
{
- "name": "FrameworksAppFunctionsTests"
- },
- {
- "name": "CtsAppFunctionTestCases"
+ "path": "frameworks/base/services/appfunctions/TEST_MAPPING"
}
]
}
\ No newline at end of file
diff --git a/core/java/android/app/ui_mode_manager.aconfig b/core/java/android/app/ui_mode_manager.aconfig
index 9f44a4d..05b46e0 100644
--- a/core/java/android/app/ui_mode_manager.aconfig
+++ b/core/java/android/app/ui_mode_manager.aconfig
@@ -9,4 +9,15 @@
metadata {
purpose: PURPOSE_BUGFIX
}
+}
+
+flag {
+ namespace: "systemui"
+ name: "enable_current_mode_type_binder_cache"
+ description: "Enables the use of binder caching for current running mode type."
+ bug: "362572732"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
\ No newline at end of file
diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl
index 6fe0a73..40debe8 100644
--- a/core/java/android/companion/virtual/IVirtualDevice.aidl
+++ b/core/java/android/companion/virtual/IVirtualDevice.aidl
@@ -31,6 +31,8 @@
import android.content.IntentFilter;
import android.graphics.Point;
import android.graphics.PointF;
+import android.hardware.display.IVirtualDisplayCallback;
+import android.hardware.display.VirtualDisplayConfig;
import android.hardware.input.VirtualDpadConfig;
import android.hardware.input.VirtualKeyboardConfig;
import android.hardware.input.VirtualKeyEvent;
@@ -93,6 +95,18 @@
*/
boolean canCreateMirrorDisplays();
+ /*
+ * Turns off all trusted non-mirror displays of the virtual device.
+ */
+ @EnforcePermission("CREATE_VIRTUAL_DEVICE")
+ void goToSleep();
+
+ /**
+ * Turns on all trusted non-mirror displays of the virtual device.
+ */
+ @EnforcePermission("CREATE_VIRTUAL_DEVICE")
+ void wakeUp();
+
/**
* Closes the virtual device and frees all associated resources.
*/
@@ -137,6 +151,13 @@
void onAudioSessionEnded();
/**
+ * Creates a virtual display and registers it with the display framework.
+ */
+ @EnforcePermission("CREATE_VIRTUAL_DEVICE")
+ int createVirtualDisplay(in VirtualDisplayConfig virtualDisplayConfig,
+ in IVirtualDisplayCallback callback);
+
+ /**
* Creates a new dpad and registers it with the input framework with the given token.
*/
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
@@ -189,6 +210,7 @@
* Returns the ID of the device corresponding to the given token, as registered with the input
* framework.
*/
+ @EnforcePermission("CREATE_VIRTUAL_DEVICE")
int getInputDeviceId(IBinder token);
/**
@@ -260,6 +282,7 @@
/**
* Launches a pending intent on the given display that is owned by this virtual device.
*/
+ @EnforcePermission("CREATE_VIRTUAL_DEVICE")
void launchPendingIntent(int displayId, in PendingIntent pendingIntent,
in ResultReceiver resultReceiver);
@@ -267,6 +290,7 @@
* Returns the current cursor position of the mouse corresponding to the given token, in x and y
* coordinates.
*/
+ @EnforcePermission("CREATE_VIRTUAL_DEVICE")
PointF getCursorPosition(IBinder token);
/** Sets whether to show or hide the cursor while this virtual device is active. */
diff --git a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
index 83e18ec..c98238c 100644
--- a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
+++ b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
@@ -23,8 +23,6 @@
import android.companion.virtual.VirtualDevice;
import android.companion.virtual.VirtualDeviceParams;
import android.content.AttributionSource;
-import android.hardware.display.IVirtualDisplayCallback;
-import android.hardware.display.VirtualDisplayConfig;
/**
* Interface for communication between VirtualDeviceManager and VirtualDeviceManagerService.
@@ -96,18 +94,6 @@
int getDevicePolicy(int deviceId, int policyType);
/**
- * Creates a virtual display owned by a particular virtual device.
- *
- * @param virtualDisplayConfig The configuration used in creating the display
- * @param callback A callback that receives display lifecycle events
- * @param virtualDevice The device that will own this display
- * @param packageName The package name of the calling app
- */
- int createVirtualDisplay(in VirtualDisplayConfig virtualDisplayConfig,
- in IVirtualDisplayCallback callback, in IVirtualDevice virtualDevice,
- String packageName);
-
- /**
* Returns device-specific session id for playback, or AUDIO_SESSION_ID_GENERATE
* if there's none.
*/
diff --git a/core/java/android/companion/virtual/VirtualDeviceInternal.java b/core/java/android/companion/virtual/VirtualDeviceInternal.java
index de20a68..6708cce 100644
--- a/core/java/android/companion/virtual/VirtualDeviceInternal.java
+++ b/core/java/android/companion/virtual/VirtualDeviceInternal.java
@@ -242,6 +242,7 @@
}
}
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@NonNull
List<VirtualSensor> getVirtualSensorList() {
try {
@@ -251,6 +252,25 @@
}
}
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ void goToSleep() {
+ try {
+ mVirtualDevice.goToSleep();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ void wakeUp() {
+ try {
+ mVirtualDevice.wakeUp();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
void launchPendingIntent(
int displayId,
@NonNull PendingIntent pendingIntent,
@@ -272,6 +292,7 @@
}
}
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@Nullable
VirtualDisplay createVirtualDisplay(
@NonNull VirtualDisplayConfig config,
@@ -281,16 +302,15 @@
new DisplayManagerGlobal.VirtualDisplayCallback(callback, executor);
final int displayId;
try {
- displayId = mService.createVirtualDisplay(config, callbackWrapper, mVirtualDevice,
- mContext.getPackageName());
+ displayId = mVirtualDevice.createVirtualDisplay(config, callbackWrapper);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
DisplayManagerGlobal displayManager = DisplayManagerGlobal.getInstance();
- return displayManager.createVirtualDisplayWrapper(config, callbackWrapper,
- displayId);
+ return displayManager.createVirtualDisplayWrapper(config, callbackWrapper, displayId);
}
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
void close() {
try {
// This also takes care of unregistering all virtual sensors.
@@ -304,6 +324,7 @@
}
}
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
void setDevicePolicy(@VirtualDeviceParams.DynamicPolicyType int policyType,
@VirtualDeviceParams.DevicePolicy int devicePolicy) {
switch (policyType) {
@@ -323,6 +344,7 @@
}
}
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
void addActivityPolicyExemption(@NonNull ActivityPolicyExemption exemption) {
try {
mVirtualDevice.addActivityPolicyExemption(exemption);
@@ -331,6 +353,7 @@
}
}
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
void removeActivityPolicyExemption(@NonNull ActivityPolicyExemption exemption) {
try {
mVirtualDevice.removeActivityPolicyExemption(exemption);
@@ -339,6 +362,7 @@
}
}
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
void setDevicePolicyForDisplay(int displayId,
@VirtualDeviceParams.DynamicDisplayPolicyType int policyType,
@VirtualDeviceParams.DevicePolicy int devicePolicy) {
@@ -358,6 +382,7 @@
}
}
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@NonNull
VirtualDpad createVirtualDpad(@NonNull VirtualDpadConfig config) {
try {
@@ -370,6 +395,7 @@
}
}
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@NonNull
VirtualKeyboard createVirtualKeyboard(@NonNull VirtualKeyboardConfig config) {
try {
@@ -382,6 +408,7 @@
}
}
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@NonNull
VirtualMouse createVirtualMouse(@NonNull VirtualMouseConfig config) {
try {
@@ -394,6 +421,7 @@
}
}
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@NonNull
VirtualTouchscreen createVirtualTouchscreen(
@NonNull VirtualTouchscreenConfig config) {
@@ -433,6 +461,7 @@
}
}
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@NonNull
VirtualNavigationTouchpad createVirtualNavigationTouchpad(
@NonNull VirtualNavigationTouchpadConfig config) {
@@ -447,6 +476,7 @@
}
}
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@NonNull
VirtualAudioDevice createVirtualAudioDevice(
@NonNull VirtualDisplay display,
@@ -483,6 +513,7 @@
}
}
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
void setShowPointerIcon(boolean showPointerIcon) {
try {
mVirtualDevice.setShowPointerIcon(showPointerIcon);
@@ -491,6 +522,7 @@
}
}
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
void setDisplayImePolicy(int displayId, @WindowManager.DisplayImePolicy int policy) {
try {
mVirtualDevice.setDisplayImePolicy(displayId, policy);
@@ -532,6 +564,7 @@
}
}
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
void registerIntentInterceptor(
@NonNull IntentFilter interceptorFilter,
@CallbackExecutor @NonNull Executor executor,
@@ -551,6 +584,7 @@
}
}
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
void unregisterIntentInterceptor(
@NonNull VirtualDeviceManager.IntentInterceptorCallback interceptorCallback) {
Objects.requireNonNull(interceptorCallback);
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 473ab27..96700a9 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -614,12 +614,52 @@
*
* @return A list of all sensors for this device, or an empty list if no sensors exist.
*/
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@NonNull
public List<VirtualSensor> getVirtualSensorList() {
return mVirtualDeviceInternal.getVirtualSensorList();
}
/**
+ * Forces all trusted non-mirror displays of the virtual device to turn off.
+ *
+ * <p>After this action, if all displays across all devices, including the default one, are
+ * off, then the physical device will be put to sleep. If the displays of this virtual
+ * device are already off, then nothing will happen.</p>
+ *
+ * <p>Overrides all the wake locks that are held. This is equivalent to pressing a "virtual
+ * power key" to turn off the screen.</p>
+ *
+ * @see #wakeUp()
+ * @see DisplayManager#VIRTUAL_DISPLAY_FLAG_TRUSTED
+ * @see DisplayManager#VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
+ */
+ @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ public void goToSleep() {
+ mVirtualDeviceInternal.goToSleep();
+ }
+
+ /**
+ * Forces all trusted non-mirror displays of the virtual device to turn on.
+ *
+ * <p>If the displays of this virtual device are turned off, then they will be turned on.
+ * Additionally, if the device is asleep it will be awoken. If the displays of this virtual
+ * device are already on, then nothing will happen.</p>
+ *
+ * <p>This is equivalent to pressing a "virtual power key" to turn on the screen.</p>
+ *
+ * @see #goToSleep()
+ * @see DisplayManager#VIRTUAL_DISPLAY_FLAG_TRUSTED
+ * @see DisplayManager#VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
+ */
+ @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ public void wakeUp() {
+ mVirtualDeviceInternal.wakeUp();
+ }
+
+ /**
* Launches a given pending intent on the give display ID.
*
* @param displayId The display to launch the pending intent on. This display must be
@@ -637,6 +677,7 @@
* on the virtual display, or one of the {@code LAUNCH_FAILED} status explaining why it
* failed.
*/
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void launchPendingIntent(
int displayId,
@NonNull PendingIntent pendingIntent,
@@ -677,6 +718,7 @@
* VirtualDisplay.Callback)}
*/
@Deprecated
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@Nullable
public VirtualDisplay createVirtualDisplay(
@IntRange(from = 1) int width,
@@ -714,6 +756,7 @@
*
* @see DisplayManager#createVirtualDisplay
*/
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@Nullable
public VirtualDisplay createVirtualDisplay(
@NonNull VirtualDisplayConfig config,
diff --git a/core/java/android/content/ClipData.java b/core/java/android/content/ClipData.java
index b421339..ff0bb25 100644
--- a/core/java/android/content/ClipData.java
+++ b/core/java/android/content/ClipData.java
@@ -1149,7 +1149,7 @@
for (int i = 0; i < size; i++) {
final Item item = mItems.get(i);
if (item.mIntent != null) {
- item.mIntent.prepareToLeaveProcess(leavingPackage);
+ item.mIntent.prepareToLeaveProcess(leavingPackage, false);
}
if (item.mUri != null && leavingPackage) {
if (StrictMode.vmFileUriExposureEnabled()) {
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 9a93ec4..0bb0027 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -12221,6 +12221,8 @@
/**
* Collects keys in the extra bundle whose value are intents.
+ * With these keys collected on the client side, the system server would only unparcel values
+ * of these keys and create IntentCreatorToken for them.
* @hide
*/
public void collectExtraIntentKeys() {
@@ -12583,22 +12585,29 @@
*/
@android.ravenwood.annotation.RavenwoodThrow
public void prepareToLeaveProcess(boolean leavingPackage) {
+ prepareToLeaveProcess(leavingPackage, true);
+ }
+
+ /**
+ * @hide
+ */
+ void prepareToLeaveProcess(boolean leavingPackage, boolean isTopLevel) {
setAllowFds(false);
if (mSelector != null) {
- mSelector.prepareToLeaveProcess(leavingPackage);
+ mSelector.prepareToLeaveProcess(leavingPackage, false);
}
if (mClipData != null) {
mClipData.prepareToLeaveProcess(leavingPackage, getFlags());
}
if (mOriginalIntent != null) {
- mOriginalIntent.prepareToLeaveProcess(leavingPackage);
+ mOriginalIntent.prepareToLeaveProcess(leavingPackage, false);
}
if (mExtras != null && !mExtras.isParcelled()) {
final Object intent = mExtras.get(Intent.EXTRA_INTENT);
if (intent instanceof Intent) {
- ((Intent) intent).prepareToLeaveProcess(leavingPackage);
+ ((Intent) intent).prepareToLeaveProcess(leavingPackage, false);
}
}
@@ -12672,6 +12681,10 @@
StrictMode.onUnsafeIntentLaunch(this);
}
}
+
+ if (isTopLevel) {
+ collectExtraIntentKeys();
+ }
}
/**
diff --git a/core/java/android/content/IntentSender.java b/core/java/android/content/IntentSender.java
index ca6d86a..f406927 100644
--- a/core/java/android/content/IntentSender.java
+++ b/core/java/android/content/IntentSender.java
@@ -288,6 +288,9 @@
@Nullable Executor executor, @Nullable OnFinished onFinished)
throws SendIntentException {
try {
+ if (intent != null) {
+ intent.collectExtraIntentKeys();
+ }
String resolvedType = intent != null ?
intent.resolveTypeIfNeeded(context.getContentResolver())
: null;
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index d5edc92..cf65539 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -456,7 +456,6 @@
}
}
-
flag {
name: "caching_development_improvements"
namespace: "multiuser"
@@ -464,3 +463,13 @@
bug: "364947162"
is_fixed_read_only: true
}
+
+flag {
+ name: "show_custom_unlock_title_inside_private_profile"
+ namespace: "profile_experiences"
+ description: "When private space is unlocked show dynamic title in unlock factor screens based on lock factor set for the profile"
+ bug: "323835257"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/hardware/DisplayLuts.java b/core/java/android/hardware/DisplayLuts.java
new file mode 100644
index 0000000..b162ad6
--- /dev/null
+++ b/core/java/android/hardware/DisplayLuts.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware;
+
+import android.annotation.NonNull;
+import android.util.IntArray;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @hide
+ */
+public final class DisplayLuts {
+ private IntArray mOffsets;
+ private int mTotalLength;
+
+ private List<float[]> mLutBuffers;
+ private IntArray mLutDimensions;
+ private IntArray mLutSizes;
+ private IntArray mLutSamplingKeys;
+ private static final int LUT_LENGTH_LIMIT = 100000;
+
+ public DisplayLuts() {
+ mOffsets = new IntArray();
+ mTotalLength = 0;
+
+ mLutBuffers = new ArrayList<>();
+ mLutDimensions = new IntArray();
+ mLutSizes = new IntArray();
+ mLutSamplingKeys = new IntArray();
+ }
+
+ /**
+ * Add the lut to be applied.
+ *
+ * @param buffer
+ * @param dimension either 1D or 3D
+ * @param size
+ * @param samplingKey
+ */
+ public void addLut(@NonNull float[] buffer, @LutProperties.Dimension int dimension,
+ int size, @LutProperties.SamplingKey int samplingKey) {
+
+ int lutLength = 0;
+ if (dimension == LutProperties.ONE_DIMENSION) {
+ lutLength = size;
+ } else if (dimension == LutProperties.THREE_DIMENSION) {
+ lutLength = size * size * size;
+ } else {
+ clear();
+ throw new IllegalArgumentException("The dimension is either 1D or 3D!");
+ }
+
+ if (lutLength >= LUT_LENGTH_LIMIT) {
+ clear();
+ throw new IllegalArgumentException("The lut length is too big to handle!");
+ }
+
+ mOffsets.add(mTotalLength);
+ mTotalLength += lutLength;
+
+ mLutBuffers.add(buffer);
+ mLutDimensions.add(dimension);
+ mLutSizes.add(size);
+ mLutSamplingKeys.add(samplingKey);
+ }
+
+ private void clear() {
+ mTotalLength = 0;
+ mOffsets.clear();
+ mLutBuffers.clear();
+ mLutDimensions.clear();
+ mLutSamplingKeys.clear();
+ }
+
+ /**
+ * @return the array of Lut buffers
+ */
+ public float[] getLutBuffers() {
+ float[] buffer = new float[mTotalLength];
+
+ for (int i = 0; i < mLutBuffers.size(); i++) {
+ float[] lutBuffer = mLutBuffers.get(i);
+ System.arraycopy(lutBuffer, 0, buffer, mOffsets.get(i), lutBuffer.length);
+ }
+ return buffer;
+ }
+
+ /**
+ * @return the starting point of each lut memory region of the lut buffer
+ */
+ public int[] getOffsets() {
+ return mOffsets.toArray();
+ }
+
+ /**
+ * @return the array of Lut size
+ */
+ public int[] getLutSizes() {
+ return mLutSizes.toArray();
+ }
+
+ /**
+ * @return the array of Lut dimension
+ */
+ public int[] getLutDimensions() {
+ return mLutDimensions.toArray();
+ }
+
+ /**
+ * @return the array of sampling key
+ */
+ public int[] getLutSamplingKeys() {
+ return mLutSamplingKeys.toArray();
+ }
+}
diff --git a/core/java/android/hardware/LutProperties.java b/core/java/android/hardware/LutProperties.java
new file mode 100644
index 0000000..57f8a4e
--- /dev/null
+++ b/core/java/android/hardware/LutProperties.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Lut properties class.
+ *
+ * A Lut (Look-Up Table) is a pre-calculated table for color transformation.
+ *
+ * @hide
+ */
+public final class LutProperties {
+ private final @Dimension int mDimension;
+ private final long mSize;
+ private final @SamplingKey int[] mSamplingKeys;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"SAMPLING_KEY_"}, value = {
+ SAMPLING_KEY_RGB,
+ SAMPLING_KEY_MAX_RGB
+ })
+ public @interface SamplingKey {
+ }
+
+ /** use r,g,b channel as the gain value of a Lut */
+ public static final int SAMPLING_KEY_RGB = 0;
+
+ /** use max of r,g,b channel as the gain value of a Lut */
+ public static final int SAMPLING_KEY_MAX_RGB = 1;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {
+ ONE_DIMENSION,
+ THREE_DIMENSION
+ })
+ public @interface Dimension {
+ }
+
+ /** The Lut is one dimensional */
+ public static final int ONE_DIMENSION = 1;
+
+ /** The Lut is three dimensional */
+ public static final int THREE_DIMENSION = 3;
+
+ public @Dimension int getDimension() {
+ return mDimension;
+ }
+
+ /**
+ * @return the size of the Lut.
+ */
+ public long getSize() {
+ return mSize;
+ }
+
+ /**
+ * @return the list of sampling keys
+ */
+ public @SamplingKey int[] getSamplingKeys() {
+ if (mSamplingKeys.length == 0) {
+ throw new IllegalStateException("no sampling key!");
+ }
+ return mSamplingKeys;
+ }
+
+ /* use in the native code */
+ private LutProperties(@Dimension int dimension, long size, @SamplingKey int[] samplingKeys) {
+ if (dimension != ONE_DIMENSION || dimension != THREE_DIMENSION) {
+ throw new IllegalArgumentException("The dimension is either 1 or 3!");
+ }
+ mDimension = dimension;
+ mSize = size;
+ mSamplingKeys = samplingKeys;
+ }
+}
diff --git a/core/java/android/hardware/OverlayProperties.java b/core/java/android/hardware/OverlayProperties.java
index 7b452a8..24cfc1b 100644
--- a/core/java/android/hardware/OverlayProperties.java
+++ b/core/java/android/hardware/OverlayProperties.java
@@ -50,6 +50,8 @@
// Invoked on destruction
private Runnable mCloser;
+ private LutProperties[] mLutProperties;
+
private OverlayProperties(long nativeObject) {
if (nativeObject != 0) {
mCloser = sRegistry.registerNativeAllocation(this, nativeObject);
@@ -70,6 +72,20 @@
}
/**
+ * Gets the lut properties of the display.
+ * @hide
+ */
+ public LutProperties[] getLutProperties() {
+ if (mNativeObject == 0) {
+ return null;
+ }
+ if (mLutProperties == null) {
+ mLutProperties = nGetLutProperties(mNativeObject);
+ }
+ return mLutProperties;
+ }
+
+ /**
* Indicates that hardware composition of a buffer encoded with the provided {@link DataSpace}
* and {@link HardwareBuffer.Format} is supported on the device.
*
@@ -140,4 +156,5 @@
long nativeObject, int dataspace, int format);
private static native void nWriteOverlayPropertiesToParcel(long nativeObject, Parcel dest);
private static native long nReadOverlayPropertiesFromParcel(Parcel in);
-}
+ private static native LutProperties[] nGetLutProperties(long nativeObject);
+}
\ No newline at end of file
diff --git a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
index b9eba9c..ce8661e 100644
--- a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
@@ -1028,6 +1028,9 @@
// Camera is already closed, so nothing left to do
if (DEBUG) Log.v(TAG, mIdString +
"Camera was already closed or busy, skipping unconfigure");
+ } catch (SecurityException e) {
+ // UID state change revoked camera permission
+ Log.e(TAG, mIdString + "Exception while unconfiguring outputs: ", e);
}
}
}
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 36e816a..6c1aa90 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -92,9 +92,10 @@
boolean waitForNegativeProximity);
/**
- * Returns {@code true} if the proximity sensor screen-off function is available.
+ * Returns {@code true} if the proximity sensor screen-off function is available for the given
+ * display.
*/
- public abstract boolean isProximitySensorAvailable();
+ public abstract boolean isProximitySensorAvailable(int displayId);
/**
* Registers a display group listener which will be informed of the addition, removal, or change
diff --git a/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig b/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig
index 51024ba..6a39365 100644
--- a/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig
+++ b/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig
@@ -43,3 +43,10 @@
description: "Enable usb state update based on udc sysfs"
bug: "339241080"
}
+
+flag {
+ name: "enable_usb_data_signal_staking_internal"
+ namespace: "preload_safety"
+ description: "Enables signal API with staking for internal local service callers"
+ bug: "369382558"
+}
\ No newline at end of file
diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java
index 80546cd..3b5a99e 100644
--- a/core/java/android/os/BinderProxy.java
+++ b/core/java/android/os/BinderProxy.java
@@ -493,6 +493,11 @@
public native boolean pingBinder();
/**
+ * Check to see if the process that the binder is in is still alive.
+ *
+ * Note, this only reflects the last known death state, if the object
+ * is linked to death or has made a transactions since the death occurs.
+ *
* @return false if the hosting process is gone
*/
public native boolean isBinderAlive();
diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl
index 5f62b8b..e85e580 100644
--- a/core/java/android/os/IPowerManager.aidl
+++ b/core/java/android/os/IPowerManager.aidl
@@ -42,7 +42,9 @@
void updateWakeLockWorkSource(IBinder lock, in WorkSource ws, String historyTag);
void updateWakeLockCallback(IBinder lock, IWakeLockCallback callback);
+ @UnsupportedAppUsage
boolean isWakeLockLevelSupported(int level);
+ boolean isWakeLockLevelSupportedWithDisplayId(int level, int displayId);
void userActivity(int displayId, long time, int event, int flags);
void wakeUp(long time, int reason, String details, String opPackageName);
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index b9bae5b..32db3be 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -1344,6 +1344,9 @@
* @see #ON_AFTER_RELEASE
*/
public WakeLock newWakeLock(int levelAndFlags, String tag) {
+ if (android.companion.virtualdevice.flags.Flags.displayPowerManagerApis()) {
+ return newWakeLock(levelAndFlags, tag, mContext.getDisplayId());
+ }
validateWakeLockParameters(levelAndFlags, tag);
return new WakeLock(levelAndFlags, tag, mContext.getOpPackageName(),
Display.INVALID_DISPLAY);
@@ -1734,6 +1737,10 @@
*/
public boolean isWakeLockLevelSupported(int level) {
try {
+ if (android.companion.virtualdevice.flags.Flags.displayPowerManagerApis()) {
+ return mService.isWakeLockLevelSupportedWithDisplayId(
+ level, mContext.getDisplayId());
+ }
return mService.isWakeLockLevelSupported(level);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -1797,6 +1804,9 @@
* @see android.content.Intent#ACTION_SCREEN_OFF
*/
public boolean isInteractive() {
+ if (android.companion.virtualdevice.flags.Flags.displayPowerManagerApis()) {
+ return isInteractive(mContext.getDisplayId());
+ }
return mInteractiveCache.query(null);
}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 461f1e0..3ae9511 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -69,6 +69,8 @@
import android.view.WindowManager.LayoutParams;
import com.android.internal.R;
+import com.android.internal.annotations.CachedProperty;
+import com.android.internal.annotations.CachedPropertyDefaults;
import java.io.IOException;
import java.lang.annotation.Retention;
@@ -90,6 +92,7 @@
*/
@SystemService(Context.USER_SERVICE)
@android.ravenwood.annotation.RavenwoodKeepPartialClass
+@CachedPropertyDefaults()
public class UserManager {
private static final String TAG = "UserManager";
@@ -108,6 +111,9 @@
/** Whether the device is in headless system user mode; null until cached. */
private static Boolean sIsHeadlessSystemUser = null;
+ /** Generated class containing IpcDataCaches. */
+ private final Object mIpcDataCache = new UserManagerCache();
+
/** Maximum length of username.
* @hide
*/
@@ -3766,62 +3772,18 @@
return isUserUnlocked(user.getIdentifier());
}
- private static final String CACHE_KEY_IS_USER_UNLOCKED_PROPERTY =
- PropertyInvalidatedCache.createPropertyName(
- PropertyInvalidatedCache.MODULE_SYSTEM, "is_user_unlocked");
-
- private final PropertyInvalidatedCache<Integer, Boolean> mIsUserUnlockedCache =
- new PropertyInvalidatedCache<Integer, Boolean>(
- 32, CACHE_KEY_IS_USER_UNLOCKED_PROPERTY) {
- @Override
- public Boolean recompute(Integer query) {
- try {
- return mService.isUserUnlocked(query);
- } catch (RemoteException re) {
- throw re.rethrowFromSystemServer();
- }
- }
- @Override
- public boolean bypass(Integer query) {
- return query < 0;
- }
- };
-
- // Uses IS_USER_UNLOCKED_PROPERTY for invalidation as the APIs have the same dependencies.
- private final PropertyInvalidatedCache<Integer, Boolean> mIsUserUnlockingOrUnlockedCache =
- new PropertyInvalidatedCache<Integer, Boolean>(
- 32, CACHE_KEY_IS_USER_UNLOCKED_PROPERTY) {
- @Override
- public Boolean recompute(Integer query) {
- try {
- return mService.isUserUnlockingOrUnlocked(query);
- } catch (RemoteException re) {
- throw re.rethrowFromSystemServer();
- }
- }
- @Override
- public boolean bypass(Integer query) {
- return query < 0;
- }
- };
-
/** @hide */
@UnsupportedAppUsage
@RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
+ @CachedProperty(modsFlagOnOrNone = {}, api = "is_user_unlocked")
public boolean isUserUnlocked(@UserIdInt int userId) {
- return mIsUserUnlockedCache.query(userId);
- }
-
- /** @hide */
- public void disableIsUserUnlockedCache() {
- mIsUserUnlockedCache.disableLocal();
- mIsUserUnlockingOrUnlockedCache.disableLocal();
+ return ((UserManagerCache) mIpcDataCache).isUserUnlocked(mService::isUserUnlocked, userId);
}
/** @hide */
public static final void invalidateIsUserUnlockedCache() {
- PropertyInvalidatedCache.invalidateCache(CACHE_KEY_IS_USER_UNLOCKED_PROPERTY);
+ UserManagerCache.invalidateUserUnlocked();
}
/**
@@ -3852,8 +3814,10 @@
/** @hide */
@RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
+ @CachedProperty(modsFlagOnOrNone = {}, api = "is_user_unlocked")
public boolean isUserUnlockingOrUnlocked(@UserIdInt int userId) {
- return mIsUserUnlockingOrUnlockedCache.query(userId);
+ return ((UserManagerCache) mIpcDataCache)
+ .isUserUnlockingOrUnlocked(mService::isUserUnlockingOrUnlocked, userId);
}
/**
@@ -5686,31 +5650,9 @@
}
}
- private static final String CACHE_KEY_QUIET_MODE_ENABLED_PROPERTY =
- PropertyInvalidatedCache.createPropertyName(
- PropertyInvalidatedCache.MODULE_SYSTEM, "quiet_mode_enabled");
-
- private final PropertyInvalidatedCache<Integer, Boolean> mQuietModeEnabledCache =
- new PropertyInvalidatedCache<Integer, Boolean>(
- 32, CACHE_KEY_QUIET_MODE_ENABLED_PROPERTY) {
- @Override
- public Boolean recompute(Integer query) {
- try {
- return mService.isQuietModeEnabled(query);
- } catch (RemoteException re) {
- throw re.rethrowFromSystemServer();
- }
- }
- @Override
- public boolean bypass(Integer query) {
- return query < 0;
- }
- };
-
-
/** @hide */
public static final void invalidateQuietModeEnabledCache() {
- PropertyInvalidatedCache.invalidateCache(CACHE_KEY_QUIET_MODE_ENABLED_PROPERTY);
+ UserManagerCache.invalidateQuietModeEnabled();
}
/**
@@ -5719,13 +5661,15 @@
* @param userHandle The user handle of the profile to be queried.
* @return true if the profile is in quiet mode, false otherwise.
*/
+ @CachedProperty(modsFlagOnOrNone = {})
public boolean isQuietModeEnabled(UserHandle userHandle) {
- if (android.multiuser.Flags.cacheQuietModeState()){
+ if (android.multiuser.Flags.cacheQuietModeState()) {
final int userId = userHandle.getIdentifier();
if (userId < 0) {
return false;
}
- return mQuietModeEnabledCache.query(userId);
+ return ((UserManagerCache) mIpcDataCache).isQuietModeEnabled(
+ (UserHandle uh) -> mService.isQuietModeEnabled(uh.getIdentifier()), userHandle);
}
try {
return mService.isQuietModeEnabled(userHandle.getIdentifier());
@@ -6424,41 +6368,21 @@
Settings.Global.DEVICE_DEMO_MODE, 0) > 0;
}
- private static final String CACHE_KEY_USER_SERIAL_NUMBER_PROPERTY =
- PropertyInvalidatedCache.createPropertyName(
- PropertyInvalidatedCache.MODULE_SYSTEM, "user_serial_number");
-
- private final PropertyInvalidatedCache<Integer, Integer> mUserSerialNumberCache =
- new PropertyInvalidatedCache<Integer, Integer>(
- 32, CACHE_KEY_USER_SERIAL_NUMBER_PROPERTY) {
- @Override
- public Integer recompute(Integer query) {
- try {
- return mService.getUserSerialNumber(query);
- } catch (RemoteException re) {
- throw re.rethrowFromSystemServer();
- }
- }
- @Override
- public boolean bypass(Integer query) {
- return query <= 0;
- }
- };
-
-
/** @hide */
public static final void invalidateUserSerialNumberCache() {
- PropertyInvalidatedCache.invalidateCache(CACHE_KEY_USER_SERIAL_NUMBER_PROPERTY);
+ UserManagerCache.invalidateUserSerialNumber();
}
/**
* Returns a serial number on this device for a given userId. User handles can be recycled
- * when deleting and creating users, but serial numbers are not reused until the device is wiped.
+ * when deleting and creating users, but serial numbers are not reused until the device is
+ * wiped.
* @param userId
* @return a serial number associated with that user, or -1 if the userId is not valid.
* @hide
*/
@UnsupportedAppUsage
+ @CachedProperty(modsFlagOnOrNone = {})
public int getUserSerialNumber(@UserIdInt int userId) {
// Read only flag should is to fix early access to this API
// cacheUserSerialNumber to be removed after the
@@ -6470,7 +6394,8 @@
if (userId == UserHandle.USER_SYSTEM) {
return UserHandle.USER_SERIAL_SYSTEM;
}
- return mUserSerialNumberCache.query(userId);
+ return ((UserManagerCache) mIpcDataCache).getUserSerialNumber(
+ mService::getUserSerialNumber, userId);
}
try {
return mService.getUserSerialNumber(userId);
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index a2c41c1..1a15d09 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2350,6 +2350,21 @@
"android.settings.ALL_APPS_NOTIFICATION_SETTINGS_FOR_REVIEW";
/**
+ * Activity Action: Show the permission screen for allowing apps to post promoted notifications.
+ * <p>
+ * Input: {@link #EXTRA_APP_PACKAGE}, the package to display.
+ * <p>
+ * In some cases, a matching Activity may not exist, so ensure you
+ * safeguard against this.
+ * <p>
+ * Output: Nothing.
+ */
+ @FlaggedApi(android.app.Flags.FLAG_API_RICH_ONGOING)
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_APP_NOTIFICATION_PROMOTION_SETTINGS
+ = "android.settings.APP_NOTIFICATION_PROMOTION_SETTINGS";
+
+ /**
* Activity Action: Show notification settings for a single app.
* <p>
* Input: {@link #EXTRA_APP_PACKAGE}, the package to display.
@@ -17798,12 +17813,6 @@
public static final String FORCE_NON_DEBUGGABLE_FINAL_BUILD_FOR_COMPAT =
"force_non_debuggable_final_build_for_compat";
- /**
- * Flag to enable the use of ApplicationInfo for getting not-launched status.
- *
- * @hide
- */
- public static final String ENABLE_USE_APP_INFO_NOT_LAUNCHED = "use_app_info_not_launched";
/**
* Current version of signed configuration applied.
@@ -20149,6 +20158,12 @@
public static final int PHONE_SWITCHING_STATUS_IN_PROGRESS_MIGRATION_SUCCESS = 11;
/**
+ * Phone switching has finished account match step.
+ * @hide
+ */
+ public static final int PHONE_SWITCHING_STATUS_ACCOUNTS_MATCHED = 12;
+
+ /**
* Phone switching request source
* @hide
*/
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index a86c961..aedf8e0 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -106,3 +106,10 @@
description: "Clear StrongAuth on add credential"
bug: "320817991"
}
+
+flag {
+ name: "afl_api"
+ namespace: "platform_security"
+ description: "AFL feature"
+ bug: "365994454"
+}
diff --git a/core/java/android/service/carrier/CarrierMessagingServiceWrapper.java b/core/java/android/service/carrier/CarrierMessagingServiceWrapper.java
index de2c6f77..afff8fe 100644
--- a/core/java/android/service/carrier/CarrierMessagingServiceWrapper.java
+++ b/core/java/android/service/carrier/CarrierMessagingServiceWrapper.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -28,8 +29,10 @@
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.telephony.SmsMessage;
+import com.android.internal.telephony.flags.Flags;
import com.android.internal.util.Preconditions;
import java.util.List;
@@ -91,8 +94,12 @@
mOnServiceReadyCallback = onServiceReadyCallback;
mServiceReadyCallbackExecutor = executor;
mContext = context;
- return context.bindService(intent, mCarrierMessagingServiceConnection,
- Context.BIND_AUTO_CREATE);
+ return Flags.supportCarrierServicesForHsum()
+ ? context.bindServiceAsUser(intent, mCarrierMessagingServiceConnection,
+ Context.BIND_AUTO_CREATE,
+ UserHandle.of(ActivityManager.getCurrentUser()))
+ : context.bindService(intent, mCarrierMessagingServiceConnection,
+ Context.BIND_AUTO_CREATE);
}
/**
diff --git a/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java b/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
index f123a96..3181556 100644
--- a/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
+++ b/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
@@ -129,6 +129,16 @@
* @hide
*/
public static final String MODEL_UNLOADED_BUNDLE_KEY = "model_unloaded";
+ /**
+ * @hide
+ */
+ public static final String MODEL_LOADED_BROADCAST_INTENT =
+ "android.service.ondeviceintelligence.MODEL_LOADED";
+ /**
+ * @hide
+ */
+ public static final String MODEL_UNLOADED_BROADCAST_INTENT =
+ "android.service.ondeviceintelligence.MODEL_UNLOADED";
/**
* @hide
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 90ceb44..19e244a 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -49,6 +49,7 @@
import android.gui.StalledTransactionInfo;
import android.gui.TrustedOverlay;
import android.hardware.DataSpace;
+import android.hardware.DisplayLuts;
import android.hardware.HardwareBuffer;
import android.hardware.OverlayProperties;
import android.hardware.SyncFence;
@@ -152,6 +153,8 @@
long nativeObject, int priority);
private static native void nativeSetWindowCrop(long transactionObj, long nativeObject,
int l, int t, int r, int b);
+ private static native void nativeSetCrop(long transactionObj, long nativeObject,
+ float l, float t, float r, float b);
private static native void nativeSetCornerRadius(long transactionObj, long nativeObject,
float cornerRadius);
private static native void nativeSetBackgroundBlurRadius(long transactionObj, long nativeObject,
@@ -305,9 +308,9 @@
private static native StalledTransactionInfo nativeGetStalledTransactionInfo(int pid);
private static native void nativeSetDesiredPresentTimeNanos(long transactionObj,
long desiredPresentTimeNanos);
- private static native void nativeSetFrameTimeline(long transactionObj,
- long vsyncId);
private static native void nativeNotifyShutdown();
+ private static native void nativeSetLuts(long transactionObj, long nativeObject,
+ float[] buffers, int[] slots, int[] dimensions, int[] sizes, int[] samplingKeys);
/**
* Transforms that can be applied to buffers as they are displayed to a window.
@@ -3452,6 +3455,29 @@
}
/**
+ * Bounds the surface and its children to the bounds specified. Size of the surface will be
+ * ignored and only the crop and buffer size will be used to determine the bounds of the
+ * surface. If no crop is specified and the surface has no buffer, the surface bounds is
+ * only constrained by the size of its parent bounds.
+ *
+ * @param sc SurfaceControl to set crop of.
+ * @param crop Bounds of the crop to apply.
+ * @return this This transaction for chaining
+ * @hide
+ */
+ public @NonNull Transaction setCrop(@NonNull SurfaceControl sc, float top, float left,
+ float bottom, float right) {
+ checkPreconditions(sc);
+ if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+ SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+ "setCrop", this, sc, "crop={" + top + ", " + left + ", " +
+ bottom + ", " + right + "}");
+ }
+ nativeSetCrop(mNativeObject, sc.mNativeObject, top, left, bottom, right);
+ return this;
+ }
+
+ /**
* Sets the corner radius of a {@link SurfaceControl}.
* @param sc SurfaceControl
* @param cornerRadius Corner radius in pixels.
@@ -4374,6 +4400,17 @@
return this;
}
+ /** @hide */
+ public @NonNull Transaction setLuts(@NonNull SurfaceControl sc,
+ @NonNull DisplayLuts displayLuts) {
+ checkPreconditions(sc);
+
+ nativeSetLuts(mNativeObject, sc.mNativeObject, displayLuts.getLutBuffers(),
+ displayLuts.getOffsets(), displayLuts.getLutDimensions(),
+ displayLuts.getLutSizes(), displayLuts.getLutSamplingKeys());
+ return this;
+ }
+
/**
* Sets the caching hint for the layer. By default, the caching hint is
* {@link CACHING_ENABLED}.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 8fb17c7..0ca442d 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -7531,6 +7531,7 @@
if (keyEvent.isCanceled()) {
animationCallback.onBackCancelled();
} else {
+ dispatcher.tryInvokeSystemNavigationObserverCallback();
topCallback.onBackInvoked();
}
break;
@@ -7538,6 +7539,7 @@
} else if (topCallback != null) {
if (keyEvent.getAction() == KeyEvent.ACTION_UP) {
if (!keyEvent.isCanceled()) {
+ dispatcher.tryInvokeSystemNavigationObserverCallback();
topCallback.onBackInvoked();
} else {
Log.d(mTag, "Skip onBackInvoked(), reason: keyEvent.isCanceled=true");
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 79ecfe1e..1a45939 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -117,6 +117,8 @@
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import sun.misc.Cleaner;
@@ -4914,7 +4916,22 @@
&& (root.getWindowFlags() & WindowManager.LayoutParams.FLAG_SECURE) == 0) {
ViewNodeBuilder viewStructure = new ViewNodeBuilder();
viewStructure.setAutofillId(view.getAutofillId());
- view.onProvideAutofillStructure(viewStructure, /* flags= */ 0);
+
+ // Post onProvideAutofillStructure to the UI thread
+ final CountDownLatch latch = new CountDownLatch(1);
+ afm.post(
+ () -> {
+ view.onProvideAutofillStructure(viewStructure, /* flags= */ 0);
+ latch.countDown();
+ }
+ );
+ try {
+ latch.await(5000, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ return null;
+ }
+
// TODO(b/141703532): We don't call View#onProvideAutofillVirtualStructure for
// efficiency reason. But this also means we will return null for virtual views
// for now. We will add a new API to fetch the view node info of the virtual
diff --git a/core/java/android/window/OnBackInvokedDispatcher.java b/core/java/android/window/OnBackInvokedDispatcher.java
index 0632a37..02ed57d 100644
--- a/core/java/android/window/OnBackInvokedDispatcher.java
+++ b/core/java/android/window/OnBackInvokedDispatcher.java
@@ -16,11 +16,14 @@
package android.window;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.SuppressLint;
+import com.android.window.flags.Flags;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -43,6 +46,7 @@
@IntDef({
PRIORITY_DEFAULT,
PRIORITY_OVERLAY,
+ PRIORITY_SYSTEM_NAVIGATION_OBSERVER,
})
@Retention(RetentionPolicy.SOURCE)
@interface Priority{}
@@ -67,6 +71,20 @@
int PRIORITY_SYSTEM = -1;
/**
+ * Priority level of {@link OnBackInvokedCallback}s designed to observe system-level back
+ * handling.
+ *
+ * <p>Callbacks registered with this priority do not consume back events. They receive back
+ * events whenever the system handles a back navigation and have no impact on the normal back
+ * navigation flow. Useful for logging or analytics.
+ *
+ * <p>Only one callback with {@link #PRIORITY_SYSTEM_NAVIGATION_OBSERVER} can be registered at a
+ * time.
+ */
+ @FlaggedApi(Flags.FLAG_PREDICTIVE_BACK_PRIORITY_SYSTEM_NAVIGATION_OBSERVER)
+ int PRIORITY_SYSTEM_NAVIGATION_OBSERVER = -2;
+
+ /**
* Registers a {@link OnBackInvokedCallback}.
*
* Within the same priority level, callbacks are invoked in the reverse order in which
diff --git a/core/java/android/window/ProxyOnBackInvokedDispatcher.java b/core/java/android/window/ProxyOnBackInvokedDispatcher.java
index 56c05b2..dfc4a58 100644
--- a/core/java/android/window/ProxyOnBackInvokedDispatcher.java
+++ b/core/java/android/window/ProxyOnBackInvokedDispatcher.java
@@ -16,6 +16,8 @@
package android.window;
+import static com.android.window.flags.Flags.predictiveBackPrioritySystemNavigationObserver;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -95,7 +97,7 @@
synchronized (mLock) {
mCallbacks.add(Pair.create(callback, priority));
if (mActualDispatcher != null) {
- if (priority <= PRIORITY_SYSTEM) {
+ if (priority == PRIORITY_SYSTEM) {
mActualDispatcher.registerSystemOnBackInvokedCallback(callback);
} else {
mActualDispatcher.registerOnBackInvokedCallback(priority, callback);
@@ -123,10 +125,19 @@
}
for (Pair<OnBackInvokedCallback, Integer> callbackPair : mCallbacks) {
int priority = callbackPair.second;
- if (priority >= PRIORITY_DEFAULT) {
- mActualDispatcher.registerOnBackInvokedCallback(priority, callbackPair.first);
+ if (predictiveBackPrioritySystemNavigationObserver()) {
+ if (priority >= PRIORITY_DEFAULT
+ || priority == PRIORITY_SYSTEM_NAVIGATION_OBSERVER) {
+ mActualDispatcher.registerOnBackInvokedCallback(priority, callbackPair.first);
+ } else {
+ mActualDispatcher.registerSystemOnBackInvokedCallback(callbackPair.first);
+ }
} else {
- mActualDispatcher.registerSystemOnBackInvokedCallback(callbackPair.first);
+ if (priority >= PRIORITY_DEFAULT) {
+ mActualDispatcher.registerOnBackInvokedCallback(priority, callbackPair.first);
+ } else {
+ mActualDispatcher.registerSystemOnBackInvokedCallback(callbackPair.first);
+ }
}
}
mCallbacks.clear();
diff --git a/core/java/android/window/TaskConstants.java b/core/java/android/window/TaskConstants.java
index 44bb33db..46bc30e 100644
--- a/core/java/android/window/TaskConstants.java
+++ b/core/java/android/window/TaskConstants.java
@@ -53,25 +53,30 @@
*/
public static final int TASK_CHILD_LAYER_COMPAT_UI = TASK_CHILD_LAYER_REGION_SIZE;
+ /**
+ * Settings dialogs belonging to the task (e.g. Open by default settings dialog)
+ * @hide
+ */
+ public static final int TASK_CHILD_LAYER_SETTINGS_DIALOG = 2 * TASK_CHILD_LAYER_REGION_SIZE;
/**
* Captions, window frames and resize handlers around task windows.
* @hide
*/
- public static final int TASK_CHILD_LAYER_WINDOW_DECORATIONS = 2 * TASK_CHILD_LAYER_REGION_SIZE;
+ public static final int TASK_CHILD_LAYER_WINDOW_DECORATIONS = 3 * TASK_CHILD_LAYER_REGION_SIZE;
/**
* Overlays the task when going into PIP w/ gesture navigation.
* @hide
*/
public static final int TASK_CHILD_LAYER_RECENTS_ANIMATION_PIP_OVERLAY =
- 3 * TASK_CHILD_LAYER_REGION_SIZE;
+ 4 * TASK_CHILD_LAYER_REGION_SIZE;
/**
* Allows other apps to add overlays on the task (i.e. game dashboard)
* @hide
*/
- public static final int TASK_CHILD_LAYER_TASK_OVERLAY = 4 * TASK_CHILD_LAYER_REGION_SIZE;
+ public static final int TASK_CHILD_LAYER_TASK_OVERLAY = 5 * TASK_CHILD_LAYER_REGION_SIZE;
/**
@@ -95,6 +100,7 @@
TASK_CHILD_LAYER_TASK_BACKGROUND,
TASK_CHILD_LAYER_LETTERBOX_BACKGROUND,
TASK_CHILD_LAYER_COMPAT_UI,
+ TASK_CHILD_LAYER_SETTINGS_DIALOG,
TASK_CHILD_LAYER_WINDOW_DECORATIONS,
TASK_CHILD_LAYER_RECENTS_ANIMATION_PIP_OVERLAY,
TASK_CHILD_LAYER_TASK_OVERLAY,
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index 314bf89..0dc9263 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -28,6 +28,7 @@
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.app.PendingIntent;
import android.app.WindowConfiguration;
@@ -44,6 +45,7 @@
import android.view.InsetsFrameProvider;
import android.view.InsetsSource;
import android.view.SurfaceControl;
+import android.view.WindowInsets;
import android.view.WindowInsets.Type.InsetsType;
import com.android.window.flags.Flags;
@@ -267,6 +269,23 @@
}
/**
+ * Sets whether the IME insets should be excluded by {@link com.android.server.wm.InsetsPolicy}.
+ * @hide
+ */
+ @SuppressLint("UnflaggedApi")
+ @NonNull
+ public WindowContainerTransaction setExcludeImeInsets(
+ @NonNull WindowContainerToken container, boolean exclude) {
+ final HierarchyOp hierarchyOp =
+ new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_SET_EXCLUDE_INSETS_TYPES)
+ .setContainer(container.asBinder())
+ .setExcludeInsetsTypes(exclude ? WindowInsets.Type.ime() : 0)
+ .build();
+ mHierarchyOps.add(hierarchyOp);
+ return this;
+ }
+
+ /**
* Sets whether a container or its children should be hidden. When {@code false}, the existing
* visibility of the container applies, but when {@code true} the container will be forced
* to be hidden.
@@ -1449,6 +1468,7 @@
public static final int HIERARCHY_OP_TYPE_MOVE_PIP_ACTIVITY_TO_PINNED_TASK = 18;
public static final int HIERARCHY_OP_TYPE_SET_IS_TRIMMABLE = 19;
public static final int HIERARCHY_OP_TYPE_RESTORE_BACK_NAVIGATION = 20;
+ public static final int HIERARCHY_OP_TYPE_SET_EXCLUDE_INSETS_TYPES = 21;
// The following key(s) are for use with mLaunchOptions:
// When launching a task (eg. from recents), this is the taskId to be launched.
@@ -1512,6 +1532,8 @@
private boolean mIsTrimmableFromRecents;
+ private @InsetsType int mExcludeInsetsTypes;
+
public static HierarchyOp createForReparent(
@NonNull IBinder container, @Nullable IBinder reparent, boolean toTop) {
return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_REPARENT)
@@ -1649,6 +1671,7 @@
mAlwaysOnTop = copy.mAlwaysOnTop;
mReparentLeafTaskIfRelaunch = copy.mReparentLeafTaskIfRelaunch;
mIsTrimmableFromRecents = copy.mIsTrimmableFromRecents;
+ mExcludeInsetsTypes = copy.mExcludeInsetsTypes;
}
protected HierarchyOp(Parcel in) {
@@ -1671,6 +1694,7 @@
mAlwaysOnTop = in.readBoolean();
mReparentLeafTaskIfRelaunch = in.readBoolean();
mIsTrimmableFromRecents = in.readBoolean();
+ mExcludeInsetsTypes = in.readInt();
}
public int getType() {
@@ -1772,6 +1796,10 @@
return mIsTrimmableFromRecents;
}
+ public @InsetsType int getExcludeInsetsTypes() {
+ return mExcludeInsetsTypes;
+ }
+
/** Gets a string representation of a hierarchy-op type. */
public static String hopToString(int type) {
switch (type) {
@@ -1795,6 +1823,7 @@
return "setReparentLeafTaskIfRelaunch";
case HIERARCHY_OP_TYPE_ADD_TASK_FRAGMENT_OPERATION:
return "addTaskFragmentOperation";
+ case HIERARCHY_OP_TYPE_SET_EXCLUDE_INSETS_TYPES: return "setExcludeInsetsTypes";
default: return "HOP(" + type + ")";
}
}
@@ -1868,6 +1897,11 @@
sb.append("fragmentToken= ").append(mContainer)
.append(" operation= ").append(mTaskFragmentOperation);
break;
+ case HIERARCHY_OP_TYPE_SET_EXCLUDE_INSETS_TYPES:
+ sb.append("container= ").append(mContainer)
+ .append(" mExcludeInsetsTypes= ")
+ .append(WindowInsets.Type.toString(mExcludeInsetsTypes));
+ break;
case HIERARCHY_OP_TYPE_SET_IS_TRIMMABLE:
sb.append("container= ").append(mContainer)
.append(" isTrimmable= ")
@@ -1903,6 +1937,7 @@
dest.writeBoolean(mAlwaysOnTop);
dest.writeBoolean(mReparentLeafTaskIfRelaunch);
dest.writeBoolean(mIsTrimmableFromRecents);
+ dest.writeInt(mExcludeInsetsTypes);
}
@Override
@@ -1974,6 +2009,8 @@
private boolean mIsTrimmableFromRecents;
+ private @InsetsType int mExcludeInsetsTypes;
+
Builder(int type) {
mType = type;
}
@@ -2069,6 +2106,11 @@
return this;
}
+ Builder setExcludeInsetsTypes(@InsetsType int excludeInsetsTypes) {
+ mExcludeInsetsTypes = excludeInsetsTypes;
+ return this;
+ }
+
HierarchyOp build() {
final HierarchyOp hierarchyOp = new HierarchyOp(mType);
hierarchyOp.mContainer = mContainer;
@@ -2093,6 +2135,7 @@
hierarchyOp.mIncludingParents = mIncludingParents;
hierarchyOp.mReparentLeafTaskIfRelaunch = mReparentLeafTaskIfRelaunch;
hierarchyOp.mIsTrimmableFromRecents = mIsTrimmableFromRecents;
+ hierarchyOp.mExcludeInsetsTypes = mExcludeInsetsTypes;
return hierarchyOp;
}
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index bb89a24..51bc7d5 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -16,6 +16,8 @@
package android.window;
+import static com.android.window.flags.Flags.predictiveBackPrioritySystemNavigationObserver;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Activity;
@@ -103,6 +105,9 @@
public final TreeMap<Integer, ArrayList<OnBackInvokedCallback>>
mOnBackInvokedCallbacks = new TreeMap<>();
+ @VisibleForTesting
+ public OnBackInvokedCallback mSystemNavigationObserverCallback = null;
+
private Checker mChecker;
private final Object mLock = new Object();
// The threshold for back swipe full progress.
@@ -170,6 +175,20 @@
}
}
+ private void registerSystemNavigationObserverCallback(@NonNull OnBackInvokedCallback callback) {
+ synchronized (mLock) {
+ // If callback has already been added as regular callback, remove it.
+ if (mAllCallbacks.containsKey(callback)) {
+ if (DEBUG) {
+ Log.i(TAG, "Callback already added. Removing and re-adding it as "
+ + "system-navigation-observer-callback.");
+ }
+ removeCallbackInternal(callback);
+ }
+ mSystemNavigationObserverCallback = callback;
+ }
+ }
+
/**
* Register a callback bypassing platform checks. This is used to register compatibility
* callbacks.
@@ -181,6 +200,12 @@
mImeDispatcher.registerOnBackInvokedCallback(priority, callback);
return;
}
+ if (predictiveBackPrioritySystemNavigationObserver()) {
+ if (priority == PRIORITY_SYSTEM_NAVIGATION_OBSERVER) {
+ registerSystemNavigationObserverCallback(callback);
+ return;
+ }
+ }
if (callback instanceof ImeOnBackInvokedDispatcher.ImeOnBackInvokedCallback) {
if (callback instanceof ImeOnBackInvokedDispatcher.DefaultImeOnBackAnimationCallback
&& mImeBackAnimationController != null) {
@@ -202,6 +227,13 @@
Integer prevPriority = mAllCallbacks.get(callback);
mOnBackInvokedCallbacks.get(prevPriority).remove(callback);
}
+ if (mSystemNavigationObserverCallback == callback) {
+ mSystemNavigationObserverCallback = null;
+ if (DEBUG) {
+ Log.i(TAG, "Callback already registered (as system-navigation-observer "
+ + "callback). Removing and re-adding it.");
+ }
+ }
OnBackInvokedCallback previousTopCallback = getTopCallback();
callbacks.add(callback);
@@ -221,6 +253,10 @@
mImeDispatcher.unregisterOnBackInvokedCallback(callback);
return;
}
+ if (mSystemNavigationObserverCallback == callback) {
+ mSystemNavigationObserverCallback = null;
+ return;
+ }
if (callback instanceof ImeOnBackInvokedDispatcher.DefaultImeOnBackAnimationCallback) {
callback = mImeBackAnimationController;
}
@@ -230,25 +266,29 @@
}
return;
}
- OnBackInvokedCallback previousTopCallback = getTopCallback();
- Integer priority = mAllCallbacks.get(callback);
- ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority);
- callbacks.remove(callback);
- if (callbacks.isEmpty()) {
- mOnBackInvokedCallbacks.remove(priority);
- }
- mAllCallbacks.remove(callback);
- // Re-populate the top callback to WM if the removed callback was previously the top
- // one.
- if (previousTopCallback == callback) {
- // We should call onBackCancelled() when an active callback is removed from
- // dispatcher.
- mProgressAnimator.removeOnBackCancelledFinishCallback();
- mProgressAnimator.removeOnBackInvokedFinishCallback();
- sendCancelledIfInProgress(callback);
- mHandler.post(mProgressAnimator::reset);
- setTopOnBackInvokedCallback(getTopCallback());
- }
+ removeCallbackInternal(callback);
+ }
+ }
+
+ private void removeCallbackInternal(@NonNull OnBackInvokedCallback callback) {
+ OnBackInvokedCallback previousTopCallback = getTopCallback();
+ Integer priority = mAllCallbacks.get(callback);
+ ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority);
+ callbacks.remove(callback);
+ if (callbacks.isEmpty()) {
+ mOnBackInvokedCallbacks.remove(priority);
+ }
+ mAllCallbacks.remove(callback);
+ // Re-populate the top callback to WM if the removed callback was previously the top
+ // one.
+ if (previousTopCallback == callback) {
+ // We should call onBackCancelled() when an active callback is removed from
+ // dispatcher.
+ mProgressAnimator.removeOnBackCancelledFinishCallback();
+ mProgressAnimator.removeOnBackInvokedFinishCallback();
+ sendCancelledIfInProgress(callback);
+ mHandler.post(mProgressAnimator::reset);
+ setTopOnBackInvokedCallback(getTopCallback());
}
}
@@ -304,6 +344,7 @@
mHandler.post(mProgressAnimator::reset);
mAllCallbacks.clear();
mOnBackInvokedCallbacks.clear();
+ mSystemNavigationObserverCallback = null;
}
}
@@ -315,6 +356,25 @@
}
}
+ /**
+ * Tries to call {@link OnBackInvokedCallback#onBackInvoked} on the system navigation observer
+ * callback (if one is set and if the top-most regular callback has
+ * {@link OnBackInvokedDispatcher#PRIORITY_SYSTEM})
+ */
+ public void tryInvokeSystemNavigationObserverCallback() {
+ OnBackInvokedCallback topCallback = getTopCallback();
+ Integer callbackPriority = mAllCallbacks.getOrDefault(topCallback, null);
+ if (callbackPriority != null && callbackPriority == PRIORITY_SYSTEM) {
+ invokeSystemNavigationObserverCallback();
+ }
+ }
+
+ private void invokeSystemNavigationObserverCallback() {
+ if (mSystemNavigationObserverCallback != null) {
+ mSystemNavigationObserverCallback.onBackInvoked();
+ }
+ }
+
private void setTopOnBackInvokedCallback(@Nullable OnBackInvokedCallback callback) {
if (mWindowSession == null || mWindow == null) {
return;
@@ -324,7 +384,9 @@
if (callback != null) {
int priority = mAllCallbacks.get(callback);
final IOnBackInvokedCallback iCallback = new OnBackInvokedCallbackWrapper(callback,
- mTouchTracker, mProgressAnimator, mHandler, this::callOnKeyPreIme);
+ mTouchTracker, mProgressAnimator, mHandler, this::callOnKeyPreIme,
+ this::invokeSystemNavigationObserverCallback,
+ /*isSystemCallback*/ priority == PRIORITY_SYSTEM);
callbackInfo = new OnBackInvokedCallbackInfo(
iCallback,
priority,
@@ -416,18 +478,26 @@
private final Handler mHandler;
@NonNull
private final BooleanSupplier mOnKeyPreIme;
+ @NonNull
+ private final Runnable mSystemNavigationObserverCallbackRunnable;
+ private final boolean mIsSystemCallback;
OnBackInvokedCallbackWrapper(
@NonNull OnBackInvokedCallback callback,
@NonNull BackTouchTracker touchTracker,
@NonNull BackProgressAnimator progressAnimator,
@NonNull Handler handler,
- @NonNull BooleanSupplier onKeyPreIme) {
+ @NonNull BooleanSupplier onKeyPreIme,
+ @NonNull Runnable systemNavigationObserverCallbackRunnable,
+ boolean isSystemCallback
+ ) {
mCallback = new WeakReference<>(callback);
mTouchTracker = touchTracker;
mProgressAnimator = progressAnimator;
mHandler = handler;
mOnKeyPreIme = onKeyPreIme;
+ mSystemNavigationObserverCallbackRunnable = systemNavigationObserverCallbackRunnable;
+ mIsSystemCallback = isSystemCallback;
}
@Override
@@ -494,9 +564,17 @@
OnBackAnimationCallback animationCallback = getBackAnimationCallback();
if (animationCallback != null
&& !(callback instanceof ImeBackAnimationController)) {
- mProgressAnimator.onBackInvoked(callback::onBackInvoked);
+ mProgressAnimator.onBackInvoked(() -> {
+ if (mIsSystemCallback) {
+ mSystemNavigationObserverCallbackRunnable.run();
+ }
+ callback.onBackInvoked();
+ });
} else {
mProgressAnimator.reset();
+ if (mIsSystemCallback) {
+ mSystemNavigationObserverCallbackRunnable.run();
+ }
callback.onBackInvoked();
}
});
@@ -597,9 +675,18 @@
+ " application manifest.");
return false;
}
- if (priority < 0) {
- throw new IllegalArgumentException("Application registered OnBackInvokedCallback "
- + "cannot have negative priority. Priority: " + priority);
+ if (predictiveBackPrioritySystemNavigationObserver()) {
+ if (priority < 0 && priority != PRIORITY_SYSTEM_NAVIGATION_OBSERVER) {
+ throw new IllegalArgumentException("Application registered "
+ + "OnBackInvokedCallback cannot have negative priority. Priority: "
+ + priority);
+ }
+ } else {
+ if (priority < 0) {
+ throw new IllegalArgumentException("Application registered "
+ + "OnBackInvokedCallback cannot have negative priority. Priority: "
+ + priority);
+ }
}
Objects.requireNonNull(callback);
return true;
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 086063f..c9b93c9 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -307,3 +307,11 @@
bug: "364930619"
is_fixed_read_only: true
}
+
+flag {
+ name: "predictive_back_priority_system_navigation_observer"
+ namespace: "systemui"
+ description: "PRIORITY_SYSTEM_NAVIGATION_OBSERVER predictive back API extension"
+ is_fixed_read_only: true
+ bug: "362938401"
+}
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 9797d96..4d2195d 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -73,6 +73,12 @@
srcs: [
"android_animation_PropertyValuesHolder.cpp",
+ "android_database_CursorWindow.cpp",
+ "android_database_SQLiteCommon.cpp",
+ "android_database_SQLiteConnection.cpp",
+ "android_database_SQLiteGlobal.cpp",
+ "android_database_SQLiteDebug.cpp",
+ "android_database_SQLiteRawStatement.cpp",
"android_os_SystemClock.cpp",
"android_os_SystemProperties.cpp",
"android_os_Trace.cpp",
@@ -157,12 +163,6 @@
"android_opengl_GLES31.cpp",
"android_opengl_GLES31Ext.cpp",
"android_opengl_GLES32.cpp",
- "android_database_CursorWindow.cpp",
- "android_database_SQLiteCommon.cpp",
- "android_database_SQLiteConnection.cpp",
- "android_database_SQLiteGlobal.cpp",
- "android_database_SQLiteDebug.cpp",
- "android_database_SQLiteRawStatement.cpp",
"android_graphics_GraphicBuffer.cpp",
"android_graphics_SurfaceTexture.cpp",
"android_view_CompositionSamplingListener.cpp",
@@ -427,6 +427,7 @@
"libnativehelper_jvm",
"libpiex",
"libpng",
+ "libsqlite",
"libtiff_directory",
"libui-types",
"libutils",
@@ -442,12 +443,6 @@
host_linux: {
srcs: [
"android_content_res_ApkAssets.cpp",
- "android_database_CursorWindow.cpp",
- "android_database_SQLiteCommon.cpp",
- "android_database_SQLiteConnection.cpp",
- "android_database_SQLiteGlobal.cpp",
- "android_database_SQLiteDebug.cpp",
- "android_database_SQLiteRawStatement.cpp",
"android_hardware_input_InputApplicationHandle.cpp",
"android_os_MessageQueue.cpp",
"android_os_Parcel.cpp",
@@ -463,7 +458,6 @@
],
static_libs: [
"libbinderthreadstateutils",
- "libsqlite",
"libgui_window_info_static",
],
shared_libs: [
diff --git a/core/jni/android_database_CursorWindow.cpp b/core/jni/android_database_CursorWindow.cpp
index c0e9215..18c3146 100644
--- a/core/jni/android_database_CursorWindow.cpp
+++ b/core/jni/android_database_CursorWindow.cpp
@@ -38,7 +38,9 @@
#define LOG_NDEBUG 1
#include <androidfw/CursorWindow.h>
+#ifdef __linux__
#include "android_os_Parcel.h"
+#endif
#include "android_util_Binder.h"
#include "android_database_SQLiteCommon.h"
@@ -111,6 +113,7 @@
return 0;
}
+#ifdef __linux__
static jlong nativeCreateFromParcel(JNIEnv* env, jclass clazz, jobject parcelObj) {
Parcel* parcel = parcelForJavaObject(env, parcelObj);
@@ -128,6 +131,7 @@
window->getNumRows(), window->getNumColumns(), window);
return reinterpret_cast<jlong>(window);
}
+#endif
static void nativeDispose(JNIEnv* env, jclass clazz, jlong windowPtr) {
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
@@ -142,6 +146,7 @@
return env->NewStringUTF(window->name().c_str());
}
+#ifdef __linux__
static void nativeWriteToParcel(JNIEnv * env, jclass clazz, jlong windowPtr,
jobject parcelObj) {
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
@@ -154,6 +159,7 @@
jniThrowRuntimeException(env, msg.c_str());
}
}
+#endif
static void nativeClear(JNIEnv * env, jclass clazz, jlong windowPtr) {
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
@@ -520,55 +526,35 @@
return true;
}
-static const JNINativeMethod sMethods[] =
-{
- /* name, signature, funcPtr */
- { "nativeCreate", "(Ljava/lang/String;I)J",
- (void*)nativeCreate },
- { "nativeCreateFromParcel", "(Landroid/os/Parcel;)J",
- (void*)nativeCreateFromParcel },
- { "nativeDispose", "(J)V",
- (void*)nativeDispose },
- { "nativeWriteToParcel", "(JLandroid/os/Parcel;)V",
- (void*)nativeWriteToParcel },
+static const JNINativeMethod sMethods[] = {
+ /* name, signature, funcPtr */
+ {"nativeCreate", "(Ljava/lang/String;I)J", (void*)nativeCreate},
+ {"nativeDispose", "(J)V", (void*)nativeDispose},
+#ifdef __linux__
+ {"nativeCreateFromParcel", "(Landroid/os/Parcel;)J", (void*)nativeCreateFromParcel},
+ {"nativeWriteToParcel", "(JLandroid/os/Parcel;)V", (void*)nativeWriteToParcel},
+#endif
+ {"nativeGetName", "(J)Ljava/lang/String;", (void*)nativeGetName},
+ {"nativeGetBlob", "(JII)[B", (void*)nativeGetBlob},
+ {"nativeGetString", "(JII)Ljava/lang/String;", (void*)nativeGetString},
+ {"nativeCopyStringToBuffer", "(JIILandroid/database/CharArrayBuffer;)V",
+ (void*)nativeCopyStringToBuffer},
+ {"nativePutBlob", "(J[BII)Z", (void*)nativePutBlob},
+ {"nativePutString", "(JLjava/lang/String;II)Z", (void*)nativePutString},
- { "nativeGetName", "(J)Ljava/lang/String;",
- (void*)nativeGetName },
- { "nativeGetBlob", "(JII)[B",
- (void*)nativeGetBlob },
- { "nativeGetString", "(JII)Ljava/lang/String;",
- (void*)nativeGetString },
- { "nativeCopyStringToBuffer", "(JIILandroid/database/CharArrayBuffer;)V",
- (void*)nativeCopyStringToBuffer },
- { "nativePutBlob", "(J[BII)Z",
- (void*)nativePutBlob },
- { "nativePutString", "(JLjava/lang/String;II)Z",
- (void*)nativePutString },
+ // ------- @FastNative below here ----------------------
+ {"nativeClear", "(J)V", (void*)nativeClear},
+ {"nativeGetNumRows", "(J)I", (void*)nativeGetNumRows},
+ {"nativeSetNumColumns", "(JI)Z", (void*)nativeSetNumColumns},
+ {"nativeAllocRow", "(J)Z", (void*)nativeAllocRow},
+ {"nativeFreeLastRow", "(J)V", (void*)nativeFreeLastRow},
+ {"nativeGetType", "(JII)I", (void*)nativeGetType},
+ {"nativeGetLong", "(JII)J", (void*)nativeGetLong},
+ {"nativeGetDouble", "(JII)D", (void*)nativeGetDouble},
- // ------- @FastNative below here ----------------------
- { "nativeClear", "(J)V",
- (void*)nativeClear },
- { "nativeGetNumRows", "(J)I",
- (void*)nativeGetNumRows },
- { "nativeSetNumColumns", "(JI)Z",
- (void*)nativeSetNumColumns },
- { "nativeAllocRow", "(J)Z",
- (void*)nativeAllocRow },
- { "nativeFreeLastRow", "(J)V",
- (void*)nativeFreeLastRow },
- { "nativeGetType", "(JII)I",
- (void*)nativeGetType },
- { "nativeGetLong", "(JII)J",
- (void*)nativeGetLong },
- { "nativeGetDouble", "(JII)D",
- (void*)nativeGetDouble },
-
- { "nativePutLong", "(JJII)Z",
- (void*)nativePutLong },
- { "nativePutDouble", "(JDII)Z",
- (void*)nativePutDouble },
- { "nativePutNull", "(JII)Z",
- (void*)nativePutNull },
+ {"nativePutLong", "(JJII)Z", (void*)nativePutLong},
+ {"nativePutDouble", "(JDII)Z", (void*)nativePutDouble},
+ {"nativePutNull", "(JII)Z", (void*)nativePutNull},
};
int register_android_database_CursorWindow(JNIEnv* env)
diff --git a/core/jni/android_hardware_OverlayProperties.cpp b/core/jni/android_hardware_OverlayProperties.cpp
index 96494b1..63de195 100644
--- a/core/jni/android_hardware_OverlayProperties.cpp
+++ b/core/jni/android_hardware_OverlayProperties.cpp
@@ -17,6 +17,7 @@
#define LOG_TAG "OverlayProperties"
// #define LOG_NDEBUG 0
+#include <android/gui/LutProperties.h>
#include <android/gui/OverlayProperties.h>
#include <binder/Parcel.h>
#include <gui/SurfaceComposerClient.h>
@@ -35,6 +36,12 @@
jclass clazz;
jmethodID ctor;
} gOverlayPropertiesClassInfo;
+
+static struct {
+ jclass clazz;
+ jmethodID ctor;
+} gLutPropertiesClassInfo;
+
// ----------------------------------------------------------------------------
// OverlayProperties lifecycle
// ----------------------------------------------------------------------------
@@ -95,6 +102,36 @@
return reinterpret_cast<jlong>(overlayProperties);
}
+static jobjectArray android_hardware_OverlayProperties_getLutProperties(JNIEnv* env, jobject thiz,
+ jlong nativeObject) {
+ gui::OverlayProperties* overlayProperties =
+ reinterpret_cast<gui::OverlayProperties*>(nativeObject);
+ if (overlayProperties->lutProperties.has_value()) {
+ return NULL;
+ }
+ auto& lutProperties = overlayProperties->lutProperties.value();
+ if (lutProperties.empty()) {
+ return NULL;
+ }
+ int32_t size = static_cast<int32_t>(lutProperties.size());
+ jobjectArray nativeLutProperties =
+ env->NewObjectArray(size, gLutPropertiesClassInfo.clazz, NULL);
+ if (nativeLutProperties == NULL) {
+ return NULL;
+ }
+ for (int32_t i = 0; i < size; i++) {
+ if (lutProperties[i].has_value()) {
+ auto& item = lutProperties[i].value();
+ jobject properties =
+ env->NewObject(gLutPropertiesClassInfo.clazz, gLutPropertiesClassInfo.ctor,
+ static_cast<int32_t>(item.dimension), item.size,
+ item.samplingKeys.data());
+ env->SetObjectArrayElement(nativeLutProperties, i, properties);
+ }
+ }
+ return nativeLutProperties;
+}
+
// ----------------------------------------------------------------------------
// Serialization
// ----------------------------------------------------------------------------
@@ -161,6 +198,8 @@
{ "nReadOverlayPropertiesFromParcel", "(Landroid/os/Parcel;)J",
(void*) android_hardware_OverlayProperties_read },
{"nCreateDefault", "()J", (void*) android_hardware_OverlayProperties_createDefault },
+ {"nGetLutProperties", "(J)[Landroid/hardware/LutProperties;",
+ (void*) android_hardware_OverlayProperties_getLutProperties },
};
// clang-format on
@@ -171,5 +210,9 @@
gOverlayPropertiesClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
gOverlayPropertiesClassInfo.ctor =
GetMethodIDOrDie(env, gOverlayPropertiesClassInfo.clazz, "<init>", "(J)V");
+ clazz = FindClassOrDie(env, "android/hardware/LutProperties");
+ gLutPropertiesClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
+ gLutPropertiesClassInfo.ctor =
+ GetMethodIDOrDie(env, gLutPropertiesClassInfo.clazz, "<init>", "(IJ[I)V");
return err;
}
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 17c89f8..a939d92 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -33,11 +33,13 @@
#include <android_runtime/android_view_Surface.h>
#include <android_runtime/android_view_SurfaceControl.h>
#include <android_runtime/android_view_SurfaceSession.h>
+#include <cutils/ashmem.h>
#include <gui/ISurfaceComposer.h>
#include <gui/Surface.h>
#include <gui/SurfaceComposerClient.h>
#include <jni.h>
#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
#include <nativehelper/ScopedUtfChars.h>
#include <private/gui/ComposerService.h>
#include <stdio.h>
@@ -48,6 +50,7 @@
#include <ui/DisplayMode.h>
#include <ui/DisplayedFrameStats.h>
#include <ui/DynamicDisplayInfo.h>
+#include <ui/FloatRect.h>
#include <ui/FrameStats.h>
#include <ui/GraphicTypes.h>
#include <ui/HdrCapabilities.h>
@@ -735,6 +738,65 @@
transaction->setDesiredHdrHeadroom(ctrl, desiredRatio);
}
+static void nativeSetLuts(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject,
+ jfloatArray jbufferArray, jintArray joffsetArray,
+ jintArray jdimensionArray, jintArray jsizeArray,
+ jintArray jsamplingKeyArray) {
+ auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+ SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject);
+
+ ScopedIntArrayRW joffsets(env, joffsetArray);
+ if (joffsets.get() == nullptr) {
+ jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRW from joffsetArray");
+ return;
+ }
+ ScopedIntArrayRW jdimensions(env, jdimensionArray);
+ if (jdimensions.get() == nullptr) {
+ jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRW from jdimensionArray");
+ return;
+ }
+ ScopedIntArrayRW jsizes(env, jsizeArray);
+ if (jsizes.get() == nullptr) {
+ jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRW from jsizeArray");
+ return;
+ }
+ ScopedIntArrayRW jsamplingKeys(env, jsamplingKeyArray);
+ if (jsamplingKeys.get() == nullptr) {
+ jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRW from jsamplingKeyArray");
+ return;
+ }
+
+ jsize numLuts = env->GetArrayLength(jdimensionArray);
+ std::vector<int32_t> offsets(joffsets.get(), joffsets.get() + numLuts);
+ std::vector<int32_t> dimensions(jdimensions.get(), jdimensions.get() + numLuts);
+ std::vector<int32_t> sizes(jsizes.get(), jsizes.get() + numLuts);
+ std::vector<int32_t> samplingKeys(jsamplingKeys.get(), jsamplingKeys.get() + numLuts);
+
+ ScopedFloatArrayRW jbuffers(env, jbufferArray);
+ if (jbuffers.get() == nullptr) {
+ jniThrowRuntimeException(env, "Failed to get ScopedFloatArrayRW from jbufferArray");
+ return;
+ }
+
+ // create the shared memory and copy jbuffers
+ size_t bufferSize = jbuffers.size() * sizeof(float);
+ int32_t fd = ashmem_create_region("lut_shread_mem", bufferSize);
+ if (fd < 0) {
+ jniThrowRuntimeException(env, "ashmem_create_region() failed");
+ return;
+ }
+ void* ptr = mmap(nullptr, bufferSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if (ptr == MAP_FAILED) {
+ jniThrowRuntimeException(env, "Failed to map the shared memory");
+ return;
+ }
+ memcpy(ptr, jbuffers.get(), bufferSize);
+ // unmap
+ munmap(ptr, bufferSize);
+
+ transaction->setLuts(ctrl, base::unique_fd(fd), offsets, dimensions, sizes, samplingKeys);
+}
+
static void nativeSetCachingHint(JNIEnv* env, jclass clazz, jlong transactionObj,
jlong nativeObject, jint cachingHint) {
auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
@@ -992,6 +1054,15 @@
transaction->setCrop(ctrl, crop);
}
+static void nativeSetCrop(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject,
+ jfloat l, jfloat t, jfloat r, jfloat b) {
+ auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+
+ SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject);
+ FloatRect crop(l, t, r, b);
+ transaction->setCrop(ctrl, crop);
+}
+
static void nativeSetCornerRadius(JNIEnv* env, jclass clazz, jlong transactionObj,
jlong nativeObject, jfloat cornerRadius) {
auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
@@ -2347,6 +2418,8 @@
(void*)nativeSetFrameRateSelectionPriority },
{"nativeSetWindowCrop", "(JJIIII)V",
(void*)nativeSetWindowCrop },
+ {"nativeSetCrop", "(JJFFFF)V",
+ (void*)nativeSetCrop },
{"nativeSetCornerRadius", "(JJF)V",
(void*)nativeSetCornerRadius },
{"nativeSetBackgroundBlurRadius", "(JJI)V",
@@ -2529,6 +2602,7 @@
(void*) nativeSetDesiredPresentTimeNanos },
{"nativeNotifyShutdown", "()V",
(void*)nativeNotifyShutdown },
+ {"nativeSetLuts", "(JJ[F[I[I[I[I)V", (void*)nativeSetLuts },
// clang-format on
};
diff --git a/core/jni/platform/host/HostRuntime.cpp b/core/jni/platform/host/HostRuntime.cpp
index 19f8299..88b3e1c 100644
--- a/core/jni/platform/host/HostRuntime.cpp
+++ b/core/jni/platform/host/HostRuntime.cpp
@@ -115,6 +115,9 @@
#ifdef __linux__
{"android.content.res.ApkAssets", REG_JNI(register_android_content_res_ApkAssets)},
{"android.content.res.AssetManager", REG_JNI(register_android_content_AssetManager)},
+ {"android.content.res.StringBlock", REG_JNI(register_android_content_StringBlock)},
+ {"android.content.res.XmlBlock", REG_JNI(register_android_content_XmlBlock)},
+#endif
{"android.database.CursorWindow", REG_JNI(register_android_database_CursorWindow)},
{"android.database.sqlite.SQLiteConnection",
REG_JNI(register_android_database_SQLiteConnection)},
@@ -122,9 +125,6 @@
{"android.database.sqlite.SQLiteDebug", REG_JNI(register_android_database_SQLiteDebug)},
{"android.database.sqlite.SQLiteRawStatement",
REG_JNI(register_android_database_SQLiteRawStatement)},
-#endif
- {"android.content.res.StringBlock", REG_JNI(register_android_content_StringBlock)},
- {"android.content.res.XmlBlock", REG_JNI(register_android_content_XmlBlock)},
#ifdef __linux__
{"android.os.Binder", REG_JNI(register_android_os_Binder)},
{"android.os.FileObserver", REG_JNI(register_android_os_FileObserver)},
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ed33ede..549f8df 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -792,6 +792,7 @@
<protected-broadcast android:name="com.android.cellbroadcastreceiver.GET_LATEST_CB_AREA_INFO" />
<protected-broadcast android:name="com.android.internal.telephony.ACTION_CARRIER_CERTIFICATE_DOWNLOAD" />
<protected-broadcast android:name="com.android.internal.telephony.action.COUNTRY_OVERRIDE" />
+ <protected-broadcast android:name="com.android.internal.telephony.action.SILENCE_WIFI_CALLING_NOTIFICATION"/>
<protected-broadcast android:name="com.android.internal.telephony.OPEN_DEFAULT_SMS_APP" />
<protected-broadcast android:name="com.android.internal.telephony.ACTION_TEST_OVERRIDE_CARRIER_ID" />
<protected-broadcast android:name="android.telephony.action.SIM_CARD_STATE_CHANGED" />
@@ -844,6 +845,8 @@
<protected-broadcast android:name="android.intent.action.MAIN_USER_LOCKSCREEN_KNOWLEDGE_FACTOR_CHANGED" />
<protected-broadcast android:name="com.android.uwb.uwbcountrycode.GEOCODE_RETRY" />
<protected-broadcast android:name="android.telephony.action.ACTION_SATELLITE_SUBSCRIBER_ID_LIST_CHANGED" />
+ <protected-broadcast android:name="android.service.ondeviceintelligence.MODEL_LOADED" />
+ <protected-broadcast android:name="android.service.ondeviceintelligence.MODEL_UNLOADED" />
<!-- ====================================================================== -->
<!-- RUNTIME PERMISSIONS -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 1a3a30d..b90ee2b 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1529,6 +1529,11 @@
factory reset. -->
<bool name="config_enableCredentialFactoryResetProtection">true</bool>
+ <!-- If true, then work around broken Weaver HALs that don't work reliably before the device has
+ fully booted. Setting this to true weakens a security feature; it should be done only when
+ necessary, though it is still better than not using Weaver at all. -->
+ <bool name="config_disableWeaverOnUnsecuredUsers">false</bool>
+
<!-- Control the behavior when the user long presses the home button.
0 - Nothing
1 - Launch all apps intent
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index d5298ac..c50c336 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3988,6 +3988,7 @@
<java-symbol type="string" name="foreground_service_multiple_separator" />
<java-symbol type="bool" name="config_enableCredentialFactoryResetProtection" />
+ <java-symbol type="bool" name="config_disableWeaverOnUnsecuredUsers" />
<!-- ETWS primary messages -->
<java-symbol type="string" name="etws_primary_default_message_earthquake" />
diff --git a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
index 3eefe04..b16c237 100644
--- a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
+++ b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
@@ -119,7 +119,7 @@
}
@Override
- protected DisplayMetrics getDisplayMetrics(int displayId, DisplayAdjustments daj) {
+ public DisplayMetrics getDisplayMetrics(int displayId, DisplayAdjustments daj) {
return mDisplayMetricsMap.get(displayId);
}
};
@@ -470,6 +470,48 @@
@Test
@SmallTest
+ public void testResourceConfigurationAppliedWhenOverrideDoesNotExist() {
+ final int width = 240;
+ final int height = 360;
+ final float densityDpi = mDisplayMetricsMap.get(Display.DEFAULT_DISPLAY).densityDpi;
+ final int widthDp = (int) (width / densityDpi + 0.5f);
+ final int heightDp = (int) (height / densityDpi + 0.5f);
+
+ final int overrideWidth = 480;
+ final int overrideHeight = 720;
+ final int overrideWidthDp = (int) (overrideWidth / densityDpi + 0.5f);
+ final int overrideHeightDp = (int) (height / densityDpi + 0.5f);
+
+ // The method to be tested is overridden for other tests to provide a setup environment.
+ // Create a new one for this test only.
+ final ResourcesManager resourcesManager = new ResourcesManager();
+
+ Configuration newConfig = new Configuration();
+ newConfig.windowConfiguration.setAppBounds(0, 0, width, height);
+ newConfig.screenWidthDp = widthDp;
+ newConfig.screenHeightDp = heightDp;
+ resourcesManager.applyConfigurationToResources(newConfig, null);
+
+ assertEquals(width, resourcesManager.getDisplayMetrics(Display.DEFAULT_DISPLAY,
+ DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS).widthPixels);
+ assertEquals(height, resourcesManager.getDisplayMetrics(Display.DEFAULT_DISPLAY,
+ DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS).heightPixels);
+
+ Configuration overrideConfig = new Configuration();
+ overrideConfig.windowConfiguration.setAppBounds(0, 0, overrideWidth, overrideHeight);
+ overrideConfig.screenWidthDp = overrideWidthDp;
+ overrideConfig.screenHeightDp = overrideHeightDp;
+
+ final DisplayAdjustments daj = new DisplayAdjustments(overrideConfig);
+
+ assertEquals(overrideWidth, resourcesManager.getDisplayMetrics(
+ Display.DEFAULT_DISPLAY, daj).widthPixels);
+ assertEquals(overrideHeight, resourcesManager.getDisplayMetrics(
+ Display.DEFAULT_DISPLAY, daj).heightPixels);
+ }
+
+ @Test
+ @SmallTest
@RequiresFlagsEnabled(Flags.FLAG_REGISTER_RESOURCE_PATHS)
@DisabledOnRavenwood(blockedBy = PackageManager.class)
public void testNewResourcesWithOutdatedImplAfterResourcePathsRegistration()
diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
index 0bda0ff..0a4c5e6 100644
--- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
+++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
@@ -18,6 +18,9 @@
import static android.window.OnBackInvokedDispatcher.PRIORITY_DEFAULT;
import static android.window.OnBackInvokedDispatcher.PRIORITY_OVERLAY;
+import static android.window.OnBackInvokedDispatcher.PRIORITY_SYSTEM_NAVIGATION_OBSERVER;
+
+import static com.android.window.flags.Flags.FLAG_PREDICTIVE_BACK_PRIORITY_SYSTEM_NAVIGATION_OBSERVER;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -39,6 +42,10 @@
import android.os.Looper;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.view.IWindow;
import android.view.IWindowSession;
import android.view.ImeBackAnimationController;
@@ -80,6 +87,8 @@
@Rule
public final MockitoRule mockito = MockitoJUnit.rule();
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
@Mock
private IWindowSession mWindowSession;
@@ -145,7 +154,8 @@
assertEquals("No setOnBackInvokedCallbackInfo", mCallbackInfoCalls, actual);
}
- private void assertCallbacksSize(int expectedDefault, int expectedOverlay) {
+ private void assertCallbacksSize(int expectedDefault, int expectedOverlay,
+ int expectedObserver) {
ArrayList<OnBackInvokedCallback> callbacksDefault = mDispatcher
.mOnBackInvokedCallbacks.get(PRIORITY_DEFAULT);
int actualSizeDefault = callbacksDefault != null ? callbacksDefault.size() : 0;
@@ -155,6 +165,10 @@
.mOnBackInvokedCallbacks.get(PRIORITY_OVERLAY);
int actualSizeOverlay = callbacksOverlay != null ? callbacksOverlay.size() : 0;
assertEquals("mOnBackInvokedCallbacks OVERLAY size", expectedOverlay, actualSizeOverlay);
+
+ int actualSizeObserver = mDispatcher.mSystemNavigationObserverCallback == null ? 0 : 1;
+ assertEquals("mOnBackInvokedCallbacks SYSTEM_NAVIGATION_OBSERVER size", expectedObserver,
+ actualSizeObserver);
}
private void assertTopCallback(OnBackInvokedCallback expectedCallback) {
@@ -164,13 +178,13 @@
@Test
public void registerCallback_samePriority_sameCallback() throws RemoteException {
mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1);
- assertCallbacksSize(/* default */ 1, /* overlay */ 0);
+ assertCallbacksSize(/* default */ 1, /* overlay */ 0, /* observer */ 0);
assertSetCallbackInfo();
assertTopCallback(mCallback1);
// The callback is removed and added again
mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1);
- assertCallbacksSize(/* default */ 1, /* overlay */ 0);
+ assertCallbacksSize(/* default */ 1, /* overlay */ 0, /* observer */ 0);
assertSetCallbackInfo();
assertTopCallback(mCallback1);
@@ -182,13 +196,13 @@
@Test
public void registerCallback_samePriority_differentCallback() throws RemoteException {
mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1);
- assertCallbacksSize(/* default */ 1, /* overlay */ 0);
+ assertCallbacksSize(/* default */ 1, /* overlay */ 0, /* observer */ 0);
assertSetCallbackInfo();
assertTopCallback(mCallback1);
// The new callback becomes the TopCallback
mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback2);
- assertCallbacksSize(/* default */ 2, /* overlay */ 0);
+ assertCallbacksSize(/* default */ 2, /* overlay */ 0, /* observer */ 0);
assertSetCallbackInfo();
assertTopCallback(mCallback2);
@@ -201,13 +215,13 @@
@Test
public void registerCallback_differentPriority_sameCallback() throws RemoteException {
mDispatcher.registerOnBackInvokedCallback(PRIORITY_OVERLAY, mCallback1);
- assertCallbacksSize(/* default */ 0, /* overlay */ 1);
+ assertCallbacksSize(/* default */ 0, /* overlay */ 1, /* observer */ 0);
assertSetCallbackInfo();
assertTopCallback(mCallback1);
// The callback is moved to the new priority list
mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1);
- assertCallbacksSize(/* default */ 1, /* overlay */ 0);
+ assertCallbacksSize(/* default */ 1, /* overlay */ 0, /* observer */ 0);
assertSetCallbackInfo();
assertTopCallback(mCallback1);
@@ -220,13 +234,13 @@
public void registerCallback_differentPriority_differentCallback() throws RemoteException {
mDispatcher.registerOnBackInvokedCallback(PRIORITY_OVERLAY, mCallback1);
assertSetCallbackInfo();
- assertCallbacksSize(/* default */ 0, /* overlay */ 1);
+ assertCallbacksSize(/* default */ 0, /* overlay */ 1, /* observer */ 0);
assertTopCallback(mCallback1);
// The callback with higher priority is still the TopCallback
mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback2);
assertNoSetCallbackInfo();
- assertCallbacksSize(/* default */ 1, /* overlay */ 1);
+ assertCallbacksSize(/* default */ 1, /* overlay */ 1, /* observer */ 0);
assertTopCallback(mCallback1);
waitForIdle();
@@ -238,22 +252,22 @@
@Test
public void registerCallback_sameInstanceAddedTwice() throws RemoteException {
mDispatcher.registerOnBackInvokedCallback(PRIORITY_OVERLAY, mCallback1);
- assertCallbacksSize(/* default */ 0, /* overlay */ 1);
+ assertCallbacksSize(/* default */ 0, /* overlay */ 1, /* observer */ 0);
assertSetCallbackInfo();
assertTopCallback(mCallback1);
mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback2);
- assertCallbacksSize(/* default */ 1, /* overlay */ 1);
+ assertCallbacksSize(/* default */ 1, /* overlay */ 1, /* observer */ 0);
assertNoSetCallbackInfo();
assertTopCallback(mCallback1);
mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1);
- assertCallbacksSize(/* default */ 2, /* overlay */ 0);
+ assertCallbacksSize(/* default */ 2, /* overlay */ 0, /* observer */ 0);
assertSetCallbackInfo();
assertTopCallback(mCallback1);
mDispatcher.registerOnBackInvokedCallback(PRIORITY_OVERLAY, mCallback2);
- assertCallbacksSize(/* default */ 1, /* overlay */ 1);
+ assertCallbacksSize(/* default */ 1, /* overlay */ 1, /* observer */ 0);
assertSetCallbackInfo();
assertTopCallback(mCallback2);
@@ -570,6 +584,102 @@
assertFalse(mDispatcher.mProgressAnimator.isBackAnimationInProgress());
}
+ @Test(expected = IllegalArgumentException.class)
+ @RequiresFlagsDisabled(FLAG_PREDICTIVE_BACK_PRIORITY_SYSTEM_NAVIGATION_OBSERVER)
+ public void testNoUiCallback_registrationFailsWithoutFlaggedApiEnabled() {
+ mDispatcher.registerOnBackInvokedCallback(PRIORITY_SYSTEM_NAVIGATION_OBSERVER, mCallback2);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_PREDICTIVE_BACK_PRIORITY_SYSTEM_NAVIGATION_OBSERVER)
+ public void testNoUiCallback_invokedWithSystemCallback() throws RemoteException {
+ mDispatcher.registerSystemOnBackInvokedCallback(mCallback1);
+ mDispatcher.registerOnBackInvokedCallback(PRIORITY_SYSTEM_NAVIGATION_OBSERVER, mCallback2);
+
+ assertCallbacksSize(/* default */ 0, /* overlay */ 0, /* observer */ 1);
+ OnBackInvokedCallbackInfo callbackInfo = assertSetCallbackInfo();
+ assertTopCallback(mCallback1);
+
+ callbackInfo.getCallback().onBackStarted(mBackEvent);
+ waitForIdle();
+ verify(mCallback1).onBackStarted(any());
+ verify(mCallback2, never()).onBackStarted(any());
+
+ callbackInfo.getCallback().onBackProgressed(mBackEvent);
+ waitForIdle();
+ verify(mCallback1).onBackProgressed(any());
+ verify(mCallback2, never()).onBackProgressed(any());
+
+ callbackInfo.getCallback().onBackCancelled();
+ waitForIdle();
+ verify(mCallback1, timeout(1000)).onBackCancelled();
+ verify(mCallback2, never()).onBackCancelled();
+
+ // start new gesture to test onBackInvoked case
+ callbackInfo.getCallback().onBackStarted(mBackEvent);
+ callbackInfo.getCallback().onBackInvoked();
+ waitForIdle();
+ verify(mCallback1, timeout(1000)).onBackInvoked();
+ verify(mCallback2).onBackInvoked();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_PREDICTIVE_BACK_PRIORITY_SYSTEM_NAVIGATION_OBSERVER)
+ public void testNoUiCallback_notInvokedWithNonSystemCallback() throws RemoteException {
+ mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1);
+ mDispatcher.registerOnBackInvokedCallback(PRIORITY_SYSTEM_NAVIGATION_OBSERVER, mCallback2);
+
+ assertCallbacksSize(/* default */ 1, /* overlay */ 0, /* observer */ 1);
+ OnBackInvokedCallbackInfo callbackInfo = assertSetCallbackInfo();
+ assertTopCallback(mCallback1);
+
+ callbackInfo.getCallback().onBackStarted(mBackEvent);
+ waitForIdle();
+ verify(mCallback1).onBackStarted(any());
+ verify(mCallback2, never()).onBackStarted(any());
+
+ callbackInfo.getCallback().onBackProgressed(mBackEvent);
+ waitForIdle();
+ verify(mCallback1).onBackProgressed(any());
+ verify(mCallback2, never()).onBackProgressed(any());
+
+ callbackInfo.getCallback().onBackCancelled();
+ waitForIdle();
+ verify(mCallback1, timeout(1000)).onBackCancelled();
+ verify(mCallback2, never()).onBackCancelled();
+
+ // start new gesture to test onBackInvoked case
+ callbackInfo.getCallback().onBackStarted(mBackEvent);
+ callbackInfo.getCallback().onBackInvoked();
+ waitForIdle();
+ verify(mCallback1, timeout(1000)).onBackInvoked();
+ verify(mCallback2, never()).onBackInvoked();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_PREDICTIVE_BACK_PRIORITY_SYSTEM_NAVIGATION_OBSERVER)
+ public void testNoUiCallback_reregistrations() {
+ mDispatcher.registerOnBackInvokedCallback(PRIORITY_SYSTEM_NAVIGATION_OBSERVER, mCallback1);
+ assertCallbacksSize(/* default */ 0, /* overlay */ 0, /* observer */ 1);
+ assertEquals(mCallback1, mDispatcher.mSystemNavigationObserverCallback);
+
+ // test reregistration of observer-callback as observer-callback
+ mDispatcher.registerOnBackInvokedCallback(PRIORITY_SYSTEM_NAVIGATION_OBSERVER, mCallback2);
+ assertCallbacksSize(/* default */ 0, /* overlay */ 0, /* observer */ 1);
+ assertEquals(mCallback2, mDispatcher.mSystemNavigationObserverCallback);
+
+ // test reregistration of observer-callback as regular callback
+ mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback2);
+ assertCallbacksSize(/* default */ 1, /* overlay */ 0, /* observer */ 0);
+
+ // test reregistration of regular callback as observer-callback
+ mDispatcher.registerOnBackInvokedCallback(PRIORITY_SYSTEM_NAVIGATION_OBSERVER, mCallback2);
+ assertCallbacksSize(/* default */ 0, /* overlay */ 0, /* observer */ 1);
+
+ mDispatcher.unregisterOnBackInvokedCallback(mCallback2);
+ assertCallbacksSize(/* default */ 0, /* overlay */ 0, /* observer */ 0);
+ }
+
private BackMotionEvent backMotionEventFrom(float progress) {
return new BackMotionEvent(
/* touchX = */ 0,
@@ -585,13 +695,13 @@
private void verifyImeCallackRegistrations() throws RemoteException {
// verify default callback is replaced with ImeBackAnimationController
mDispatcher.registerOnBackInvokedCallbackUnchecked(mDefaultImeCallback, PRIORITY_DEFAULT);
- assertCallbacksSize(/* default */ 1, /* overlay */ 0);
+ assertCallbacksSize(/* default */ 1, /* overlay */ 0, /* observer */ 0);
assertSetCallbackInfo();
assertTopCallback(mImeBackAnimationController);
// verify regular ime callback is successfully registered
mDispatcher.registerOnBackInvokedCallbackUnchecked(mImeCallback, PRIORITY_DEFAULT);
- assertCallbacksSize(/* default */ 2, /* overlay */ 0);
+ assertCallbacksSize(/* default */ 2, /* overlay */ 0, /* observer */ 0);
assertSetCallbackInfo();
assertTopCallback(mImeCallback);
}
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 880f30c..2e72f0e 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -585,6 +585,8 @@
<permission name="android.permission.EXECUTE_APP_FUNCTIONS" />
<!-- Permission required for CTS test - CtsNfcTestCases -->
<permission name="android.permission.NFC_SET_CONTROLLER_ALWAYS_ON" />
+ <!-- Permission required for CTS test - CtsAppTestCases -->
+ <permission name="android.permission.KILL_UID" />
</privapp-permissions>
<privapp-permissions package="com.android.statementservice">
diff --git a/errorprone/java/com/google/errorprone/bugpatterns/android/RequiresPermissionChecker.java b/errorprone/java/com/google/errorprone/bugpatterns/android/RequiresPermissionChecker.java
index 9887c27..af26bd0 100644
--- a/errorprone/java/com/google/errorprone/bugpatterns/android/RequiresPermissionChecker.java
+++ b/errorprone/java/com/google/errorprone/bugpatterns/android/RequiresPermissionChecker.java
@@ -130,10 +130,6 @@
.onDescendantOf("android.content.Context")
.withNameMatching(
Pattern.compile("^send(Ordered|Sticky)?Broadcast.*AsUser.*$")));
- private static final Matcher<ExpressionTree> SEND_PENDING_INTENT = methodInvocation(
- instanceMethod()
- .onDescendantOf("android.app.PendingIntent")
- .named("send"));
private static final Matcher<ExpressionTree> INTENT_SET_ACTION = methodInvocation(
instanceMethod().onDescendantOf("android.content.Intent").named("setAction"));
diff --git a/libs/WindowManager/Shell/res/color/open_by_default_settings_dialog_radio_button_color.xml b/libs/WindowManager/Shell/res/color/open_by_default_settings_dialog_radio_button_color.xml
new file mode 100644
index 0000000..0f9b28a
--- /dev/null
+++ b/libs/WindowManager/Shell/res/color/open_by_default_settings_dialog_radio_button_color.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <item android:state_checked="true"
+ android:color="?androidprv:attr/materialColorPrimaryContainer"/>
+ <item android:color="?androidprv:attr/materialColorSurfaceContainer"/>
+</selector>
diff --git a/libs/WindowManager/Shell/res/drawable/app_handle_education_tooltip_icon.xml b/libs/WindowManager/Shell/res/drawable/app_handle_education_tooltip_icon.xml
index 07e5ac1..b74d922 100644
--- a/libs/WindowManager/Shell/res/drawable/app_handle_education_tooltip_icon.xml
+++ b/libs/WindowManager/Shell/res/drawable/app_handle_education_tooltip_icon.xml
@@ -22,6 +22,6 @@
android:viewportHeight="960"
android:viewportWidth="960">
<path
- android:fillColor="@android:color/system_on_tertiary_fixed"
+ android:fillColor="@android:color/system_on_tertiary_container_light"
android:pathData="M419,880Q391,880 366.5,868Q342,856 325,834L107,557L126,537Q146,516 174,512Q202,508 226,523L300,568L300,240Q300,223 311.5,211.5Q323,200 340,200Q357,200 369,211.5Q381,223 381,240L381,712L284,652L388,785Q394,792 402,796Q410,800 419,800L640,800Q673,800 696.5,776.5Q720,753 720,720L720,560Q720,543 708.5,531.5Q697,520 680,520L461,520L461,440L680,440Q730,440 765,475Q800,510 800,560L800,720Q800,786 753,833Q706,880 640,880L419,880ZM167,340Q154,318 147,292.5Q140,267 140,240Q140,157 198.5,98.5Q257,40 340,40Q423,40 481.5,98.5Q540,157 540,240Q540,267 533,292.5Q526,318 513,340L444,300Q452,286 456,271.5Q460,257 460,240Q460,190 425,155Q390,120 340,120Q290,120 255,155Q220,190 220,240Q220,257 224,271.5Q228,286 236,300L167,340ZM502,620L502,620L502,620L502,620Q502,620 502,620Q502,620 502,620L502,620Q502,620 502,620Q502,620 502,620L502,620Q502,620 502,620Q502,620 502,620L502,620L502,620Z" />
</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_open_by_default_settings.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_open_by_default_settings.xml
new file mode 100644
index 0000000..4070c3d
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_open_by_default_settings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="?attr/colorControlNormal">
+ <path android:fillColor="@android:color/black"
+ android:pathData="M370,880L354,752Q341,747 329.5,740Q318,733 307,725L188,775L78,585L181,507Q180,500 180,493.5Q180,487 180,480Q180,473 180,466.5Q180,460 181,453L78,375L188,185L307,235Q318,227 330,220Q342,213 354,208L370,80L590,80L606,208Q619,213 630.5,220Q642,227 653,235L772,185L882,375L779,453Q780,460 780,466.5Q780,473 780,480Q780,487 780,493.5Q780,500 778,507L881,585L771,775L653,725Q642,733 630,740Q618,747 606,752L590,880L370,880ZM440,800L519,800L533,694Q564,686 590.5,670.5Q617,655 639,633L738,674L777,606L691,541Q696,527 698,511.5Q700,496 700,480Q700,464 698,448.5Q696,433 691,419L777,354L738,286L639,328Q617,305 590.5,289.5Q564,274 533,266L520,160L441,160L427,266Q396,274 369.5,289.5Q343,305 321,327L222,286L183,354L269,418Q264,433 262,448Q260,463 260,480Q260,496 262,511Q264,526 269,541L183,606L222,674L321,632Q343,655 369.5,670.5Q396,686 427,694L440,800ZM482,620Q540,620 581,579Q622,538 622,480Q622,422 581,381Q540,340 482,340Q423,340 382.5,381Q342,422 342,480Q342,538 382.5,579Q423,620 482,620ZM480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480Z"/>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/open_by_default_settings_dialog_background.xml b/libs/WindowManager/Shell/res/drawable/open_by_default_settings_dialog_background.xml
new file mode 100644
index 0000000..4eb2271
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/open_by_default_settings_dialog_background.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<shape android:shape="rectangle"
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <solid android:color="?androidprv:attr/materialColorSurfaceContainer"/>
+ <corners android:radius="28dp"/>
+</shape>
diff --git a/libs/WindowManager/Shell/res/drawable/open_by_default_settings_dialog_dismiss_button_background.xml b/libs/WindowManager/Shell/res/drawable/open_by_default_settings_dialog_dismiss_button_background.xml
new file mode 100644
index 0000000..2b2e9df
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/open_by_default_settings_dialog_dismiss_button_background.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:shape="rectangle">
+ <solid android:color="?androidprv:attr/materialColorPrimary"/>
+ <corners android:radius="50dp"/>
+</shape>
diff --git a/libs/WindowManager/Shell/res/drawable/open_by_default_settings_dialog_radio_buttons_background.xml b/libs/WindowManager/Shell/res/drawable/open_by_default_settings_dialog_radio_buttons_background.xml
new file mode 100644
index 0000000..1ac952b
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/open_by_default_settings_dialog_radio_buttons_background.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<shape android:shape="rectangle"
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="@color/open_by_default_settings_dialog_radio_button_color"/>
+ <corners android:radius="16dp"/>
+</shape>
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
index 6913e54..aeb734e 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
@@ -165,17 +165,28 @@
android:layout_height="@dimen/desktop_mode_handle_menu_open_in_browser_pill_height"
android:layout_marginTop="@dimen/desktop_mode_handle_menu_pill_spacing_margin"
android:layout_marginStart="1dp"
- android:orientation="vertical"
+ android:orientation="horizontal"
android:elevation="@dimen/desktop_mode_handle_menu_pill_elevation"
android:background="@drawable/desktop_mode_decor_handle_menu_background">
<Button
android:id="@+id/open_in_browser_button"
+ android:layout_weight="1"
android:contentDescription="@string/open_in_browser_text"
android:text="@string/open_in_browser_text"
android:drawableStart="@drawable/desktop_mode_ic_handle_menu_open_in_browser"
android:drawableTint="?androidprv:attr/materialColorOnSurface"
style="@style/DesktopModeHandleMenuActionButton"/>
+
+ <ImageButton
+ android:id="@+id/open_by_default_button"
+ android:layout_width="20dp"
+ android:layout_height="20dp"
+ android:layout_gravity="end|center_vertical"
+ android:layout_marginEnd="16dp"
+ android:contentDescription="@string/open_by_default_settings_text"
+ android:src="@drawable/desktop_mode_ic_handle_menu_open_by_default_settings"
+ android:tint="?androidprv:attr/materialColorOnSurface"/>
</LinearLayout>
</LinearLayout>
diff --git a/libs/WindowManager/Shell/res/layout/desktop_windowing_education_tooltip_container.xml b/libs/WindowManager/Shell/res/layout/desktop_windowing_education_tooltip_container.xml
index bdee883..09a049c 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_windowing_education_tooltip_container.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_windowing_education_tooltip_container.xml
@@ -37,7 +37,7 @@
android:layout_marginStart="2dp"
android:lineHeight="20dp"
android:maxWidth="150dp"
- android:textColor="@android:color/system_on_tertiary_fixed"
+ android:textColor="@android:color/system_on_tertiary_container_light"
android:textFontWeight="500"
android:textSize="14sp" />
</LinearLayout>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/open_by_default_settings_dialog.xml b/libs/WindowManager/Shell/res/layout/open_by_default_settings_dialog.xml
new file mode 100644
index 0000000..8ff382b
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/open_by_default_settings_dialog.xml
@@ -0,0 +1,130 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<com.android.wm.shell.apptoweb.OpenByDefaultDialogView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ style="@style/LetterboxDialog">
+
+ <!-- The background of the top-level layout acts as the background dim. -->
+ <FrameLayout
+ android:id="@+id/open_by_default_dialog_container"
+ android:layout_width="@dimen/open_by_default_settings_dialog_width"
+ android:layout_height="wrap_content"
+ android:background="@drawable/open_by_default_settings_dialog_background"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent">
+
+ <!-- The ScrollView should only wrap the content of the dialog, otherwise the background
+ corner radius will be cut off when scrolling to the top/bottom. -->
+ <ScrollView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <LinearLayout
+ android:padding="24dp"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:orientation="vertical">
+
+ <ImageView
+ android:id="@+id/application_icon"
+ android:layout_width="32dp"
+ android:layout_height="32dp"
+ android:layout_gravity="center_horizontal"
+ android:importantForAccessibility="no"
+ android:layout_marginTop="24dp"
+ android:layout_marginBottom="16dp"
+ android:scaleType="centerCrop"/>
+
+ <TextView
+ android:id="@+id/application_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:layout_marginBottom="8dp"
+ android:lineHeight="32dp"
+ android:textFontWeight="400"
+ android:textSize="24sp"
+ android:textColor="?androidprv:attr/materialColorOnSurfaceVariant"
+ tools:text="Gmail" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="12sp"
+ android:textFontWeight="400"
+ android:lineHeight="16dp"
+ android:layout_gravity="center_horizontal"
+ android:layout_marginBottom="16dp"
+ android:textColor="?androidprv:attr/materialColorOnSurfaceVariant"
+ android:text="@string/open_by_default_dialog_subheader_text"/>
+
+ <RadioGroup
+ android:id="@+id/open_by_default_radio_group"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal">
+ <RadioButton
+ android:id="@+id/open_in_app_button"
+ android:layout_width="@dimen/open_by_default_settings_dialog_radio_button_width"
+ android:layout_height="@dimen/open_by_default_settings_dialog_radio_button_height"
+ android:paddingStart="20dp"
+ android:paddingEnd="0dp"
+ android:layout_marginHorizontal="16dp"
+ android:layout_marginBottom="4dp"
+ android:text="@string/open_by_default_dialog_in_app_text"
+ android:textFontWeight="500"
+ android:textSize="16sp"
+ android:lineHeight="24dp"
+ android:background="@drawable/open_by_default_settings_dialog_radio_buttons_background"/>
+ <RadioButton
+ android:id="@+id/open_in_browser_button"
+ android:layout_width="@dimen/open_by_default_settings_dialog_radio_button_width"
+ android:layout_height="@dimen/open_by_default_settings_dialog_radio_button_height"
+ android:paddingStart="20dp"
+ android:paddingEnd="0dp"
+ android:layout_marginStart="16dp"
+ android:text="@string/open_by_default_dialog_in_browser_text"
+ android:textFontWeight="500"
+ android:textSize="16sp"
+ android:lineHeight="24dp"
+ android:background="@drawable/open_by_default_settings_dialog_radio_buttons_background"/>
+ </RadioGroup>
+
+ <Button
+ android:id="@+id/open_by_default_settings_dialog_dismiss_button"
+ android:layout_width="wrap_content"
+ android:layout_height="36dp"
+ android:text="@string/open_by_default_dialog_dismiss_button_text"
+ android:layout_gravity="end"
+ android:layout_marginHorizontal="24dp"
+ android:layout_marginTop="32dp"
+ android:layout_marginBottom="24dp"
+ android:textSize="14sp"
+ android:textFontWeight="500"
+ android:textColor="?androidprv:attr/materialColorOnPrimary"
+ android:background="@drawable/open_by_default_settings_dialog_dismiss_button_background"/>
+ </LinearLayout>
+ </ScrollView>
+ </FrameLayout>
+</com.android.wm.shell.apptoweb.OpenByDefaultDialogView>
+
diff --git a/libs/WindowManager/Shell/res/layout/pip2_menu_action.xml b/libs/WindowManager/Shell/res/layout/pip2_menu_action.xml
new file mode 100644
index 0000000..04ece31
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/pip2_menu_action.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<com.android.wm.shell.pip2.phone.PipMenuActionView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="@dimen/pip_action_size"
+ android:layout_height="@dimen/pip_action_size"
+ android:background="?android:selectableItemBackgroundBorderless"
+ android:forceHasOverlappingRendering="false">
+
+ <ImageView
+ android:id="@+id/custom_close_bg"
+ android:layout_width="@dimen/pip_custom_close_bg_size"
+ android:layout_height="@dimen/pip_custom_close_bg_size"
+ android:layout_gravity="center"
+ android:src="@drawable/pip_custom_close_bg"
+ android:visibility="gone"/>
+
+ <ImageView
+ android:id="@+id/image"
+ android:layout_width="@dimen/pip_action_inner_size"
+ android:layout_height="@dimen/pip_action_inner_size"
+ android:layout_gravity="center"
+ android:scaleType="fitXY"/>
+
+</com.android.wm.shell.pip2.phone.PipMenuActionView>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index c7109f5..1f15651 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -624,4 +624,12 @@
<!-- The offset from the left edge of the entering page for the cross-activity animation -->
<dimen name="cross_activity_back_entering_start_offset">96dp</dimen>
+ <!-- The open by default settings dialog menu width. -->
+ <dimen name="open_by_default_settings_dialog_width">348dp</dimen>
+ <!-- The open by default settings dialog menu height. -->
+ <dimen name="open_by_default_settings_dialog_height">380dp</dimen>
+ <!-- The height of radio buttons in the open by default settings dialog. -->
+ <dimen name="open_by_default_settings_dialog_radio_button_height">64dp</dimen>
+ <!-- The width of radio buttons in the open by default settings dialog. -->
+ <dimen name="open_by_default_settings_dialog_radio_button_width">316dp</dimen>
</resources>
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 56f25da..5ef8432 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -323,4 +323,15 @@
<string name="desktop_mode_maximize_menu_snap_left_button_text">Snap left</string>
<!-- Accessibility text for the Maximize Menu's snap right button [CHAR LIMIT=NONE] -->
<string name="desktop_mode_maximize_menu_snap_right_button_text">Snap right</string>
+
+ <!-- Accessibility text for open by default settings button [CHAR LIMIT=NONE] -->
+ <string name="open_by_default_settings_text">Open by default settings</string>
+ <!-- Subheader for open by default menu string. -->
+ <string name="open_by_default_dialog_subheader_text">Choose how to open web links for this app</string>
+ <!-- Text for open by default settings dialog option. -->
+ <string name="open_by_default_dialog_in_app_text">In the app</string>
+ <!-- Text for open by default settings dialog option. -->
+ <string name="open_by_default_dialog_in_browser_text">In your browser</string>
+ <!-- Text for open by default settings dialog dismiss button. -->
+ <string name="open_by_default_dialog_dismiss_button_text">OK</string>
</resources>
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleInfo.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleInfo.java
index 5876682..85dabce 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleInfo.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleInfo.java
@@ -30,29 +30,32 @@
*/
public class BubbleInfo implements Parcelable {
- private String mKey; // Same key as the Notification
+ private final String mKey; // Same key as the Notification
private int mFlags; // Flags from BubbleMetadata
@Nullable
- private String mShortcutId;
- private int mUserId;
- private String mPackageName;
+ private final String mShortcutId;
+ private final int mUserId;
+ private final String mPackageName;
/**
* All notification bubbles require a shortcut to be set on the notification, however, the
* app could still specify an Icon and PendingIntent to use for the bubble. In that case
* this icon will be populated. If the bubble is entirely shortcut based, this will be null.
*/
@Nullable
- private Icon mIcon;
+ private final Icon mIcon;
@Nullable
- private String mTitle;
+ private final String mTitle;
@Nullable
- private String mAppName;
- private boolean mIsImportantConversation;
- private boolean mShowAppBadge;
+ private final String mAppName;
+ private final boolean mIsImportantConversation;
+ private final boolean mShowAppBadge;
+ @Nullable
+ private final ParcelableFlyoutMessage mParcelableFlyoutMessage;
public BubbleInfo(String key, int flags, @Nullable String shortcutId, @Nullable Icon icon,
int userId, String packageName, @Nullable String title, @Nullable String appName,
- boolean isImportantConversation, boolean showAppBadge) {
+ boolean isImportantConversation, boolean showAppBadge,
+ @Nullable ParcelableFlyoutMessage flyoutMessage) {
mKey = key;
mFlags = flags;
mShortcutId = shortcutId;
@@ -63,6 +66,7 @@
mAppName = appName;
mIsImportantConversation = isImportantConversation;
mShowAppBadge = showAppBadge;
+ mParcelableFlyoutMessage = flyoutMessage;
}
private BubbleInfo(Parcel source) {
@@ -76,6 +80,8 @@
mAppName = source.readString();
mIsImportantConversation = source.readBoolean();
mShowAppBadge = source.readBoolean();
+ mParcelableFlyoutMessage = source.readParcelable(
+ ParcelableFlyoutMessage.class.getClassLoader(), ParcelableFlyoutMessage.class);
}
public String getKey() {
@@ -122,6 +128,11 @@
return mShowAppBadge;
}
+ @Nullable
+ public ParcelableFlyoutMessage getParcelableFlyoutMessage() {
+ return mParcelableFlyoutMessage;
+ }
+
/**
* Whether this bubble is currently being hidden from the stack.
*/
@@ -180,6 +191,7 @@
parcel.writeString(mAppName);
parcel.writeBoolean(mIsImportantConversation);
parcel.writeBoolean(mShowAppBadge);
+ parcel.writeParcelable(mParcelableFlyoutMessage, flags);
}
@NonNull
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/ParcelableFlyoutMessage.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/ParcelableFlyoutMessage.kt
new file mode 100644
index 0000000..294d5e5
--- /dev/null
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/ParcelableFlyoutMessage.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.shared.bubbles
+
+import android.graphics.drawable.Icon
+import android.os.Parcel
+import android.os.Parcelable
+
+/** The contents of the flyout message to be passed to launcher for rendering in the bubble bar. */
+class ParcelableFlyoutMessage(
+ val icon: Icon?,
+ val title: String?,
+ val message: String?,
+) : Parcelable {
+
+ constructor(
+ parcel: Parcel
+ ) : this(
+ icon = parcel.readParcelable(Icon::class.java.classLoader),
+ title = parcel.readString(),
+ message = parcel.readString(),
+ )
+
+ override fun writeToParcel(parcel: Parcel, flags: Int) {
+ parcel.writeParcelable(icon, flags)
+ parcel.writeString(title)
+ parcel.writeString(message)
+ }
+
+ override fun describeContents() = 0
+
+ companion object {
+ @JvmField
+ val CREATOR =
+ object : Parcelable.Creator<ParcelableFlyoutMessage> {
+ override fun createFromParcel(parcel: Parcel) = ParcelableFlyoutMessage(parcel)
+
+ override fun newArray(size: Int) = arrayOfNulls<ParcelableFlyoutMessage>(size)
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OpenByDefaultDialog.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OpenByDefaultDialog.kt
new file mode 100644
index 0000000..4926cbd
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OpenByDefaultDialog.kt
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.apptoweb
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.TaskInfo
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.PixelFormat
+import android.view.LayoutInflater
+import android.view.SurfaceControl
+import android.view.SurfaceControlViewHost
+import android.view.WindowManager
+import android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+import android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+import android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL
+import android.view.WindowlessWindowManager
+import android.widget.ImageView
+import android.widget.TextView
+import android.window.TaskConstants
+import com.android.wm.shell.R
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewHostViewContainer
+import java.util.function.Supplier
+
+
+/**
+ * Window manager for the open by default settings dialog
+ */
+internal class OpenByDefaultDialog(
+ private val context: Context,
+ private val taskInfo: TaskInfo,
+ private val taskSurface: SurfaceControl,
+ private val displayController: DisplayController,
+ private val surfaceControlTransactionSupplier: Supplier<SurfaceControl.Transaction>,
+ private val listener: DialogLifecycleListener,
+ appIconBitmap: Bitmap?,
+ appName: CharSequence?
+) {
+ private lateinit var dialog: OpenByDefaultDialogView
+ private lateinit var viewHost: SurfaceControlViewHost
+ private lateinit var dialogSurfaceControl: SurfaceControl
+ private var dialogContainer: AdditionalViewHostViewContainer? = null
+ private lateinit var appIconView: ImageView
+ private lateinit var appNameView: TextView
+
+ init {
+ createDialog()
+ bindAppInfo(appIconBitmap, appName)
+ }
+
+ /** Creates an open by default settings dialog. */
+ fun createDialog() {
+ val t = SurfaceControl.Transaction()
+ val taskBounds = taskInfo.configuration.windowConfiguration.bounds
+
+ dialog = LayoutInflater.from(context)
+ .inflate(
+ R.layout.open_by_default_settings_dialog,
+ null /* root */
+ ) as OpenByDefaultDialogView
+ appIconView = dialog.requireViewById(R.id.application_icon)
+ appNameView = dialog.requireViewById(R.id.application_name)
+
+ val display = displayController.getDisplay(taskInfo.displayId)
+ val builder: SurfaceControl.Builder = SurfaceControl.Builder()
+ dialogSurfaceControl = builder
+ .setName("Open by Default Dialog of Task=" + taskInfo.taskId)
+ .setContainerLayer()
+ .setParent(taskSurface)
+ .setCallsite("OpenByDefaultDialog#createDialog")
+ .build()
+ t.setPosition(dialogSurfaceControl, 0f, 0f)
+ .setWindowCrop(dialogSurfaceControl, taskBounds.width(), taskBounds.height())
+ .setLayer(dialogSurfaceControl, TaskConstants.TASK_CHILD_LAYER_SETTINGS_DIALOG)
+ .show(dialogSurfaceControl)
+ val lp = WindowManager.LayoutParams(
+ taskBounds.width(),
+ taskBounds.height(),
+ TYPE_APPLICATION_PANEL,
+ FLAG_NOT_FOCUSABLE or FLAG_NOT_TOUCH_MODAL,
+ PixelFormat.TRANSLUCENT)
+ lp.title = "Open by default settings dialog of task=" + taskInfo.taskId
+ lp.setTrustedOverlay()
+ val windowManager = WindowlessWindowManager(
+ taskInfo.configuration,
+ dialogSurfaceControl, null /* hostInputToken */
+ )
+ viewHost = SurfaceControlViewHost(context, display, windowManager, "Dialog").apply {
+ setView(dialog, lp)
+ rootSurfaceControl.applyTransactionOnDraw(t)
+ }
+ dialogContainer = AdditionalViewHostViewContainer(
+ dialogSurfaceControl, viewHost, surfaceControlTransactionSupplier)
+
+ dialog.setDismissOnClickListener{
+ closeMenu()
+ }
+
+ listener.onDialogCreated()
+ }
+
+ private fun closeMenu() {
+ dialogContainer?.releaseView()
+ dialogContainer = null
+ listener.onDialogDismissed()
+ }
+
+ private fun bindAppInfo(
+ appIconBitmap: Bitmap?,
+ appName: CharSequence?
+ ) {
+ appIconView.setImageBitmap(appIconBitmap)
+ appNameView.text = appName
+ }
+
+ /**
+ * Relayout the dialog to the new task bounds.
+ */
+ fun relayout(
+ taskInfo: RunningTaskInfo,
+ ) {
+ val t = surfaceControlTransactionSupplier.get()
+ val taskBounds = taskInfo.configuration.windowConfiguration.bounds
+ t.setWindowCrop(dialogSurfaceControl, taskBounds.width(), taskBounds.height())
+ viewHost.rootSurfaceControl.applyTransactionOnDraw(t)
+ viewHost.relayout(taskBounds.width(), taskBounds.height())
+ }
+
+ /**
+ * Defines interface for classes that can listen to lifecycle events of open by default settings
+ * dialog.
+ */
+ interface DialogLifecycleListener {
+ /** Called when open by default dialog view has been created. */
+ fun onDialogCreated()
+
+ /** Called when open by default dialog view has been released. */
+ fun onDialogDismissed()
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OpenByDefaultDialogView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OpenByDefaultDialogView.kt
new file mode 100644
index 0000000..d03a38e
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OpenByDefaultDialogView.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.apptoweb
+import android.content.Context
+import android.graphics.drawable.Drawable
+import android.util.AttributeSet
+import android.view.View
+import android.widget.Button
+import androidx.constraintlayout.widget.ConstraintLayout
+import com.android.wm.shell.R
+
+/** View for open by default settings dialog for an application which allows the user to change
+ * where links will open by default, in the default browser or in the application. */
+class OpenByDefaultDialogView @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0,
+ defStyleRes: Int = 0
+) : ConstraintLayout(context, attrs, defStyleAttr, defStyleRes) {
+
+ private lateinit var dialogContainer: View
+ private lateinit var backgroundDim: Drawable
+
+ fun setDismissOnClickListener(callback: (View) -> Unit) {
+ val dismissButton = dialogContainer.requireViewById<Button>(
+ R.id.open_by_default_settings_dialog_dismiss_button)
+ dismissButton.setOnClickListener(callback)
+ // Clicks on the background dim should also dismiss the dialog.
+ setOnClickListener(callback)
+ // We add a no-op on-click listener to the dialog container so that clicks on it won't
+ // propagate to the listener of the layout (which represents the background dim).
+ dialogContainer.setOnClickListener { }
+ }
+
+ override fun onFinishInflate() {
+ super.onFinishInflate()
+ dialogContainer = requireViewById(R.id.open_by_default_dialog_container)
+ backgroundDim = background.mutate()
+ backgroundDim.alpha = 128
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index 169361a..e3fc5c2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -56,6 +56,7 @@
import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.shared.bubbles.BubbleInfo;
+import com.android.wm.shell.shared.bubbles.ParcelableFlyoutMessage;
import java.io.PrintWriter;
import java.util.List;
@@ -350,7 +351,22 @@
getTitle(),
getAppName(),
isImportantConversation(),
- !isAppLaunchIntent());
+ !isAppLaunchIntent(),
+ getParcelableFlyoutMessage());
+ }
+
+ /** Creates a parcelable flyout message to send to launcher. */
+ @Nullable
+ private ParcelableFlyoutMessage getParcelableFlyoutMessage() {
+ if (mFlyoutMessage == null) {
+ return null;
+ }
+ // the icon is only used in group chats
+ Icon icon = mFlyoutMessage.isGroupChat ? mFlyoutMessage.senderIcon : null;
+ String title =
+ mFlyoutMessage.senderName == null ? null : mFlyoutMessage.senderName.toString();
+ String message = mFlyoutMessage.message == null ? null : mFlyoutMessage.message.toString();
+ return new ParcelableFlyoutMessage(icon, title, message);
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 03b7c8b..a8a8c2a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -274,7 +274,8 @@
private final DragAndDropController mDragAndDropController;
/** Used to send bubble events to launcher. */
private Bubbles.BubbleStateListener mBubbleStateListener;
-
+ /** Used to track previous navigation mode to detect switch to buttons navigation. */
+ private boolean mIsPrevNavModeGestures;
/** Used to send updates to the views from {@link #mBubbleDataListener}. */
private BubbleViewCallback mBubbleViewCallback;
@@ -356,6 +357,7 @@
}
};
mExpandedViewManager = BubbleExpandedViewManager.fromBubbleController(this);
+ mIsPrevNavModeGestures = ContextUtils.isGestureNavigationMode(mContext);
}
private void registerOneHandedState(OneHandedController oneHanded) {
@@ -589,6 +591,13 @@
*/
private void sendInitialListenerUpdate() {
if (mBubbleStateListener != null) {
+ boolean isCurrentNavModeGestures = ContextUtils.isGestureNavigationMode(mContext);
+ if (mIsPrevNavModeGestures && !isCurrentNavModeGestures) {
+ BubbleBarLocation navButtonsLocation = ContextUtils.isRtl(mContext)
+ ? BubbleBarLocation.RIGHT : BubbleBarLocation.LEFT;
+ mBubblePositioner.setBubbleBarLocation(navButtonsLocation);
+ }
+ mIsPrevNavModeGestures = isCurrentNavModeGestures;
BubbleBarUpdate update = mBubbleData.getInitialStateForBubbleBar();
mBubbleStateListener.onBubbleStateChange(update);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 2795881..35a0d07 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -61,7 +61,6 @@
import android.view.ViewPropertyAnimator;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
-import android.view.WindowManagerPolicyConstants;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.widget.FrameLayout;
@@ -91,10 +90,10 @@
import com.android.wm.shell.bubbles.animation.StackAnimationController;
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.shared.bubbles.DismissView;
-import com.android.wm.shell.shared.bubbles.RelativeTouchListener;
import com.android.wm.shell.shared.animation.Interpolators;
import com.android.wm.shell.shared.animation.PhysicsAnimator;
+import com.android.wm.shell.shared.bubbles.DismissView;
+import com.android.wm.shell.shared.bubbles.RelativeTouchListener;
import com.android.wm.shell.shared.magnetictarget.MagnetizedObject;
import java.io.PrintWriter;
@@ -2276,7 +2275,7 @@
void startMonitoringSwipeUpGesture() {
stopMonitoringSwipeUpGestureInternal();
- if (isGestureNavEnabled()) {
+ if (ContextUtils.isGestureNavigationMode(mContext)) {
mBubblesNavBarGestureTracker = new BubblesNavBarGestureTracker(mContext, mPositioner);
mBubblesNavBarGestureTracker.start(mSwipeUpListener);
setOnTouchListener(mContainerSwipeListener);
@@ -2311,12 +2310,6 @@
}
}
- private boolean isGestureNavEnabled() {
- return mContext.getResources().getInteger(
- com.android.internal.R.integer.config_navBarInteractionMode)
- == WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
- }
-
/**
* Stop monitoring for swipe up gesture
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
index 3982a23..c5e3afd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
@@ -274,7 +274,7 @@
@Nullable BubbleExpandedView expandedView;
int dotColor;
Path dotPath;
- @Nullable Bubble.FlyoutMessage flyoutMessage;
+ Bubble.FlyoutMessage flyoutMessage;
Bitmap bubbleBitmap;
Bitmap badgeBitmap;
@@ -300,6 +300,10 @@
return null;
}
+ // set the flyout message but don't load the avatar because we can't pass it on the
+ // binder to launcher
+ info.flyoutMessage = b.getFlyoutMessage();
+
return info;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskLegacy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskLegacy.java
index 1b7bb0d..c12822a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskLegacy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskLegacy.java
@@ -181,7 +181,7 @@
@Nullable BubbleExpandedView expandedView;
int dotColor;
Path dotPath;
- @Nullable Bubble.FlyoutMessage flyoutMessage;
+ Bubble.FlyoutMessage flyoutMessage;
Bitmap bubbleBitmap;
Bitmap badgeBitmap;
@@ -221,6 +221,10 @@
return null;
}
+ // set the flyout message but don't load the avatar because we can't pass it on the
+ // binder to launcher
+ info.flyoutMessage = b.getFlyoutMessage();
+
return info;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ContextUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ContextUtils.kt
new file mode 100644
index 0000000..0b36f45
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ContextUtils.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles
+
+import android.content.Context
+import android.view.View
+import android.view.WindowManagerPolicyConstants
+import com.android.internal.R
+
+/** Simplifies accessing context fields. */
+object ContextUtils {
+
+ /** Gets navigation mode. */
+ @JvmStatic
+ val Context.navigationMode: Int
+ get() = resources.getInteger(R.integer.config_navBarInteractionMode)
+
+ /** Returns whether the navigation mode is gestures. */
+ @JvmStatic
+ val Context.isGestureNavigationMode: Boolean
+ get() = navigationMode == WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL
+
+ /** Returns whether layout direction is rtl. */
+ @JvmStatic
+ val Context.isRtl: Boolean
+ get() = resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index 0047ec5..38087c0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -157,6 +157,14 @@
}
}
+ private void dispatchImeRequested(int displayId, boolean isRequested) {
+ synchronized (mPositionProcessors) {
+ for (ImePositionProcessor pp : mPositionProcessors) {
+ pp.onImeRequested(displayId, isRequested);
+ }
+ }
+ }
+
@ImePositionProcessor.ImeAnimationFlags
private int dispatchStartPositioning(int displayId, int hiddenTop, int shownTop,
boolean show, boolean isFloating, SurfaceControl.Transaction t) {
@@ -398,6 +406,8 @@
public void setImeInputTargetRequestedVisibility(boolean visible) {
if (android.view.inputmethod.Flags.refactorInsetsController()) {
mImeRequestedVisible = visible;
+ dispatchImeRequested(mDisplayId, mImeRequestedVisible);
+
// In the case that the IME becomes visible, but we have the control with leash
// already (e.g., when focussing an editText in activity B, while and editText in
// activity A is focussed), we will not get a call of #insetsControlChanged, and
@@ -446,6 +456,8 @@
if (imeSource == null || mImeSourceControl == null) {
return;
}
+ // TODO(b/353463205): For hide: this still has the statsToken from the previous show
+ // request
final var statsToken = mImeSourceControl.getImeStatsToken();
startAnimation(show, forceRestart, statsToken);
@@ -706,6 +718,14 @@
}
/**
+ * Called when the IME was requested by an app
+ *
+ * @param isRequested {@code true} if the IME was requested to be visible
+ */
+ default void onImeRequested(int displayId, boolean isRequested) {
+ }
+
+ /**
* Called when the IME position is starting to animate.
*
* @param hiddenTop The y position of the top of the IME surface when it is hidden.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 4b55fd0..83ffaf4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -1106,6 +1106,11 @@
default void onDoubleTappedDivider() {
}
+ /**
+ * Sets the excludedInsetsTypes for the IME in the root WindowContainer.
+ */
+ void setExcludeImeInsets(boolean exclude);
+
/** Returns split position of the token. */
@SplitPosition
int getSplitItemPosition(WindowContainerToken token);
@@ -1305,6 +1310,14 @@
}
@Override
+ public void onImeRequested(int displayId, boolean isRequested) {
+ if (displayId != mDisplayId) return;
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "IME was set to requested=%s",
+ isRequested);
+ mSplitLayoutHandler.setExcludeImeInsets(true);
+ }
+
+ @Override
public int onImeStartPositioning(int displayId, int hiddenTop, int shownTop,
boolean showing, boolean isFloating, SurfaceControl.Transaction t) {
if (displayId != mDisplayId || !mInitialized) {
@@ -1356,6 +1369,12 @@
setDividerInteractive(!mImeShown || !mHasImeFocus || isFloating, true,
"onImeStartPositioning");
+ if (android.view.inputmethod.Flags.refactorInsetsController()) {
+ if (mImeShown) {
+ mSplitLayoutHandler.setExcludeImeInsets(false);
+ }
+ }
+
return mTargetYOffset != mLastYOffset ? IME_ANIMATION_NO_ALPHA : 0;
}
@@ -1374,6 +1393,16 @@
"Split IME animation ending, canceled=%b", cancel);
onProgress(1.0f);
mSplitLayoutHandler.onLayoutPositionChanging(SplitLayout.this);
+ if (android.view.inputmethod.Flags.refactorInsetsController()) {
+ if (!mImeShown) {
+ // The IME hide animation is started immediately and at that point, the IME
+ // insets are not yet set to hidden. Therefore only resetting the
+ // excludedTypes at the end of the animation. Note: InsetsPolicy will only
+ // set the IME height to zero, when it is visible. When it becomes invisible,
+ // we dispatch the insets (the height there is zero as well)
+ mSplitLayoutHandler.setExcludeImeInsets(false);
+ }
+ }
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 4227a6e..2a5a519 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -87,7 +87,7 @@
import com.android.wm.shell.compatui.impl.DefaultCompatUIRepository;
import com.android.wm.shell.compatui.impl.DefaultComponentIdGenerator;
import com.android.wm.shell.desktopmode.DesktopMode;
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
import com.android.wm.shell.displayareahelper.DisplayAreaHelperController;
@@ -267,7 +267,7 @@
Lazy<CompatUIShellCommandHandler> compatUIShellCommandHandler,
Lazy<AccessibilityManager> accessibilityManager,
CompatUIRepository compatUIRepository,
- Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
+ Optional<DesktopRepository> desktopRepository,
@NonNull CompatUIState compatUIState,
@NonNull CompatUIComponentIdGenerator componentIdGenerator,
@NonNull CompatUIComponentFactory compatUIComponentFactory,
@@ -281,7 +281,7 @@
componentIdGenerator, compatUIComponentFactory, mainExecutor));
}
final IntPredicate inDesktopModePredicate =
- desktopModeTaskRepository.<IntPredicate>map(modeTaskRepository -> displayId ->
+ desktopRepository.<IntPredicate>map(modeTaskRepository -> displayId ->
modeTaskRepository.getVisibleTaskCount(displayId) > 0)
.orElseGet(() -> displayId -> false);
return Optional.of(
@@ -707,14 +707,14 @@
ShellCommandHandler shellCommandHandler,
TaskStackListenerImpl taskStackListener,
ActivityTaskManager activityTaskManager,
- Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
+ Optional<DesktopRepository> desktopRepository,
TaskStackTransitionObserver taskStackTransitionObserver,
@ShellMainThread ShellExecutor mainExecutor
) {
return Optional.ofNullable(
RecentTasksController.create(context, shellInit, shellController,
shellCommandHandler, taskStackListener, activityTaskManager,
- desktopModeTaskRepository, taskStackTransitionObserver, mainExecutor));
+ desktopRepository, taskStackTransitionObserver, mainExecutor));
}
@BindsOptionalOf
@@ -1003,16 +1003,16 @@
@BindsOptionalOf
@DynamicOverride
- abstract DesktopModeTaskRepository optionalDesktopModeTaskRepository();
+ abstract DesktopRepository optionalDesktopRepository();
@WMSingleton
@Provides
- static Optional<DesktopModeTaskRepository> provideDesktopTaskRepository(Context context,
- @DynamicOverride Optional<Lazy<DesktopModeTaskRepository>> desktopModeTaskRepository) {
+ static Optional<DesktopRepository> provideDesktopRepository(Context context,
+ @DynamicOverride Optional<Lazy<DesktopRepository>> desktopRepository) {
// Use optional-of-lazy for the dependency that this provider relies on.
// Lazy ensures that this provider will not be the cause the dependency is created
// when it will not be returned due to the condition below.
- return desktopModeTaskRepository.flatMap((lazy) -> {
+ return desktopRepository.flatMap((lazy) -> {
if (DesktopModeStatus.canEnterDesktopMode(context)) {
return Optional.of(lazy.get());
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 7293956..79c31e0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -65,14 +65,16 @@
import com.android.wm.shell.desktopmode.CloseDesktopTaskTransitionHandler;
import com.android.wm.shell.desktopmode.DefaultDragToDesktopTransitionHandler;
import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler;
+import com.android.wm.shell.desktopmode.DesktopFullImmersiveTransitionHandler;
import com.android.wm.shell.desktopmode.DesktopMixedTransitionHandler;
import com.android.wm.shell.desktopmode.DesktopModeDragAndDropTransitionHandler;
import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
import com.android.wm.shell.desktopmode.DesktopModeLoggerTransitionObserver;
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.desktopmode.DesktopTasksLimiter;
import com.android.wm.shell.desktopmode.DesktopTasksTransitionObserver;
+import com.android.wm.shell.desktopmode.DesktopTaskChangeListener;
import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler;
import com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler;
import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler;
@@ -90,6 +92,7 @@
import com.android.wm.shell.freeform.FreeformTaskListener;
import com.android.wm.shell.freeform.FreeformTaskTransitionHandler;
import com.android.wm.shell.freeform.FreeformTaskTransitionObserver;
+import com.android.wm.shell.freeform.TaskChangeListener;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarterInitializer;
import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
@@ -245,7 +248,7 @@
IWindowManager windowManager,
ShellCommandHandler shellCommandHandler,
ShellTaskOrganizer taskOrganizer,
- @DynamicOverride DesktopModeTaskRepository desktopRepository,
+ @DynamicOverride DesktopRepository desktopRepository,
DisplayController displayController,
ShellController shellController,
DisplayInsetsController displayInsetsController,
@@ -352,7 +355,8 @@
Context context,
ShellInit shellInit,
ShellTaskOrganizer shellTaskOrganizer,
- Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
+ Optional<DesktopRepository> desktopRepository,
+ Optional<DesktopTasksController> desktopTasksController,
LaunchAdjacentController launchAdjacentController,
WindowDecorViewModel windowDecorViewModel) {
// TODO(b/238217847): Temporarily add this check here until we can remove the dynamic
@@ -361,7 +365,8 @@
? shellInit
: null;
return new FreeformTaskListener(context, init, shellTaskOrganizer,
- desktopModeTaskRepository, launchAdjacentController, windowDecorViewModel);
+ desktopRepository, desktopTasksController, launchAdjacentController,
+ windowDecorViewModel);
}
@WMSingleton
@@ -384,9 +389,12 @@
Context context,
ShellInit shellInit,
Transitions transitions,
- WindowDecorViewModel windowDecorViewModel) {
+ Optional<DesktopFullImmersiveTransitionHandler> desktopImmersiveTransitionHandler,
+ WindowDecorViewModel windowDecorViewModel,
+ Optional<TaskChangeListener> taskChangeListener) {
return new FreeformTaskTransitionObserver(
- context, shellInit, transitions, windowDecorViewModel);
+ context, shellInit, transitions, desktopImmersiveTransitionHandler,
+ windowDecorViewModel, taskChangeListener);
}
@WMSingleton
@@ -410,7 +418,6 @@
// One handed mode
//
-
// Needs the shell main handler for ContentObserver callbacks
@WMSingleton
@Provides
@@ -620,7 +627,8 @@
DesktopModeDragAndDropTransitionHandler desktopModeDragAndDropTransitionHandler,
ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler,
DragToDesktopTransitionHandler dragToDesktopTransitionHandler,
- @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
+ @DynamicOverride DesktopRepository desktopRepository,
+ Optional<DesktopFullImmersiveTransitionHandler> desktopFullImmersiveTransitionHandler,
DesktopModeLoggerTransitionObserver desktopModeLoggerTransitionObserver,
LaunchAdjacentController launchAdjacentController,
RecentsTransitionHandler recentsTransitionHandler,
@@ -636,7 +644,8 @@
returnToDragStartAnimator, enterDesktopTransitionHandler,
exitDesktopTransitionHandler, desktopModeDragAndDropTransitionHandler,
toggleResizeDesktopTaskTransitionHandler,
- dragToDesktopTransitionHandler, desktopModeTaskRepository,
+ dragToDesktopTransitionHandler, desktopFullImmersiveTransitionHandler.get(),
+ desktopRepository,
desktopModeLoggerTransitionObserver, launchAdjacentController,
recentsTransitionHandler, multiInstanceHelper, mainExecutor, desktopTasksLimiter,
recentTasksController.orElse(null), interactionJankMonitor, mainHandler);
@@ -644,10 +653,20 @@
@WMSingleton
@Provides
+ static Optional<TaskChangeListener> provideDesktopTaskChangeListener(Context context) {
+ if (Flags.enableWindowingTransitionHandlersObservers() &&
+ DesktopModeStatus.canEnterDesktopMode(context)) {
+ return Optional.of(new DesktopTaskChangeListener());
+ }
+ return Optional.empty();
+ }
+
+ @WMSingleton
+ @Provides
static Optional<DesktopTasksLimiter> provideDesktopTasksLimiter(
Context context,
Transitions transitions,
- @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
+ @DynamicOverride DesktopRepository desktopRepository,
ShellTaskOrganizer shellTaskOrganizer,
InteractionJankMonitor interactionJankMonitor,
@ShellMainThread Handler handler) {
@@ -660,7 +679,7 @@
return Optional.of(
new DesktopTasksLimiter(
transitions,
- desktopModeTaskRepository,
+ desktopRepository,
shellTaskOrganizer,
maxTaskLimit,
interactionJankMonitor,
@@ -671,6 +690,19 @@
@WMSingleton
@Provides
+ static Optional<DesktopFullImmersiveTransitionHandler> provideDesktopImmersiveHandler(
+ Context context,
+ Transitions transitions,
+ @DynamicOverride DesktopRepository desktopRepository) {
+ if (DesktopModeStatus.canEnterDesktopMode(context)) {
+ return Optional.of(
+ new DesktopFullImmersiveTransitionHandler(transitions, desktopRepository));
+ }
+ return Optional.empty();
+ }
+
+ @WMSingleton
+ @Provides
static ReturnToDragStartAnimator provideReturnToDragStartAnimator(
Context context, InteractionJankMonitor interactionJankMonitor) {
return new ReturnToDragStartAnimator(context, interactionJankMonitor);
@@ -739,13 +771,14 @@
@WMSingleton
@Provides
@DynamicOverride
- static DesktopModeTaskRepository provideDesktopModeTaskRepository(
+
+ static DesktopRepository provideDesktopRepository(
Context context,
ShellInit shellInit,
DesktopPersistentRepository desktopPersistentRepository,
@ShellMainThread CoroutineScope mainScope
) {
- return new DesktopModeTaskRepository(context, shellInit, desktopPersistentRepository,
+ return new DesktopRepository(context, shellInit, desktopPersistentRepository,
mainScope);
}
@@ -757,12 +790,12 @@
ShellTaskOrganizer shellTaskOrganizer,
TaskStackListenerImpl taskStackListener,
ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler,
- @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository
+ @DynamicOverride DesktopRepository desktopRepository
) {
if (DesktopModeStatus.canEnterDesktopMode(context)) {
return Optional.of(new DesktopActivityOrientationChangeHandler(
context, shellInit, shellTaskOrganizer, taskStackListener,
- toggleResizeDesktopTaskTransitionHandler, desktopModeTaskRepository));
+ toggleResizeDesktopTaskTransitionHandler, desktopRepository));
}
return Optional.empty();
}
@@ -771,12 +804,12 @@
@Provides
static Optional<DesktopTasksTransitionObserver> provideDesktopTasksTransitionObserver(
Context context,
- Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
+ Optional<DesktopRepository> desktopRepository,
Transitions transitions,
ShellTaskOrganizer shellTaskOrganizer,
ShellInit shellInit
) {
- return desktopModeTaskRepository.flatMap(repository ->
+ return desktopRepository.flatMap(repository ->
Optional.of(new DesktopTasksTransitionObserver(
context, repository, transitions, shellTaskOrganizer, shellInit))
);
@@ -787,7 +820,7 @@
static Optional<DesktopMixedTransitionHandler> provideDesktopMixedTransitionHandler(
Context context,
Transitions transitions,
- @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
+ @DynamicOverride DesktopRepository desktopRepository,
FreeformTaskTransitionHandler freeformTaskTransitionHandler,
CloseDesktopTaskTransitionHandler closeDesktopTaskTransitionHandler,
InteractionJankMonitor interactionJankMonitor,
@@ -801,7 +834,7 @@
new DesktopMixedTransitionHandler(
context,
transitions,
- desktopModeTaskRepository,
+ desktopRepository,
freeformTaskTransitionHandler,
closeDesktopTaskTransitionHandler,
interactionJankMonitor,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
index 3464fef..3508ece 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -77,10 +77,12 @@
PipTaskListener pipTaskListener,
@NonNull PipScheduler pipScheduler,
@NonNull PipTransitionState pipStackListenerController,
+ @NonNull PipDisplayLayoutState pipDisplayLayoutState,
@NonNull PipUiStateChangeController pipUiStateChangeController) {
return new PipTransition(context, shellInit, shellTaskOrganizer, transitions,
pipBoundsState, null, pipBoundsAlgorithm, pipTaskListener,
- pipScheduler, pipStackListenerController, pipUiStateChangeController);
+ pipScheduler, pipStackListenerController, pipDisplayLayoutState,
+ pipUiStateChangeController);
}
@WMSingleton
@@ -125,11 +127,9 @@
@Provides
static PipScheduler providePipScheduler(Context context,
PipBoundsState pipBoundsState,
- PhonePipMenuController pipMenuController,
@ShellMainThread ShellExecutor mainExecutor,
PipTransitionState pipTransitionState) {
- return new PipScheduler(context, pipBoundsState, pipMenuController,
- mainExecutor, pipTransitionState);
+ return new PipScheduler(context, pipBoundsState, mainExecutor, pipTransitionState);
}
@WMSingleton
@@ -138,10 +138,13 @@
PipBoundsState pipBoundsState, PipMediaController pipMediaController,
SystemWindows systemWindows,
PipUiEventLogger pipUiEventLogger,
+ PipTaskListener pipTaskListener,
+ @NonNull PipTransitionState pipTransitionState,
@ShellMainThread ShellExecutor mainExecutor,
@ShellMainThread Handler mainHandler) {
return new PhonePipMenuController(context, pipBoundsState, pipMediaController,
- systemWindows, pipUiEventLogger, mainExecutor, mainHandler);
+ systemWindows, pipUiEventLogger, pipTaskListener, pipTransitionState, mainExecutor,
+ mainHandler);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandler.kt
index 59e0068..606aa6c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandler.kt
@@ -39,7 +39,7 @@
private val shellTaskOrganizer: ShellTaskOrganizer,
private val taskStackListener: TaskStackListenerImpl,
private val resizeHandler: ToggleResizeDesktopTaskTransitionHandler,
- private val taskRepository: DesktopModeTaskRepository,
+ private val taskRepository: DesktopRepository,
) {
init {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopFullImmersiveTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopFullImmersiveTransitionHandler.kt
new file mode 100644
index 0000000..f749aa1
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopFullImmersiveTransitionHandler.kt
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.desktopmode
+
+import android.animation.RectEvaluator
+import android.animation.ValueAnimator
+import android.app.ActivityManager.RunningTaskInfo
+import android.graphics.Rect
+import android.os.IBinder
+import android.view.SurfaceControl
+import android.view.WindowManager.TRANSIT_CHANGE
+import android.view.animation.DecelerateInterpolator
+import android.window.TransitionInfo
+import android.window.TransitionRequestInfo
+import android.window.WindowContainerTransaction
+import androidx.core.animation.addListener
+import com.android.internal.protolog.ProtoLog
+import com.android.wm.shell.protolog.ShellProtoLogGroup
+import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.transition.Transitions.TransitionHandler
+import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener
+
+/**
+ * A [TransitionHandler] to move a task in/out of desktop's full immersive state where the task
+ * remains freeform while being able to take fullscreen bounds and have its App Header visibility
+ * be transient below the status bar like in fullscreen immersive mode.
+ */
+class DesktopFullImmersiveTransitionHandler(
+ private val transitions: Transitions,
+ private val desktopRepository: DesktopRepository,
+ private val transactionSupplier: () -> SurfaceControl.Transaction,
+) : TransitionHandler {
+
+ constructor(
+ transitions: Transitions,
+ desktopRepository: DesktopRepository,
+ ) : this(transitions, desktopRepository, { SurfaceControl.Transaction() })
+
+ private var state: TransitionState? = null
+
+ /** Whether there is an immersive transition that hasn't completed yet. */
+ private val inProgress: Boolean
+ get() = state != null
+
+ private val rectEvaluator = RectEvaluator()
+
+ /** A listener to invoke on animation changes during entry/exit. */
+ var onTaskResizeAnimationListener: OnTaskResizeAnimationListener? = null
+
+ /** Starts a transition to enter full immersive state inside the desktop. */
+ fun enterImmersive(taskInfo: RunningTaskInfo, wct: WindowContainerTransaction) {
+ if (inProgress) {
+ ProtoLog.v(
+ ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+ "FullImmersive: cannot start entry because transition already in progress."
+ )
+ return
+ }
+
+ val transition = transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ this)
+ state = TransitionState(
+ transition = transition,
+ displayId = taskInfo.displayId,
+ taskId = taskInfo.taskId,
+ direction = Direction.ENTER
+ )
+ }
+
+ fun exitImmersive(taskInfo: RunningTaskInfo, wct: WindowContainerTransaction) {
+ if (inProgress) {
+ ProtoLog.v(
+ ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+ "$TAG: cannot start exit because transition already in progress."
+ )
+ return
+ }
+
+ val transition = transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ this)
+ state = TransitionState(
+ transition = transition,
+ displayId = taskInfo.displayId,
+ taskId = taskInfo.taskId,
+ direction = Direction.EXIT
+ )
+ }
+
+ override fun startAnimation(
+ transition: IBinder,
+ info: TransitionInfo,
+ startTransaction: SurfaceControl.Transaction,
+ finishTransaction: SurfaceControl.Transaction,
+ finishCallback: Transitions.TransitionFinishCallback
+ ): Boolean {
+ val state = requireState()
+ if (transition != state.transition) return false
+ animateResize(
+ transitionState = state,
+ info = info,
+ startTransaction = startTransaction,
+ finishTransaction = finishTransaction,
+ finishCallback = finishCallback
+ )
+ return true
+ }
+
+ private fun animateResize(
+ transitionState: TransitionState,
+ info: TransitionInfo,
+ startTransaction: SurfaceControl.Transaction,
+ finishTransaction: SurfaceControl.Transaction,
+ finishCallback: Transitions.TransitionFinishCallback
+ ) {
+ val change = info.changes.first { c ->
+ val taskInfo = c.taskInfo
+ return@first taskInfo != null && taskInfo.taskId == transitionState.taskId
+ }
+ val leash = change.leash
+ val startBounds = change.startAbsBounds
+ val endBounds = change.endAbsBounds
+
+ val updateTransaction = transactionSupplier()
+ ValueAnimator.ofObject(rectEvaluator, startBounds, endBounds).apply {
+ duration = FULL_IMMERSIVE_ANIM_DURATION_MS
+ interpolator = DecelerateInterpolator()
+ addListener(
+ onStart = {
+ startTransaction
+ .setPosition(leash, startBounds.left.toFloat(), startBounds.top.toFloat())
+ .setWindowCrop(leash, startBounds.width(), startBounds.height())
+ .show(leash)
+ onTaskResizeAnimationListener
+ ?.onAnimationStart(transitionState.taskId, startTransaction, startBounds)
+ ?: startTransaction.apply()
+ },
+ onEnd = {
+ finishTransaction
+ .setPosition(leash, endBounds.left.toFloat(), endBounds.top.toFloat())
+ .setWindowCrop(leash, endBounds.width(), endBounds.height())
+ .apply()
+ onTaskResizeAnimationListener?.onAnimationEnd(transitionState.taskId)
+ finishCallback.onTransitionFinished(null /* wct */)
+ clearState()
+ }
+ )
+ addUpdateListener { animation ->
+ val rect = animation.animatedValue as Rect
+ updateTransaction
+ .setPosition(leash, rect.left.toFloat(), rect.top.toFloat())
+ .setWindowCrop(leash, rect.width(), rect.height())
+ .apply()
+ onTaskResizeAnimationListener
+ ?.onBoundsChange(transitionState.taskId, updateTransaction, rect)
+ ?: updateTransaction.apply()
+ }
+ start()
+ }
+ }
+
+ override fun handleRequest(
+ transition: IBinder,
+ request: TransitionRequestInfo
+ ): WindowContainerTransaction? = null
+
+ override fun onTransitionConsumed(
+ transition: IBinder,
+ aborted: Boolean,
+ finishTransaction: SurfaceControl.Transaction?
+ ) {
+ val state = this.state ?: return
+ if (transition == state.transition && aborted) {
+ clearState()
+ }
+ super.onTransitionConsumed(transition, aborted, finishTransaction)
+ }
+
+ /**
+ * Called when any transition in the system is ready to play. This is needed to update the
+ * repository state before window decorations are drawn (which happens immediately after
+ * |onTransitionReady|, before this transition actually animates) because drawing decorations
+ * depends in whether the task is in full immersive state or not.
+ */
+ fun onTransitionReady(transition: IBinder) {
+ val state = this.state ?: return
+ // TODO: b/369443668 - this assumes invoking the exit transition is the only way to exit
+ // immersive, which isn't realistic. The app could crash, the user could dismiss it from
+ // overview, etc. This (or its caller) should search all transitions to look for any
+ // immersive task exiting that state to keep the repository properly updated.
+ if (transition == state.transition) {
+ when (state.direction) {
+ Direction.ENTER -> {
+ desktopRepository.setTaskInFullImmersiveState(
+ displayId = state.displayId,
+ taskId = state.taskId,
+ immersive = true
+ )
+ }
+ Direction.EXIT -> {
+ desktopRepository.setTaskInFullImmersiveState(
+ displayId = state.displayId,
+ taskId = state.taskId,
+ immersive = false
+ )
+ }
+ }
+ }
+ }
+
+ private fun clearState() {
+ state = null
+ }
+
+ private fun requireState(): TransitionState =
+ state ?: error("Expected non-null transition state")
+
+ /** The state of the currently running transition. */
+ private data class TransitionState(
+ val transition: IBinder,
+ val displayId: Int,
+ val taskId: Int,
+ val direction: Direction
+ )
+
+ private enum class Direction {
+ ENTER, EXIT
+ }
+
+ private companion object {
+ private const val TAG = "FullImmersiveHandler"
+
+ private const val FULL_IMMERSIVE_ANIM_DURATION_MS = 336L
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
index ec3f8c5..4350199 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
@@ -40,7 +40,7 @@
class DesktopMixedTransitionHandler(
private val context: Context,
private val transitions: Transitions,
- private val desktopTaskRepository: DesktopModeTaskRepository,
+ private val desktopRepository: DesktopRepository,
private val freeformTaskTransitionHandler: FreeformTaskTransitionHandler,
private val closeDesktopTaskTransitionHandler: CloseDesktopTaskTransitionHandler,
private val interactionJankMonitor: InteractionJankMonitor,
@@ -138,7 +138,7 @@
private fun isLastDesktopTask(change: TransitionInfo.Change): Boolean =
change.taskInfo?.let {
- desktopTaskRepository.getActiveNonMinimizedTaskCount(it.displayId) == 1
+ desktopRepository.getActiveNonMinimizedTaskCount(it.displayId) == 1
} ?: false
private fun findCloseDesktopTaskChange(info: TransitionInfo): TransitionInfo.Change? {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
index cca7500..73e55b2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
@@ -36,7 +36,7 @@
* @param listener the listener to add.
* @param callbackExecutor the executor to call the listener on.
*/
- void addVisibleTasksListener(DesktopModeTaskRepository.VisibleTasksListener listener,
+ void addVisibleTasksListener(DesktopRepository.VisibleTasksListener listener,
Executor callbackExecutor);
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
index 7261919..52b92a8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
@@ -21,6 +21,12 @@
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR;
+import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR;
+import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR;
+import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR;
+import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.RectEvaluator;
@@ -125,7 +131,7 @@
mContext = context;
mTaskSurface = taskSurface;
mRootTdaOrganizer = taskDisplayAreaOrganizer;
- mCurrentType = IndicatorType.NO_INDICATOR;
+ mCurrentType = NO_INDICATOR;
mDragStartState = dragStartState;
}
@@ -136,8 +142,16 @@
@NonNull
IndicatorType updateIndicatorType(PointF inputCoordinates) {
final DisplayLayout layout = mDisplayController.getDisplayLayout(mTaskInfo.displayId);
+ // Perform a quick check first: any input off the left edge of the display should be split
+ // left, and split right for the right edge. This is universal across all drag event types.
+ if (inputCoordinates.x < 0) return TO_SPLIT_LEFT_INDICATOR;
+ if (inputCoordinates.x > layout.width()) return TO_SPLIT_RIGHT_INDICATOR;
// If we are in freeform, we don't want a visible indicator in the "freeform" drag zone.
- IndicatorType result = IndicatorType.NO_INDICATOR;
+ // In drags not originating on a freeform caption, we should default to a TO_DESKTOP
+ // indicator.
+ IndicatorType result = mDragStartState == DragStartState.FROM_FREEFORM
+ ? NO_INDICATOR
+ : TO_DESKTOP_INDICATOR;
final int transitionAreaWidth = mContext.getResources().getDimensionPixelSize(
com.android.wm.shell.R.dimen.desktop_mode_transition_region_thickness);
// Because drags in freeform use task position for indicator calculation, we need to
@@ -149,10 +163,8 @@
captionHeight);
final Region splitRightRegion = calculateSplitRightRegion(layout, transitionAreaWidth,
captionHeight);
- final Region toDesktopRegion = calculateToDesktopRegion(layout, splitLeftRegion,
- splitRightRegion, fullscreenRegion);
if (fullscreenRegion.contains((int) inputCoordinates.x, (int) inputCoordinates.y)) {
- result = IndicatorType.TO_FULLSCREEN_INDICATOR;
+ result = TO_FULLSCREEN_INDICATOR;
}
if (splitLeftRegion.contains((int) inputCoordinates.x, (int) inputCoordinates.y)) {
result = IndicatorType.TO_SPLIT_LEFT_INDICATOR;
@@ -160,9 +172,6 @@
if (splitRightRegion.contains((int) inputCoordinates.x, (int) inputCoordinates.y)) {
result = IndicatorType.TO_SPLIT_RIGHT_INDICATOR;
}
- if (toDesktopRegion.contains((int) inputCoordinates.x, (int) inputCoordinates.y)) {
- result = IndicatorType.TO_DESKTOP_INDICATOR;
- }
if (mDragStartState != DragStartState.DRAGGED_INTENT) {
transitionIndicator(result);
}
@@ -182,7 +191,7 @@
R.dimen.desktop_mode_fullscreen_region_scale);
final float toFullscreenWidth = (layout.width() * toFullscreenScale);
region.union(new Rect((int) ((layout.width() / 2f) - (toFullscreenWidth / 2f)),
- -captionHeight,
+ Short.MIN_VALUE,
(int) ((layout.width() / 2f) + (toFullscreenWidth / 2f)),
transitionHeight));
}
@@ -192,7 +201,7 @@
|| mDragStartState == DragStartState.DRAGGED_INTENT
) {
region.union(new Rect(0,
- -captionHeight,
+ Short.MIN_VALUE,
layout.width(),
transitionHeight));
}
@@ -200,21 +209,6 @@
}
@VisibleForTesting
- Region calculateToDesktopRegion(DisplayLayout layout,
- Region splitLeftRegion, Region splitRightRegion,
- Region toFullscreenRegion) {
- final Region region = new Region();
- // If in desktop, we need no region. Otherwise it's the same for all windowing modes.
- if (mDragStartState != DragStartState.FROM_FREEFORM) {
- region.union(new Rect(0, 0, layout.width(), layout.height()));
- region.op(splitLeftRegion, Region.Op.DIFFERENCE);
- region.op(splitRightRegion, Region.Op.DIFFERENCE);
- region.op(toFullscreenRegion, Region.Op.DIFFERENCE);
- }
- return region;
- }
-
- @VisibleForTesting
Region calculateSplitLeftRegion(DisplayLayout layout,
int transitionEdgeWidth, int captionHeight) {
final Region region = new Region();
@@ -311,7 +305,7 @@
}
});
}
- mCurrentType = IndicatorType.NO_INDICATOR;
+ mCurrentType = NO_INDICATOR;
}
/**
@@ -322,9 +316,9 @@
if (mView == null) {
createView();
}
- if (mCurrentType == IndicatorType.NO_INDICATOR) {
+ if (mCurrentType == NO_INDICATOR) {
fadeInIndicator(newType);
- } else if (newType == IndicatorType.NO_INDICATOR) {
+ } else if (newType == NO_INDICATOR) {
fadeOutIndicator(null /* finishCallback */);
} else {
final VisualIndicatorAnimator animator = VisualIndicatorAnimator.animateIndicatorType(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
similarity index 98%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
rename to libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
index 985224e..7b2a5d3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
@@ -42,8 +42,8 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
-/** Tracks task data for Desktop Mode. */
-class DesktopModeTaskRepository (
+/** Tracks desktop data for Android Desktop Windowing. */
+class DesktopRepository (
private val context: Context,
shellInit: ShellInit,
private val persistentRepository: DesktopPersistentRepository,
@@ -467,10 +467,9 @@
}
}
-
internal fun dump(pw: PrintWriter, prefix: String) {
val innerPrefix = "$prefix "
- pw.println("${prefix}DesktopModeTaskRepository")
+ pw.println("${prefix}DesktopRepository")
dumpDesktopTaskData(pw, innerPrefix)
pw.println("${innerPrefix}activeTasksListeners=${activeTasksListeners.size}")
pw.println("${innerPrefix}visibleTasksListeners=${visibleTasksListeners.size}")
@@ -512,10 +511,9 @@
}
companion object {
- private const val TAG = "DesktopModeTaskRepository"
+ private const val TAG = "DesktopRepository"
}
}
private fun <T> Iterable<T>.toDumpString(): String =
joinToString(separator = ", ", prefix = "[", postfix = "]")
-
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListener.kt
new file mode 100644
index 0000000..1ee2de9
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListener.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode
+
+import android.app.ActivityManager.RunningTaskInfo
+import com.android.wm.shell.freeform.TaskChangeListener
+
+/** Manages tasks handling specific to Android Desktop Mode. */
+class DesktopTaskChangeListener: TaskChangeListener {
+
+ override fun onTaskOpening(taskInfo: RunningTaskInfo) {
+ // TODO: b/367268953 - Connect this with DesktopRepository.
+ }
+
+ override fun onTaskChanging(taskInfo: RunningTaskInfo) {
+ // TODO: b/367268953 - Connect this with DesktopRepository.
+ }
+
+ override fun onTaskMovingToFront(taskInfo: RunningTaskInfo) {
+ // TODO: b/367268953 - Connect this with DesktopRepository.
+ }
+
+ override fun onTaskMovingToBack(taskInfo: RunningTaskInfo) {
+ // TODO: b/367268953 - Connect this with DesktopRepository.
+ }
+
+ override fun onTaskClosing(taskInfo: RunningTaskInfo) {
+ // TODO: b/367268953 - Connect this with DesktopRepository.
+ }
+}
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 fcd2f8c..5b9d2fe 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
@@ -73,7 +73,7 @@
import com.android.wm.shell.common.SingleInstanceRemoteListener
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.compatui.isTopActivityExemptFromDesktopWindowing
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener
+import com.android.wm.shell.desktopmode.DesktopRepository.VisibleTasksListener
import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.DragStartState
import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType
import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.DragToDesktopStateListener
@@ -110,6 +110,7 @@
import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener
import com.android.wm.shell.windowdecor.extension.isFullscreen
import com.android.wm.shell.windowdecor.extension.isMultiWindow
+import com.android.wm.shell.windowdecor.extension.requestingImmersive
import java.io.PrintWriter
import java.util.Optional
import java.util.concurrent.Executor
@@ -134,7 +135,8 @@
private val desktopModeDragAndDropTransitionHandler: DesktopModeDragAndDropTransitionHandler,
private val toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler,
private val dragToDesktopTransitionHandler: DragToDesktopTransitionHandler,
- private val taskRepository: DesktopModeTaskRepository,
+ private val immersiveTransitionHandler: DesktopFullImmersiveTransitionHandler,
+ private val taskRepository: DesktopRepository,
private val desktopModeLoggerTransitionObserver: DesktopModeLoggerTransitionObserver,
private val launchAdjacentController: LaunchAdjacentController,
private val recentsTransitionHandler: RecentsTransitionHandler,
@@ -231,6 +233,7 @@
toggleResizeDesktopTaskTransitionHandler.setOnTaskResizeAnimationListener(listener)
enterDesktopTaskTransitionHandler.setOnTaskResizeAnimationListener(listener)
dragToDesktopTransitionHandler.onTaskResizeAnimationListener = listener
+ immersiveTransitionHandler.onTaskResizeAnimationListener = listener
}
fun setOnTaskRepositionAnimationListener(listener: OnTaskRepositionAnimationListener) {
@@ -649,6 +652,35 @@
}
}
+ /** Moves a task in/out of full immersive state within the desktop. */
+ fun toggleDesktopTaskFullImmersiveState(taskInfo: RunningTaskInfo) {
+ if (taskRepository.isTaskInFullImmersiveState(taskInfo.taskId)) {
+ exitDesktopTaskFromFullImmersive(taskInfo)
+ } else {
+ moveDesktopTaskToFullImmersive(taskInfo)
+ }
+ }
+
+ private fun moveDesktopTaskToFullImmersive(taskInfo: RunningTaskInfo) {
+ check(taskInfo.isFreeform) { "Task must already be in freeform" }
+ val wct = WindowContainerTransaction().apply {
+ setBounds(taskInfo.token, Rect())
+ }
+ immersiveTransitionHandler.enterImmersive(taskInfo, wct)
+ }
+
+ private fun exitDesktopTaskFromFullImmersive(taskInfo: RunningTaskInfo) {
+ check(taskInfo.isFreeform) { "Task must already be in freeform" }
+ val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
+ val stableBounds = Rect().apply { displayLayout.getStableBounds(this) }
+ val destinationBounds = getMaximizeBounds(taskInfo, stableBounds)
+
+ val wct = WindowContainerTransaction().apply {
+ setBounds(taskInfo.token, destinationBounds)
+ }
+ immersiveTransitionHandler.exitImmersive(taskInfo, wct)
+ }
+
/**
* Quick-resizes a desktop task, toggling between a fullscreen state (represented by the stable
* bounds) and a free floating state (either the last saved bounds if available or the default
@@ -685,18 +717,7 @@
// and toggle to the stable bounds.
taskRepository.saveBoundsBeforeMaximize(taskInfo.taskId, currentTaskBounds)
- if (taskInfo.isResizeable) {
- // if resizable then expand to entire stable bounds (full display minus insets)
- destinationBounds.set(stableBounds)
- } else {
- // if non-resizable then calculate max bounds according to aspect ratio
- val activityAspectRatio = calculateAspectRatio(taskInfo)
- val newSize = maximizeSizeGivenAspectRatio(taskInfo,
- Size(stableBounds.width(), stableBounds.height()), activityAspectRatio)
- val newBounds = centerInArea(
- newSize, stableBounds, stableBounds.left, stableBounds.top)
- destinationBounds.set(newBounds)
- }
+ destinationBounds.set(getMaximizeBounds(taskInfo, stableBounds))
}
@@ -719,6 +740,20 @@
}
}
+ private fun getMaximizeBounds(taskInfo: RunningTaskInfo, stableBounds: Rect): Rect {
+ if (taskInfo.isResizeable) {
+ // if resizable then expand to entire stable bounds (full display minus insets)
+ return Rect(stableBounds)
+ } else {
+ // if non-resizable then calculate max bounds according to aspect ratio
+ val activityAspectRatio = calculateAspectRatio(taskInfo)
+ val newSize = maximizeSizeGivenAspectRatio(taskInfo,
+ Size(stableBounds.width(), stableBounds.height()), activityAspectRatio)
+ return centerInArea(
+ newSize, stableBounds, stableBounds.left, stableBounds.top)
+ }
+ }
+
private fun isTaskMaximized(
taskInfo: RunningTaskInfo,
stableBounds: Rect
@@ -1594,7 +1629,7 @@
return
}
- val indicator = visualIndicator ?: return
+ val indicator = getVisualIndicator() ?: return
val indicatorType =
indicator.updateIndicatorType(
PointF(inputCoordinate.x, currentDragBounds.top.toFloat()),
@@ -1810,6 +1845,17 @@
userId = newUserId
}
+ /** Called when a task's info changes. */
+ fun onTaskInfoChanged(taskInfo: RunningTaskInfo) {
+ if (!Flags.enableFullyImmersiveInDesktop()) return
+ val inImmersive = taskRepository.isTaskInFullImmersiveState(taskInfo.taskId)
+ val requestingImmersive = taskInfo.requestingImmersive
+ if (inImmersive && !requestingImmersive) {
+ // Exit immersive if the app is no longer requesting it.
+ exitDesktopTaskFromFullImmersive(taskInfo)
+ }
+ }
+
private fun dump(pw: PrintWriter, prefix: String) {
val innerPrefix = "$prefix "
pw.println("${prefix}DesktopTasksController")
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
index 37ad0c9..37bec21 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
@@ -44,7 +44,7 @@
*/
class DesktopTasksLimiter (
transitions: Transitions,
- private val taskRepository: DesktopModeTaskRepository,
+ private val taskRepository: DesktopRepository,
private val shellTaskOrganizer: ShellTaskOrganizer,
private val maxTasksLimit: Int,
private val interactionJankMonitor: InteractionJankMonitor,
@@ -160,7 +160,7 @@
}
@VisibleForTesting
- inner class LeftoverMinimizedTasksRemover : DesktopModeTaskRepository.ActiveTasksListener {
+ inner class LeftoverMinimizedTasksRemover : DesktopRepository.ActiveTasksListener {
override fun onActiveTasksChanged(displayId: Int) {
// If back navigation is enabled, we shouldn't remove the leftover tasks
if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) return
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
index b20c9fc..e086e40 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
@@ -36,12 +36,12 @@
/**
* A [Transitions.TransitionObserver] that observes shell transitions and updates the
- * [DesktopModeTaskRepository] state TODO: b/332682201 This observes transitions related to desktop
+ * [DesktopRepository] state TODO: b/332682201 This observes transitions related to desktop
* mode and other transitions that originate both within and outside shell.
*/
class DesktopTasksTransitionObserver(
private val context: Context,
- private val desktopModeTaskRepository: DesktopModeTaskRepository,
+ private val desktopRepository: DesktopRepository,
private val transitions: Transitions,
private val shellTaskOrganizer: ShellTaskOrganizer,
shellInit: ShellInit
@@ -83,10 +83,10 @@
val taskInfo = change.taskInfo
if (taskInfo == null || taskInfo.taskId == -1) continue
- if (desktopModeTaskRepository.isActiveTask(taskInfo.taskId)
+ if (desktopRepository.isActiveTask(taskInfo.taskId)
&& taskInfo.windowingMode != WINDOWING_MODE_FREEFORM
) {
- desktopModeTaskRepository.removeFreeformTask(
+ desktopRepository.removeFreeformTask(
taskInfo.displayId,
taskInfo.taskId
)
@@ -104,11 +104,11 @@
continue
}
- if (desktopModeTaskRepository.getVisibleTaskCount(taskInfo.displayId) > 0 &&
+ if (desktopRepository.getVisibleTaskCount(taskInfo.displayId) > 0 &&
change.mode == TRANSIT_TO_BACK &&
taskInfo.windowingMode == WINDOWING_MODE_FREEFORM
) {
- desktopModeTaskRepository.minimizeTask(taskInfo.displayId, taskInfo.taskId)
+ desktopRepository.minimizeTask(taskInfo.displayId, taskInfo.taskId)
}
}
}
@@ -135,7 +135,7 @@
if (DesktopWallpaperActivity.isWallpaperTask(taskInfo)) {
when (change.mode) {
WindowManager.TRANSIT_OPEN -> {
- desktopModeTaskRepository.wallpaperActivityToken = taskInfo.token
+ desktopRepository.wallpaperActivityToken = taskInfo.token
// After the task for the wallpaper is created, set it non-trimmable.
// This is important to prevent recents from trimming and removing the
// task.
@@ -145,7 +145,7 @@
)
}
WindowManager.TRANSIT_CLOSE ->
- desktopModeTaskRepository.wallpaperActivityToken = null
+ desktopRepository.wallpaperActivityToken = null
else -> {}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt
index 68a250d..334dc5a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt
@@ -23,6 +23,7 @@
import android.graphics.Point
import android.os.SystemProperties
import android.util.Slog
+import androidx.core.content.withStyledAttributes
import com.android.window.flags.Flags
import com.android.wm.shell.R
import com.android.wm.shell.desktopmode.CaptionState
@@ -32,8 +33,11 @@
import com.android.wm.shell.shared.annotations.ShellMainThread
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.canEnterDesktopMode
import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource
+import com.android.wm.shell.windowdecor.common.DecorThemeUtil
+import com.android.wm.shell.windowdecor.common.Theme
import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationTooltipController
import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationTooltipController.EducationViewConfig
+import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationTooltipController.TooltipColorScheme
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.MainCoroutineDispatcher
@@ -70,6 +74,7 @@
@ShellMainThread private val applicationCoroutineScope: CoroutineScope,
@ShellBackgroundThread private val backgroundDispatcher: MainCoroutineDispatcher,
) {
+ private val decorThemeUtil = DecorThemeUtil(context)
private lateinit var openHandleMenuCallback: (Int) -> Unit
private lateinit var toDesktopModeCallback: (Int, DesktopModeTransitionSource) -> Unit
@@ -97,7 +102,9 @@
}
.flowOn(backgroundDispatcher)
.collectLatest { captionState ->
- showEducation(captionState)
+ val tooltipColorScheme = tooltipColorScheme(captionState)
+
+ showEducation(captionState, tooltipColorScheme)
// After showing first tooltip, mark education as viewed
appHandleEducationDatastoreRepository.updateEducationViewedTimestampMillis(true)
}
@@ -123,7 +130,7 @@
if (canEnterDesktopMode(context) && Flags.enableDesktopWindowingAppHandleEducation()) block()
}
- private fun showEducation(captionState: CaptionState) {
+ private fun showEducation(captionState: CaptionState, tooltipColorScheme: TooltipColorScheme) {
val appHandleBounds = (captionState as CaptionState.AppHandle).globalAppHandleBounds
val tooltipGlobalCoordinates =
Point(appHandleBounds.left + appHandleBounds.width() / 2, appHandleBounds.bottom)
@@ -132,14 +139,17 @@
val appHandleTooltipConfig =
EducationViewConfig(
tooltipViewLayout = R.layout.desktop_windowing_education_top_arrow_tooltip,
+ tooltipColorScheme = tooltipColorScheme,
tooltipViewGlobalCoordinates = tooltipGlobalCoordinates,
tooltipText = getString(R.string.windowing_app_handle_education_tooltip),
arrowDirection = DesktopWindowingEducationTooltipController.TooltipArrowDirection.UP,
onEducationClickAction = {
- launchWithExceptionHandling { showWindowingImageButtonTooltip() }
+ launchWithExceptionHandling { showWindowingImageButtonTooltip(tooltipColorScheme) }
openHandleMenuCallback(captionState.runningTaskInfo.taskId)
},
- onDismissAction = { launchWithExceptionHandling { showWindowingImageButtonTooltip() } },
+ onDismissAction = {
+ launchWithExceptionHandling { showWindowingImageButtonTooltip(tooltipColorScheme) }
+ },
)
windowingEducationViewController.showEducationTooltip(
@@ -147,7 +157,7 @@
}
/** Show tooltip that points to windowing image button in app handle menu */
- private suspend fun showWindowingImageButtonTooltip() {
+ private suspend fun showWindowingImageButtonTooltip(tooltipColorScheme: TooltipColorScheme) {
val appInfoPillHeight = getSize(R.dimen.desktop_mode_handle_menu_app_info_pill_height)
val windowingOptionPillHeight = getSize(R.dimen.desktop_mode_handle_menu_windowing_pill_height)
val appHandleMenuWidth =
@@ -188,18 +198,21 @@
val windowingImageButtonTooltipConfig =
EducationViewConfig(
tooltipViewLayout = R.layout.desktop_windowing_education_left_arrow_tooltip,
+ tooltipColorScheme = tooltipColorScheme,
tooltipViewGlobalCoordinates = tooltipGlobalCoordinates,
tooltipText =
getString(R.string.windowing_desktop_mode_image_button_education_tooltip),
arrowDirection =
DesktopWindowingEducationTooltipController.TooltipArrowDirection.LEFT,
onEducationClickAction = {
- launchWithExceptionHandling { showExitWindowingTooltip() }
+ launchWithExceptionHandling { showExitWindowingTooltip(tooltipColorScheme) }
toDesktopModeCallback(
captionState.runningTaskInfo.taskId,
DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON)
},
- onDismissAction = { launchWithExceptionHandling { showExitWindowingTooltip() } },
+ onDismissAction = {
+ launchWithExceptionHandling { showExitWindowingTooltip(tooltipColorScheme) }
+ },
)
windowingEducationViewController.showEducationTooltip(
@@ -209,7 +222,7 @@
}
/** Show tooltip that points to app chip button and educates user on how to exit desktop mode */
- private suspend fun showExitWindowingTooltip() {
+ private suspend fun showExitWindowingTooltip(tooltipColorScheme: TooltipColorScheme) {
windowDecorCaptionHandleRepository.captionStateFlow
// After the previous tooltip was dismissed, wait for 400 ms and see if the user entered
// desktop mode.
@@ -238,6 +251,7 @@
val exitWindowingTooltipConfig =
EducationViewConfig(
tooltipViewLayout = R.layout.desktop_windowing_education_left_arrow_tooltip,
+ tooltipColorScheme = tooltipColorScheme,
tooltipViewGlobalCoordinates = tooltipGlobalCoordinates,
tooltipText = getString(R.string.windowing_desktop_mode_exit_education_tooltip),
arrowDirection =
@@ -254,6 +268,32 @@
}
}
+ private fun tooltipColorScheme(captionState: CaptionState): TooltipColorScheme {
+ context.withStyledAttributes(
+ set = null,
+ attrs =
+ intArrayOf(
+ com.android.internal.R.attr.materialColorOnTertiaryFixed,
+ com.android.internal.R.attr.materialColorTertiaryFixed,
+ com.android.internal.R.attr.materialColorTertiaryFixedDim),
+ defStyleAttr = 0,
+ defStyleRes = 0) {
+ val onTertiaryFixed = getColor(/* index= */ 0, /* defValue= */ 0)
+ val tertiaryFixed = getColor(/* index= */ 1, /* defValue= */ 0)
+ val tertiaryFixedDim = getColor(/* index= */ 2, /* defValue= */ 0)
+ val taskInfo = (captionState as CaptionState.AppHandle).runningTaskInfo
+
+ val tooltipContainerColor =
+ if (decorThemeUtil.getAppTheme(taskInfo) == Theme.LIGHT) {
+ tertiaryFixed
+ } else {
+ tertiaryFixedDim
+ }
+ return TooltipColorScheme(tooltipContainerColor, onTertiaryFixed, onTertiaryFixed)
+ }
+ return TooltipColorScheme(0, 0, 0)
+ }
+
/**
* Setup callbacks for app handle education tooltips.
*
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
index 7f7f105..fbd3c10 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -29,7 +29,8 @@
import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.LaunchAdjacentController;
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.desktopmode.DesktopRepository;
+import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.sysui.ShellInit;
@@ -49,7 +50,8 @@
private final Context mContext;
private final ShellTaskOrganizer mShellTaskOrganizer;
- private final Optional<DesktopModeTaskRepository> mDesktopModeTaskRepository;
+ private final Optional<DesktopRepository> mDesktopRepository;
+ private final Optional<DesktopTasksController> mDesktopTasksController;
private final WindowDecorViewModel mWindowDecorationViewModel;
private final LaunchAdjacentController mLaunchAdjacentController;
@@ -64,13 +66,15 @@
Context context,
ShellInit shellInit,
ShellTaskOrganizer shellTaskOrganizer,
- Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
+ Optional<DesktopRepository> desktopRepository,
+ Optional<DesktopTasksController> desktopTasksController,
LaunchAdjacentController launchAdjacentController,
WindowDecorViewModel windowDecorationViewModel) {
mContext = context;
mShellTaskOrganizer = shellTaskOrganizer;
mWindowDecorationViewModel = windowDecorationViewModel;
- mDesktopModeTaskRepository = desktopModeTaskRepository;
+ mDesktopRepository = desktopRepository;
+ mDesktopTasksController = desktopTasksController;
mLaunchAdjacentController = launchAdjacentController;
if (shellInit != null) {
shellInit.addInitCallback(this::onInit, this);
@@ -102,7 +106,7 @@
}
if (DesktopModeStatus.canEnterDesktopMode(mContext)) {
- mDesktopModeTaskRepository.ifPresent(repository -> {
+ mDesktopRepository.ifPresent(repository -> {
repository.addOrMoveFreeformTaskToTop(taskInfo.displayId, taskInfo.taskId);
if (taskInfo.isVisible) {
repository.addActiveTask(taskInfo.displayId, taskInfo.taskId);
@@ -121,7 +125,7 @@
mTasks.remove(taskInfo.taskId);
if (DesktopModeStatus.canEnterDesktopMode(mContext)) {
- mDesktopModeTaskRepository.ifPresent(repository -> {
+ mDesktopRepository.ifPresent(repository -> {
// TODO: b/370038902 - Handle Activity#finishAndRemoveTask.
if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()
|| repository.isClosingTask(taskInfo.taskId)) {
@@ -147,10 +151,11 @@
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Freeform Task Info Changed: #%d",
taskInfo.taskId);
+ mDesktopTasksController.ifPresent(c -> c.onTaskInfoChanged(taskInfo));
mWindowDecorationViewModel.onTaskInfoChanged(taskInfo);
state.mTaskInfo = taskInfo;
if (DesktopModeStatus.canEnterDesktopMode(mContext)) {
- mDesktopModeTaskRepository.ifPresent(repository -> {
+ mDesktopRepository.ifPresent(repository -> {
if (taskInfo.isVisible) {
repository.addActiveTask(taskInfo.displayId, taskInfo.taskId);
} else if (repository.isClosingTask(taskInfo.taskId)) {
@@ -182,7 +187,7 @@
"Freeform Task Focus Changed: #%d focused=%b",
taskInfo.taskId, taskInfo.isFocused);
if (DesktopModeStatus.canEnterDesktopMode(mContext) && taskInfo.isFocused) {
- mDesktopModeTaskRepository.ifPresent(repository -> {
+ mDesktopRepository.ifPresent(repository -> {
repository.addOrMoveFreeformTaskToTop(taskInfo.displayId, taskInfo.taskId);
});
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
index ffcc526..056f6b9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
@@ -27,6 +27,8 @@
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
+import com.android.window.flags.Flags;
+import com.android.wm.shell.desktopmode.DesktopFullImmersiveTransitionHandler;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
@@ -36,6 +38,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
/**
* The {@link Transitions.TransitionHandler} that handles freeform task launches, closes,
@@ -44,7 +47,9 @@
*/
public class FreeformTaskTransitionObserver implements Transitions.TransitionObserver {
private final Transitions mTransitions;
+ private final Optional<DesktopFullImmersiveTransitionHandler> mImmersiveTransitionHandler;
private final WindowDecorViewModel mWindowDecorViewModel;
+ private final Optional<TaskChangeListener> mTaskChangeListener;
private final Map<IBinder, List<ActivityManager.RunningTaskInfo>> mTransitionToTaskInfo =
new HashMap<>();
@@ -53,9 +58,13 @@
Context context,
ShellInit shellInit,
Transitions transitions,
- WindowDecorViewModel windowDecorViewModel) {
+ Optional<DesktopFullImmersiveTransitionHandler> immersiveTransitionHandler,
+ WindowDecorViewModel windowDecorViewModel,
+ Optional<TaskChangeListener> taskChangeListener) {
mTransitions = transitions;
+ mImmersiveTransitionHandler = immersiveTransitionHandler;
mWindowDecorViewModel = windowDecorViewModel;
+ mTaskChangeListener = taskChangeListener;
if (Transitions.ENABLE_SHELL_TRANSITIONS && FreeformComponents.isFreeformEnabled(context)) {
shellInit.addInitCallback(this::onInit, this);
}
@@ -72,6 +81,13 @@
@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startT,
@NonNull SurfaceControl.Transaction finishT) {
+ if (Flags.enableFullyImmersiveInDesktop()) {
+ // TODO(b/367268953): Remove when DesktopTaskListener is introduced and the repository
+ // is updated from there **before** the |mWindowDecorViewModel| methods are invoked.
+ // Otherwise window decoration relayout won't run with the immersive state up to date.
+ mImmersiveTransitionHandler.ifPresent(h -> h.onTransitionReady(transition));
+ }
+
final ArrayList<ActivityManager.RunningTaskInfo> taskInfoList = new ArrayList<>();
final ArrayList<WindowContainerToken> taskParents = new ArrayList<>();
for (TransitionInfo.Change change : info.getChanges()) {
@@ -103,6 +119,9 @@
case WindowManager.TRANSIT_TO_FRONT:
onToFrontTransitionReady(change, startT, finishT);
break;
+ case WindowManager.TRANSIT_TO_BACK:
+ onToBackTransitionReady(change, startT, finishT);
+ break;
case WindowManager.TRANSIT_CLOSE: {
taskInfoList.add(change.getTaskInfo());
onCloseTransitionReady(change, startT, finishT);
@@ -120,29 +139,49 @@
TransitionInfo.Change change,
SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT) {
+ mTaskChangeListener.ifPresent(
+ listener -> listener.onTaskOpening(change.getTaskInfo()));
mWindowDecorViewModel.onTaskOpening(
- change.getTaskInfo(), change.getLeash(), startT, finishT);
+ change.getTaskInfo(), change.getLeash(), startT, finishT);
}
private void onCloseTransitionReady(
TransitionInfo.Change change,
SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT) {
+ mTaskChangeListener.ifPresent(
+ listener -> listener.onTaskClosing(change.getTaskInfo()));
mWindowDecorViewModel.onTaskClosing(change.getTaskInfo(), startT, finishT);
+
}
private void onChangeTransitionReady(
TransitionInfo.Change change,
SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT) {
+ mTaskChangeListener.ifPresent(listener ->
+ listener.onTaskChanging(change.getTaskInfo()));
mWindowDecorViewModel.onTaskChanging(
change.getTaskInfo(), change.getLeash(), startT, finishT);
}
+
private void onToFrontTransitionReady(
TransitionInfo.Change change,
SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT) {
+ mTaskChangeListener.ifPresent(
+ listener -> listener.onTaskMovingToFront(change.getTaskInfo()));
+ mWindowDecorViewModel.onTaskChanging(
+ change.getTaskInfo(), change.getLeash(), startT, finishT);
+ }
+
+ private void onToBackTransitionReady(
+ TransitionInfo.Change change,
+ SurfaceControl.Transaction startT,
+ SurfaceControl.Transaction finishT) {
+ mTaskChangeListener.ifPresent(
+ listener -> listener.onTaskMovingToBack(change.getTaskInfo()));
mWindowDecorViewModel.onTaskChanging(
change.getTaskInfo(), change.getLeash(), startT, finishT);
}
@@ -179,4 +218,4 @@
mWindowDecorViewModel.destroyWindowDecoration(taskInfo.get(i));
}
}
-}
\ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/TaskChangeListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/TaskChangeListener.kt
new file mode 100644
index 0000000..f07c069
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/TaskChangeListener.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.freeform
+
+import android.app.ActivityManager.RunningTaskInfo;
+
+/**
+ * Interface used by [FreeformTaskTransitionObserver] to manage freeform tasks.
+ *
+ * The implementations are responsible for handle all the task management.
+ */
+interface TaskChangeListener {
+ /** Notifies a task opening in freeform mode. */
+ fun onTaskOpening(taskInfo: RunningTaskInfo)
+
+ /** Notifies a task info update on the given task. */
+ fun onTaskChanging(taskInfo: RunningTaskInfo)
+
+ /** Notifies a task moving to the front. */
+ fun onTaskMovingToFront(taskInfo: RunningTaskInfo)
+
+ /** Notifies a task moving to the back. */
+ fun onTaskMovingToBack(taskInfo: RunningTaskInfo)
+
+ /** Notifies a task is closing. */
+ fun onTaskClosing(taskInfo: RunningTaskInfo)
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java
index 9cfe162..8c1e5e6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java
@@ -18,10 +18,12 @@
import static android.view.WindowManager.SHELL_ROOT_LAYER_PIP;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.RemoteAction;
import android.content.Context;
import android.graphics.Rect;
+import android.os.Bundle;
import android.os.Debug;
import android.os.Handler;
import android.os.RemoteException;
@@ -52,7 +54,8 @@
* The current media session provides actions whenever there are no valid actions provided by the
* current PiP activity. Otherwise, those actions always take precedence.
*/
-public class PhonePipMenuController implements PipMenuController {
+public class PhonePipMenuController implements PipMenuController,
+ PipTransitionState.PipTransitionStateChangedListener {
private static final String TAG = "PhonePipMenuController";
private static final boolean DEBUG = false;
@@ -113,6 +116,11 @@
private PipMenuView mPipMenuView;
+ private final PipTaskListener mPipTaskListener;
+
+ @NonNull
+ private final PipTransitionState mPipTransitionState;
+
private SurfaceControl mLeash;
private ActionListener mMediaActionListener = new ActionListener() {
@@ -125,15 +133,27 @@
public PhonePipMenuController(Context context, PipBoundsState pipBoundsState,
PipMediaController mediaController, SystemWindows systemWindows,
- PipUiEventLogger pipUiEventLogger,
- ShellExecutor mainExecutor, Handler mainHandler) {
+ PipUiEventLogger pipUiEventLogger, PipTaskListener pipTaskListener,
+ @NonNull PipTransitionState pipTransitionState, ShellExecutor mainExecutor,
+ Handler mainHandler) {
mContext = context;
mPipBoundsState = pipBoundsState;
mMediaController = mediaController;
mSystemWindows = systemWindows;
+ mPipTaskListener = pipTaskListener;
+ mPipTransitionState = pipTransitionState;
mMainExecutor = mainExecutor;
mMainHandler = mainHandler;
mPipUiEventLogger = pipUiEventLogger;
+
+ mPipTransitionState.addPipTransitionStateChangedListener(this);
+
+ mPipTaskListener.addParamsChangedListener(new PipTaskListener.PipParamsChangedCallback() {
+ @Override
+ public void onActionsChanged(List<RemoteAction> actions, RemoteAction closeAction) {
+ setAppActions(actions, closeAction);
+ }
+ });
}
public boolean isMenuVisible() {
@@ -438,8 +458,7 @@
* Sets the menu actions to the actions provided by the current PiP menu.
*/
@Override
- public void setAppActions(List<RemoteAction> appActions,
- RemoteAction closeAction) {
+ public void setAppActions(List<RemoteAction> appActions, RemoteAction closeAction) {
mAppActions = appActions;
mCloseAction = closeAction;
updateMenuActions();
@@ -468,8 +487,8 @@
*/
private void updateMenuActions() {
if (mPipMenuView != null) {
- mPipMenuView.setActions(mPipBoundsState.getBounds(),
- resolveMenuActions(), mCloseAction);
+ mPipMenuView.setActions(mPipBoundsState.getBounds(), resolveMenuActions(),
+ mCloseAction);
}
}
@@ -567,6 +586,29 @@
}
}
+ @Override
+ public void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState,
+ @PipTransitionState.TransitionState int newState, Bundle extra) {
+ switch (newState) {
+ case PipTransitionState.ENTERED_PIP:
+ attach(mPipTransitionState.mPinnedTaskLeash);
+ break;
+ case PipTransitionState.EXITED_PIP:
+ detach();
+ break;
+ case PipTransitionState.CHANGED_PIP_BOUNDS:
+ updateMenuLayout(mPipBoundsState.getBounds());
+ hideMenu();
+ break;
+ case PipTransitionState.CHANGING_PIP_BOUNDS:
+ hideMenu();
+ break;
+ case PipTransitionState.SCHEDULED_BOUNDS_CHANGE:
+ hideMenu();
+ break;
+ }
+ }
+
void dump(PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
pw.println(prefix + TAG);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
index e9c4c14..73be8db 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -324,10 +324,16 @@
int launcherRotation, Rect hotseatKeepClearArea) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"getSwipePipToHomeBounds: %s", componentName);
- // preemptively add the keep clear area for Hotseat, so that it is taken into account
- // when calculating the entry destination bounds of PiP window
+ // Preemptively add the keep clear area for Hotseat, so that it is taken into account
+ // when calculating the entry destination bounds of PiP window.
mPipBoundsState.setNamedUnrestrictedKeepClearArea(
PipBoundsState.NAMED_KCA_LAUNCHER_SHELF, hotseatKeepClearArea);
+
+ // Set the display layout rotation early to calculate final orientation bounds that
+ // the animator expects, this will also be used to detect the fixed rotation when
+ // Shell resolves the type of the animation we are undergoing.
+ mPipDisplayLayoutState.rotateTo(launcherRotation);
+
mPipBoundsState.setBoundsStateForEntry(componentName, activityInfo, pictureInPictureParams,
mPipBoundsAlgorithm);
return mPipBoundsAlgorithm.getEntryDestinationBounds();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMenuView.java
index a29104c..0910919 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMenuView.java
@@ -447,7 +447,7 @@
final LayoutInflater inflater = LayoutInflater.from(mContext);
while (mActionsGroup.getChildCount() < mActions.size()) {
final PipMenuActionView actionView = (PipMenuActionView) inflater.inflate(
- R.layout.pip_menu_action, mActionsGroup, false);
+ R.layout.pip2_menu_action, mActionsGroup, false);
mActionsGroup.addView(actionView);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
index 0324fdb..268c3a2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
@@ -336,7 +336,7 @@
}
cancelPhysicsAnimation();
mMenuController.hideMenu(ANIM_TYPE_NONE, false /* resize */);
- // mPipTaskOrganizer.exitPip(skipAnimation ? 0 : LEAVE_PIP_DURATION, enterSplit);
+ mPipScheduler.scheduleExitPipViaExpand();
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
index f4defdc..d4f190e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
@@ -52,7 +52,6 @@
private final Context mContext;
private final PipBoundsState mPipBoundsState;
- private final PhonePipMenuController mPipMenuController;
private final ShellExecutor mMainExecutor;
private final PipTransitionState mPipTransitionState;
private PipSchedulerReceiver mSchedulerReceiver;
@@ -97,12 +96,10 @@
public PipScheduler(Context context,
PipBoundsState pipBoundsState,
- PhonePipMenuController pipMenuController,
ShellExecutor mainExecutor,
PipTransitionState pipTransitionState) {
mContext = context;
mPipBoundsState = pipBoundsState;
- mPipMenuController = pipMenuController;
mMainExecutor = mainExecutor;
mPipTransitionState = pipTransitionState;
@@ -263,7 +260,6 @@
return;
}
mPipBoundsState.setBounds(newBounds);
- mPipMenuController.updateMenuLayout(newBounds);
maybeUpdateMovementBounds();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java
index 262c14d..c58de2c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java
@@ -20,6 +20,7 @@
import android.app.ActivityManager;
import android.app.PictureInPictureParams;
+import android.app.RemoteAction;
import android.content.Context;
import android.graphics.Rect;
import android.os.Bundle;
@@ -37,6 +38,9 @@
import com.android.wm.shell.pip2.animation.PipResizeAnimator;
import com.android.wm.shell.shared.annotations.ShellMainThread;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* A Task Listener implementation used only for CUJs and trigger paths that cannot be initiated via
* Transitions framework directly.
@@ -57,6 +61,7 @@
new PictureInPictureParams.Builder().build();
private boolean mWaitingForAspectRatioChange = false;
+ private final List<PipParamsChangedCallback> mPipParamsChangedListeners = new ArrayList<>();
public PipTaskListener(Context context,
ShellTaskOrganizer shellTaskOrganizer,
@@ -85,10 +90,25 @@
if (mPictureInPictureParams.equals(params)) {
return;
}
+ if (PipUtils.remoteActionsChanged(params.getActions(), mPictureInPictureParams.getActions())
+ || !PipUtils.remoteActionsMatch(params.getCloseAction(),
+ mPictureInPictureParams.getCloseAction())) {
+ for (PipParamsChangedCallback listener : mPipParamsChangedListeners) {
+ listener.onActionsChanged(params.getActions(), params.getCloseAction());
+ }
+ }
mPictureInPictureParams.copyOnlySet(params != null ? params
: new PictureInPictureParams.Builder().build());
}
+ /** Add a PipParamsChangedCallback listener. */
+ public void addParamsChangedListener(PipParamsChangedCallback listener) {
+ if (mPipParamsChangedListeners.contains(listener)) {
+ return;
+ }
+ mPipParamsChangedListeners.add(listener);
+ }
+
@NonNull
public PictureInPictureParams getPictureInPictureParams() {
return mPictureInPictureParams;
@@ -164,4 +184,12 @@
break;
}
}
+
+ public interface PipParamsChangedCallback {
+ /**
+ * Called if either the actions or the close action changed.
+ */
+ default void onActionsChanged(List<RemoteAction> actions, RemoteAction closeAction) {
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index dc0bc78..62a60fa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -17,6 +17,7 @@
package com.android.wm.shell.pip2.phone;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.view.Surface.ROTATION_270;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_PIP;
@@ -33,6 +34,7 @@
import android.app.ActivityManager;
import android.app.PictureInPictureParams;
import android.content.Context;
+import android.graphics.Matrix;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
@@ -49,6 +51,7 @@
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipMenuController;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.pip.PipTransitionController;
@@ -82,7 +85,7 @@
* The fixed start delay in ms when fading out the content overlay from bounds animation.
* The fadeout animation is guaranteed to start after the client has drawn under the new config.
*/
- private static final int CONTENT_OVERLAY_FADE_OUT_DELAY_MS = 400;
+ private static final int CONTENT_OVERLAY_FADE_OUT_DELAY_MS = 500;
//
// Dependencies
@@ -92,6 +95,7 @@
private final PipTaskListener mPipTaskListener;
private final PipScheduler mPipScheduler;
private final PipTransitionState mPipTransitionState;
+ private final PipDisplayLayoutState mPipDisplayLayoutState;
//
// Transition caches
@@ -124,6 +128,7 @@
PipTaskListener pipTaskListener,
PipScheduler pipScheduler,
PipTransitionState pipTransitionState,
+ PipDisplayLayoutState pipDisplayLayoutState,
PipUiStateChangeController pipUiStateChangeController) {
super(shellInit, shellTaskOrganizer, transitions, pipBoundsState, pipMenuController,
pipBoundsAlgorithm);
@@ -134,6 +139,7 @@
mPipScheduler.setPipTransitionController(this);
mPipTransitionState = pipTransitionState;
mPipTransitionState.addPipTransitionStateChangedListener(this);
+ mPipDisplayLayoutState = pipDisplayLayoutState;
}
@Override
@@ -321,11 +327,30 @@
(destinationBounds.width() - overlaySize) / 2f,
(destinationBounds.height() - overlaySize) / 2f);
}
-
startTransaction.merge(finishTransaction);
+
+ final int startRotation = pipChange.getStartRotation();
+ final int endRotation = mPipDisplayLayoutState.getRotation();
+ if (endRotation != startRotation) {
+ boolean isClockwise = (endRotation - startRotation) == -ROTATION_270;
+
+ // Display bounds were already updated to represent the final orientation,
+ // so we just need to readjust the origin, and perform rotation about (0, 0).
+ Rect displayBounds = mPipDisplayLayoutState.getDisplayBounds();
+ int originTranslateX = isClockwise ? 0 : -displayBounds.width();
+ int originTranslateY = isClockwise ? -displayBounds.height() : 0;
+
+ Matrix transformTensor = new Matrix();
+ final float[] matrixTmp = new float[9];
+ transformTensor.setTranslate(originTranslateX + destinationBounds.left,
+ originTranslateY + destinationBounds.top);
+ final float degrees = (endRotation - startRotation) * 90f;
+ transformTensor.postRotate(degrees);
+ startTransaction.setMatrix(pipLeash, transformTensor, matrixTmp);
+ }
startTransaction.apply();
finishCallback.onTransitionFinished(null /* finishWct */);
- onClientDrawAtTransitionEnd();
+ finishInner();
return true;
}
@@ -397,7 +422,7 @@
sourceRectHint, PipEnterExitAnimator.BOUNDS_ENTER, Surface.ROTATION_0);
tx.addTransactionCommittedListener(mPipScheduler.getMainExecutor(),
- this::onClientDrawAtTransitionEnd);
+ this::finishInner);
finishWct.setBoundsChangeTransaction(pipTaskToken, tx);
animator.setAnimationEndCallback(() ->
@@ -430,7 +455,7 @@
animator.setAnimationEndCallback(() -> {
finishCallback.onTransitionFinished(null);
// This should update the pip transition state accordingly after we stop playing.
- onClientDrawAtTransitionEnd();
+ finishInner();
});
animator.start();
@@ -605,7 +630,7 @@
// Miscellaneous callbacks and listeners
//
- private void onClientDrawAtTransitionEnd() {
+ private void finishInner() {
if (mPipTransitionState.getSwipePipToHomeOverlay() != null) {
startOverlayFadeoutAnimation();
} else if (mPipTransitionState.getState() == PipTransitionState.ENTERING_PIP) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index c9c0873..95cb3df 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -54,7 +54,7 @@
import com.android.wm.shell.common.SingleInstanceRemoteListener;
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.GroupedRecentTaskInfo;
import com.android.wm.shell.shared.annotations.ExternalThread;
@@ -81,14 +81,14 @@
* Manages the recent task list from the system, caching it as necessary.
*/
public class RecentTasksController implements TaskStackListenerCallback,
- RemoteCallable<RecentTasksController>, DesktopModeTaskRepository.ActiveTasksListener,
+ RemoteCallable<RecentTasksController>, DesktopRepository.ActiveTasksListener,
TaskStackTransitionObserver.TaskStackTransitionObserverListener {
private static final String TAG = RecentTasksController.class.getSimpleName();
private final Context mContext;
private final ShellController mShellController;
private final ShellCommandHandler mShellCommandHandler;
- private final Optional<DesktopModeTaskRepository> mDesktopModeTaskRepository;
+ private final Optional<DesktopRepository> mDesktopRepository;
private final ShellExecutor mMainExecutor;
private final TaskStackListenerImpl mTaskStackListener;
private final RecentTasksImpl mImpl = new RecentTasksImpl();
@@ -121,7 +121,7 @@
ShellCommandHandler shellCommandHandler,
TaskStackListenerImpl taskStackListener,
ActivityTaskManager activityTaskManager,
- Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
+ Optional<DesktopRepository> desktopRepository,
TaskStackTransitionObserver taskStackTransitionObserver,
@ShellMainThread ShellExecutor mainExecutor
) {
@@ -129,7 +129,7 @@
return null;
}
return new RecentTasksController(context, shellInit, shellController, shellCommandHandler,
- taskStackListener, activityTaskManager, desktopModeTaskRepository,
+ taskStackListener, activityTaskManager, desktopRepository,
taskStackTransitionObserver, mainExecutor);
}
@@ -139,7 +139,7 @@
ShellCommandHandler shellCommandHandler,
TaskStackListenerImpl taskStackListener,
ActivityTaskManager activityTaskManager,
- Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
+ Optional<DesktopRepository> desktopRepository,
TaskStackTransitionObserver taskStackTransitionObserver,
ShellExecutor mainExecutor) {
mContext = context;
@@ -148,7 +148,7 @@
mActivityTaskManager = activityTaskManager;
mPcFeatureEnabled = mContext.getPackageManager().hasSystemFeature(FEATURE_PC);
mTaskStackListener = taskStackListener;
- mDesktopModeTaskRepository = desktopModeTaskRepository;
+ mDesktopRepository = desktopRepository;
mTaskStackTransitionObserver = taskStackTransitionObserver;
mMainExecutor = mainExecutor;
shellInit.addInitCallback(this::onInit, this);
@@ -168,7 +168,7 @@
this::createExternalInterface, this);
mShellCommandHandler.addDumpCallback(this::dump, this);
mTaskStackListener.addListener(this);
- mDesktopModeTaskRepository.ifPresent(it -> it.addActiveTaskListener(this));
+ mDesktopRepository.ifPresent(it -> it.addActiveTaskListener(this));
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
mTaskStackTransitionObserver.addTaskStackTransitionObserverListener(this,
mMainExecutor);
@@ -417,8 +417,8 @@
}
if (DesktopModeStatus.canEnterDesktopMode(mContext)
- && mDesktopModeTaskRepository.isPresent()
- && mDesktopModeTaskRepository.get().isActiveTask(taskInfo.taskId)) {
+ && mDesktopRepository.isPresent()
+ && mDesktopRepository.get().isActiveTask(taskInfo.taskId)) {
// Freeform tasks will be added as a separate entry
if (mostRecentFreeformTaskIndex == Integer.MAX_VALUE) {
mostRecentFreeformTaskIndex = recentTasks.size();
@@ -434,7 +434,7 @@
taskInfo.lastNonFullscreenBounds.top);
}
freeformTasks.add(taskInfo);
- if (mDesktopModeTaskRepository.get().isMinimizedTask(taskInfo.taskId)) {
+ if (mDesktopRepository.get().isMinimizedTask(taskInfo.taskId)) {
minimizedFreeformTasks.add(taskInfo.taskId);
}
continue;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index e8eb10c..e527c02 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -901,6 +901,23 @@
setEnterInstanceId(instanceId);
}
+
+ @Override
+ public void setExcludeImeInsets(boolean exclude) {
+ if (android.view.inputmethod.Flags.refactorInsetsController()) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ if (mRootTaskInfo == null) {
+ ProtoLog.e(WM_SHELL_SPLIT_SCREEN, "setExcludeImeInsets: mRootTaskInfo is null");
+ return;
+ }
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
+ "setExcludeImeInsets: root taskId=%s exclude=%s",
+ mRootTaskInfo.taskId, exclude);
+ wct.setExcludeImeInsets(mRootTaskInfo.token, exclude);
+ mTaskOrganizer.applyTransaction(wct);
+ }
+ }
+
/**
* Checks if either of the apps in the desired split launch is currently in Pip. If so, it will
* launch the non-pipped app as a fullscreen app, otherwise no-op.
@@ -1717,6 +1734,7 @@
final WindowContainerTransaction wct = new WindowContainerTransaction();
wct.reparent(mMainStage.mRootTaskInfo.token, mRootTaskInfo.token, true);
wct.reparent(mSideStage.mRootTaskInfo.token, mRootTaskInfo.token, true);
+
// Make the stages adjacent to each other so they occlude what's behind them.
wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token);
setRootForceTranslucent(true, wct);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java
index 30d7245..e61929f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java
@@ -141,10 +141,13 @@
pipHandler.setEnterAnimationType(ANIM_TYPE_ALPHA);
pipHandler.startEnterAnimation(pipChange, startTransaction, finishTransaction,
finishCB);
+ // make a new finishTransaction because pip's startEnterAnimation "consumes" it so
+ // we need a separate one to send over to launcher.
+ SurfaceControl.Transaction otherFinishT = new SurfaceControl.Transaction();
// Dispatch the rest of the transition normally. This will most-likely be taken by
// recents or default handler.
mixed.mLeftoversHandler = player.dispatchTransition(mixed.mTransition, everythingElse,
- otherStartT, finishTransaction, finishCB, mixedHandler);
+ otherStartT, otherFinishT, finishCB, mixedHandler);
} else {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Not leaving split, so just "
+ "forward animation to Pip-Handler.");
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 3330f96..bcf48d9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -103,7 +103,7 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler;
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition;
@@ -155,7 +155,7 @@
private final ActivityTaskManager mActivityTaskManager;
private final ShellCommandHandler mShellCommandHandler;
private final ShellTaskOrganizer mTaskOrganizer;
- private final DesktopModeTaskRepository mDesktopRepository;
+ private final DesktopRepository mDesktopRepository;
private final ShellController mShellController;
private final Context mContext;
private final @ShellMainThread Handler mMainHandler;
@@ -227,7 +227,7 @@
ShellCommandHandler shellCommandHandler,
IWindowManager windowManager,
ShellTaskOrganizer taskOrganizer,
- DesktopModeTaskRepository desktopRepository,
+ DesktopRepository desktopRepository,
DisplayController displayController,
ShellController shellController,
DisplayInsetsController displayInsetsController,
@@ -288,7 +288,7 @@
ShellCommandHandler shellCommandHandler,
IWindowManager windowManager,
ShellTaskOrganizer taskOrganizer,
- DesktopModeTaskRepository desktopRepository,
+ DesktopRepository desktopRepository,
DisplayController displayController,
ShellController shellController,
DisplayInsetsController displayInsetsController,
@@ -538,6 +538,14 @@
decoration.closeMaximizeMenu();
}
+ private void onEnterOrExitImmersive(int taskId) {
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
+ if (decoration == null) {
+ return;
+ }
+ mDesktopTasksController.toggleDesktopTaskFullImmersiveState(decoration.mTaskInfo);
+ }
+
private void onSnapResize(int taskId, boolean left) {
final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
if (decoration == null) {
@@ -755,7 +763,16 @@
// back to the decoration using
// {@link DesktopModeWindowDecoration#setOnMaximizeOrRestoreClickListener}, which
// should shared with the maximize menu's maximize/restore actions.
- onMaximizeOrRestore(decoration.mTaskInfo.taskId, "caption_bar_button");
+ if (Flags.enableFullyImmersiveInDesktop()
+ && TaskInfoKt.getRequestingImmersive(decoration.mTaskInfo)) {
+ // Task is requesting immersive, so it should either enter or exit immersive,
+ // depending on immersive state.
+ onEnterOrExitImmersive(decoration.mTaskInfo.taskId);
+ } else {
+ // Full immersive is disabled or task doesn't request/support it, so just
+ // toggle between maximize/restore states.
+ onMaximizeOrRestore(decoration.mTaskInfo.taskId, "caption_bar_button");
+ }
} else if (id == R.id.minimize_window) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
mDesktopTasksController.onDesktopWindowMinimize(wct, mTaskId);
@@ -935,14 +952,18 @@
}
final boolean touchingButton = (id == R.id.close_window || id == R.id.maximize_window
|| id == R.id.open_menu_button || id == R.id.minimize_window);
+ final boolean dragAllowed =
+ !mDesktopRepository.isTaskInFullImmersiveState(taskInfo.taskId);
switch (e.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
- mDragPointerId = e.getPointerId(0);
- final Rect initialBounds = mDragPositioningCallback.onDragPositioningStart(
- 0 /* ctrlType */, e.getRawX(0),
- e.getRawY(0));
- updateDragStatus(e.getActionMasked());
- mOnDragStartInitialBounds.set(initialBounds);
+ if (dragAllowed) {
+ mDragPointerId = e.getPointerId(0);
+ final Rect initialBounds = mDragPositioningCallback.onDragPositioningStart(
+ 0 /* ctrlType */, e.getRawX(0),
+ e.getRawY(0));
+ updateDragStatus(e.getActionMasked());
+ mOnDragStartInitialBounds.set(initialBounds);
+ }
mHasLongClicked = false;
// Do not consume input event if a button is touched, otherwise it would
// prevent the button's ripple effect from showing.
@@ -951,6 +972,9 @@
case ACTION_MOVE: {
// If a decor's resize drag zone is active, don't also try to reposition it.
if (decoration.isHandlingDragResize()) break;
+ // Dragging the header isn't allowed, so skip the positioning work.
+ if (!dragAllowed) break;
+
decoration.closeMaximizeMenu();
if (e.findPointerIndex(mDragPointerId) == -1) {
mDragPointerId = e.getPointerId(0);
@@ -1036,6 +1060,10 @@
&& action != MotionEvent.ACTION_CANCEL)) {
return false;
}
+ if (mDesktopRepository.isTaskInFullImmersiveState(mTaskId)) {
+ // Disallow double-tap to resize when in full immersive.
+ return false;
+ }
onMaximizeOrRestore(mTaskId, "double_tap");
return true;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index ada1e0b..25d37fc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -87,13 +87,14 @@
import com.android.wm.shell.apptoweb.AppToWebGenericLinksParser;
import com.android.wm.shell.apptoweb.AppToWebUtils;
import com.android.wm.shell.apptoweb.AssistContentRequester;
+import com.android.wm.shell.apptoweb.OpenByDefaultDialog;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.MultiInstanceHelper;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.desktopmode.CaptionState;
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository;
import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
@@ -163,6 +164,8 @@
private MaximizeMenu mMaximizeMenu;
+ private OpenByDefaultDialog mOpenByDefaultDialog;
+
private ResizeVeil mResizeVeil;
private Bitmap mAppIconBitmap;
private Bitmap mResizeVeilBitmap;
@@ -193,14 +196,14 @@
private final Runnable mCapturedLinkExpiredRunnable = this::onCapturedLinkExpired;
private final MultiInstanceHelper mMultiInstanceHelper;
private final WindowDecorCaptionHandleRepository mWindowDecorCaptionHandleRepository;
- private final DesktopModeTaskRepository mDesktopRepository;
+ private final DesktopRepository mDesktopRepository;
DesktopModeWindowDecoration(
Context context,
@NonNull Context userContext,
DisplayController displayController,
SplitScreenController splitScreenController,
- DesktopModeTaskRepository desktopRepository,
+ DesktopRepository desktopRepository,
ShellTaskOrganizer taskOrganizer,
ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
@@ -232,7 +235,7 @@
@NonNull Context userContext,
DisplayController displayController,
SplitScreenController splitScreenController,
- DesktopModeTaskRepository desktopRepository,
+ DesktopRepository desktopRepository,
ShellTaskOrganizer taskOrganizer,
ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
@@ -448,6 +451,10 @@
mHandleMenu.relayout(startT, mResult.mCaptionX);
}
+ if (isOpenByDefaultDialogActive()) {
+ mOpenByDefaultDialog.relayout(taskInfo);
+ }
+
final boolean inFullImmersive = mDesktopRepository
.isTaskInFullImmersiveState(taskInfo.taskId);
updateRelayoutParams(mRelayoutParams, mContext, taskInfo, applyStartTransactionOnDraw,
@@ -510,8 +517,8 @@
closeManageWindowsMenu();
closeMaximizeMenu();
}
- updateDragResizeListener(oldDecorationSurface);
- updateMaximizeMenu(startT);
+ updateDragResizeListener(oldDecorationSurface, inFullImmersive);
+ updateMaximizeMenu(startT, inFullImmersive);
Trace.endSection(); // DesktopModeWindowDecoration#updateRelayoutParamsAndSurfaces
}
@@ -564,11 +571,12 @@
return mUserContext.getUser();
}
- private void updateDragResizeListener(SurfaceControl oldDecorationSurface) {
- if (!isDragResizable(mTaskInfo)) {
+ private void updateDragResizeListener(SurfaceControl oldDecorationSurface,
+ boolean inFullImmersive) {
+ if (!isDragResizable(mTaskInfo, inFullImmersive)) {
if (!mTaskInfo.positionInParent.equals(mPositionInParent)) {
// We still want to track caption bar's exclusion region on a non-resizeable task.
- updateExclusionRegion();
+ updateExclusionRegion(inFullImmersive);
}
closeDragResizeListener();
return;
@@ -602,11 +610,16 @@
getResizeEdgeHandleSize(res), getResizeHandleEdgeInset(res),
getFineResizeCornerSize(res), getLargeResizeCornerSize(res)), touchSlop)
|| !mTaskInfo.positionInParent.equals(mPositionInParent)) {
- updateExclusionRegion();
+ updateExclusionRegion(inFullImmersive);
}
}
- private static boolean isDragResizable(ActivityManager.RunningTaskInfo taskInfo) {
+ private static boolean isDragResizable(ActivityManager.RunningTaskInfo taskInfo,
+ boolean inFullImmersive) {
+ if (inFullImmersive) {
+ // Task cannot be resized in full immersive.
+ return false;
+ }
if (DesktopModeFlags.ENABLE_WINDOWING_SCALED_RESIZING.isTrue()) {
return taskInfo.isFreeform();
}
@@ -670,8 +683,8 @@
mWindowDecorCaptionHandleRepository.notifyCaptionChanged(captionState);
}
- private void updateMaximizeMenu(SurfaceControl.Transaction startT) {
- if (!isDragResizable(mTaskInfo) || !isMaximizeMenuActive()) {
+ private void updateMaximizeMenu(SurfaceControl.Transaction startT, boolean inFullImmersive) {
+ if (!isDragResizable(mTaskInfo, inFullImmersive) || !isMaximizeMenuActive()) {
return;
}
if (!mTaskInfo.isVisible()) {
@@ -940,6 +953,33 @@
return mHandleMenu != null;
}
+ boolean isOpenByDefaultDialogActive() {
+ return mOpenByDefaultDialog != null;
+ }
+
+ void createOpenByDefaultDialog() {
+ mOpenByDefaultDialog = new OpenByDefaultDialog(
+ mContext,
+ mTaskInfo,
+ mTaskSurface,
+ mDisplayController,
+ mSurfaceControlTransactionSupplier,
+ new OpenByDefaultDialog.DialogLifecycleListener() {
+ @Override
+ public void onDialogCreated() {
+ closeHandleMenu();
+ }
+
+ @Override
+ public void onDialogDismissed() {
+ mOpenByDefaultDialog = null;
+ }
+ },
+ mAppIconBitmap,
+ mAppName
+ );
+ }
+
boolean shouldResizeListenerHandleEvent(@NonNull MotionEvent e, @NonNull Point offset) {
return mDragResizeListener != null && mDragResizeListener.shouldHandleEvent(e, offset);
}
@@ -1236,6 +1276,12 @@
onCapturedLinkExpired();
return Unit.INSTANCE;
},
+ /* onOpenByDefaultClickListener= */ () -> {
+ if (!isOpenByDefaultDialogActive()) {
+ createOpenByDefaultDialog();
+ }
+ return Unit.INSTANCE;
+ },
/* onCloseMenuClickListener= */ () -> {
closeHandleMenu();
return Unit.INSTANCE;
@@ -1506,24 +1552,29 @@
mPositionInParent.set(mTaskInfo.positionInParent);
}
- private void updateExclusionRegion() {
+ private void updateExclusionRegion(boolean inFullImmersive) {
// An outdated position in parent is one reason for this to be called; update it here.
updatePositionInParent();
mExclusionRegionListener
- .onExclusionRegionChanged(mTaskInfo.taskId, getGlobalExclusionRegion());
+ .onExclusionRegionChanged(mTaskInfo.taskId,
+ getGlobalExclusionRegion(inFullImmersive));
}
/**
* Create a new exclusion region from the corner rects (if resizeable) and caption bounds
* of this task.
*/
- private Region getGlobalExclusionRegion() {
+ private Region getGlobalExclusionRegion(boolean inFullImmersive) {
Region exclusionRegion;
- if (mDragResizeListener != null && isDragResizable(mTaskInfo)) {
+ if (mDragResizeListener != null && isDragResizable(mTaskInfo, inFullImmersive)) {
exclusionRegion = mDragResizeListener.getCornersRegion();
} else {
exclusionRegion = new Region();
}
+ if (inFullImmersive) {
+ // Task can't be moved in full immersive, so skip excluding the caption region.
+ return exclusionRegion;
+ }
exclusionRegion.union(new Rect(0, 0, mResult.mWidth,
getCaptionHeight(mTaskInfo.getWindowingMode())));
exclusionRegion.translate(mPositionInParent.x, mPositionInParent.y);
@@ -1591,7 +1642,7 @@
@NonNull Context userContext,
DisplayController displayController,
SplitScreenController splitScreenController,
- DesktopModeTaskRepository desktopRepository,
+ DesktopRepository desktopRepository,
ShellTaskOrganizer taskOrganizer,
ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
index 98fef47..2e32703 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
@@ -120,6 +120,7 @@
onNewWindowClickListener: () -> Unit,
onManageWindowsClickListener: () -> Unit,
openInBrowserClickListener: (Intent) -> Unit,
+ onOpenByDefaultClickListener: () -> Unit,
onCloseMenuClickListener: () -> Unit,
onOutsideTouchListener: () -> Unit,
) {
@@ -135,6 +136,7 @@
onNewWindowClickListener = onNewWindowClickListener,
onManageWindowsClickListener = onManageWindowsClickListener,
openInBrowserClickListener = openInBrowserClickListener,
+ onOpenByDefaultClickListener = onOpenByDefaultClickListener,
onCloseMenuClickListener = onCloseMenuClickListener,
onOutsideTouchListener = onOutsideTouchListener,
)
@@ -153,6 +155,7 @@
onNewWindowClickListener: () -> Unit,
onManageWindowsClickListener: () -> Unit,
openInBrowserClickListener: (Intent) -> Unit,
+ onOpenByDefaultClickListener: () -> Unit,
onCloseMenuClickListener: () -> Unit,
onOutsideTouchListener: () -> Unit
) {
@@ -174,6 +177,7 @@
this.onOpenInBrowserClickListener = {
openInBrowserClickListener.invoke(openInBrowserIntent!!)
}
+ this.onOpenByDefaultClickListener = onOpenByDefaultClickListener
this.onCloseMenuClickListener = onCloseMenuClickListener
this.onOutsideTouchListener = onOutsideTouchListener
}
@@ -448,7 +452,8 @@
private val openInBrowserPill = rootView.requireViewById<View>(R.id.open_in_browser_pill)
private val browserBtn = openInBrowserPill.requireViewById<Button>(
R.id.open_in_browser_button)
-
+ private val openByDefaultBtn = openInBrowserPill.requireViewById<ImageButton>(
+ R.id.open_by_default_button)
private val decorThemeUtil = DecorThemeUtil(context)
private val animator = HandleMenuAnimator(rootView, menuWidth, captionHeight.toFloat())
@@ -461,6 +466,7 @@
var onNewWindowClickListener: (() -> Unit)? = null
var onManageWindowsClickListener: (() -> Unit)? = null
var onOpenInBrowserClickListener: (() -> Unit)? = null
+ var onOpenByDefaultClickListener: (() -> Unit)? = null
var onCloseMenuClickListener: (() -> Unit)? = null
var onOutsideTouchListener: (() -> Unit)? = null
@@ -469,6 +475,9 @@
splitscreenBtn.setOnClickListener { onToSplitScreenClickListener?.invoke() }
desktopBtn.setOnClickListener { onToDesktopClickListener?.invoke() }
browserBtn.setOnClickListener { onOpenInBrowserClickListener?.invoke() }
+ openByDefaultBtn.setOnClickListener {
+ onOpenByDefaultClickListener?.invoke()
+ }
collapseMenuButton.setOnClickListener { onCloseMenuClickListener?.invoke() }
newWindowBtn.setOnClickListener { onNewWindowClickListener?.invoke() }
manageWindowBtn.setOnClickListener { onManageWindowsClickListener?.invoke() }
@@ -634,6 +643,8 @@
setTextColor(style.textColor)
compoundDrawableTintList = ColorStateList.valueOf(style.textColor)
}
+
+ openByDefaultBtn.imageTintList = ColorStateList.valueOf(style.textColor)
}
private data class MenuStyle(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationTooltipController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationTooltipController.kt
index a9a16bc..c61b31e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationTooltipController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationTooltipController.kt
@@ -16,6 +16,7 @@
package com.android.wm.shell.windowdecor.education
+import android.annotation.ColorInt
import android.annotation.DimenRes
import android.annotation.LayoutRes
import android.content.Context
@@ -32,6 +33,7 @@
import android.widget.TextView
import android.window.DisplayAreaInfo
import android.window.WindowContainerTransaction
+import androidx.core.graphics.drawable.DrawableCompat
import androidx.dynamicanimation.animation.DynamicAnimation
import androidx.dynamicanimation.animation.SpringForce
import com.android.wm.shell.R
@@ -120,6 +122,7 @@
hideEducationTooltip()
tooltipViewConfig.onEducationClickAction()
}
+ setTooltipColorScheme(tooltipViewConfig.tooltipColorScheme)
}
val tooltipDimens = tooltipDimens(tooltipView = tooltipView, tooltipViewConfig.arrowDirection)
@@ -189,6 +192,21 @@
view = tooltipView)
}
+ private fun View.setTooltipColorScheme(tooltipColorScheme: TooltipColorScheme) {
+ requireViewById<LinearLayout>(R.id.tooltip_container).apply {
+ background.setTint(tooltipColorScheme.container)
+ }
+ requireViewById<ImageView>(R.id.arrow_icon).apply {
+ val wrappedDrawable = DrawableCompat.wrap(this.drawable)
+ DrawableCompat.setTint(wrappedDrawable, tooltipColorScheme.container)
+ }
+ requireViewById<TextView>(R.id.tooltip_text).apply { setTextColor(tooltipColorScheme.text) }
+ requireViewById<ImageView>(R.id.tooltip_icon).apply {
+ val wrappedDrawable = DrawableCompat.wrap(this.drawable)
+ DrawableCompat.setTint(wrappedDrawable, tooltipColorScheme.icon)
+ }
+ }
+
private fun tooltipViewGlobalCoordinates(
tooltipViewGlobalCoordinates: Point,
arrowDirection: TooltipArrowDirection,
@@ -255,6 +273,7 @@
*/
data class EducationViewConfig(
@LayoutRes val tooltipViewLayout: Int,
+ val tooltipColorScheme: TooltipColorScheme,
val tooltipViewGlobalCoordinates: Point,
val tooltipText: String,
val arrowDirection: TooltipArrowDirection,
@@ -262,6 +281,19 @@
val onDismissAction: () -> Unit,
)
+ /**
+ * Color scheme of education view:
+ *
+ * @property container Color of the container of the tooltip.
+ * @property text Text color of the [TextView] of education tooltip.
+ * @property icon Color to be filled in tooltip's icon.
+ */
+ data class TooltipColorScheme(
+ @ColorInt val container: Int,
+ @ColorInt val text: Int,
+ @ColorInt val icon: Int,
+ )
+
/** Direction of arrow of the tooltip */
enum class TooltipArrowDirection {
UP,
diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/Android.bp b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/Android.bp
index a231e38..176020f 100644
--- a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/Android.bp
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/Android.bp
@@ -69,116 +69,6 @@
}
////////////////////////////////////////////////////////////////////////////////
-// Begin cleanup after gcl merges
-
-filegroup {
- name: "WMShellFlickerTestsSplitScreenGroup1-src",
- srcs: [
- "src/**/A*.kt",
- "src/**/B*.kt",
- "src/**/C*.kt",
- "src/**/D*.kt",
- ],
-}
-
-filegroup {
- name: "WMShellFlickerTestsSplitScreenGroup2-src",
- srcs: [
- "src/**/E*.kt",
- ],
-}
-
-filegroup {
- name: "WMShellFlickerTestsSplitScreenGroup3-src",
- srcs: [
- "src/**/S*.kt",
- ],
-}
-
-filegroup {
- name: "WMShellFlickerTestsSplitScreenGroupOther-src",
- srcs: [
- "src/**/*.kt",
- ],
-}
-
-android_test {
- name: "WMShellFlickerTestsSplitScreenGroup1",
- defaults: ["WMShellFlickerTestsDefault"],
- manifest: "AndroidManifest.xml",
- package_name: "com.android.wm.shell.flicker.splitscreen",
- instrumentation_target_package: "com.android.wm.shell.flicker.splitscreen",
- test_config_template: "AndroidTestTemplate.xml",
- srcs: [
- ":WMShellFlickerTestsSplitScreenGroup1-src",
- ],
- static_libs: [
- "WMShellFlickerTestsBase",
- "WMShellFlickerTestsSplitScreenBase",
- ],
- data: ["trace_config/*"],
-}
-
-android_test {
- name: "WMShellFlickerTestsSplitScreenGroup2",
- defaults: ["WMShellFlickerTestsDefault"],
- manifest: "AndroidManifest.xml",
- package_name: "com.android.wm.shell.flicker.splitscreen",
- instrumentation_target_package: "com.android.wm.shell.flicker.splitscreen",
- test_config_template: "AndroidTestTemplate.xml",
- srcs: [
- ":WMShellFlickerTestsSplitScreenGroup2-src",
- ],
- static_libs: [
- "WMShellFlickerTestsBase",
- "WMShellFlickerTestsSplitScreenBase",
- ],
- data: ["trace_config/*"],
-}
-
-android_test {
- name: "WMShellFlickerTestsSplitScreenGroup3",
- defaults: ["WMShellFlickerTestsDefault"],
- manifest: "AndroidManifest.xml",
- package_name: "com.android.wm.shell.flicker.splitscreen",
- instrumentation_target_package: "com.android.wm.shell.flicker.splitscreen",
- test_config_template: "AndroidTestTemplate.xml",
- srcs: [
- ":WMShellFlickerTestsSplitScreenGroup3-src",
- ],
- static_libs: [
- "WMShellFlickerTestsBase",
- "WMShellFlickerTestsSplitScreenBase",
- ],
- data: ["trace_config/*"],
-}
-
-android_test {
- name: "WMShellFlickerTestsSplitScreenGroupOther",
- defaults: ["WMShellFlickerTestsDefault"],
- manifest: "AndroidManifest.xml",
- package_name: "com.android.wm.shell.flicker.splitscreen",
- instrumentation_target_package: "com.android.wm.shell.flicker.splitscreen",
- test_config_template: "AndroidTestTemplate.xml",
- srcs: [
- ":WMShellFlickerTestsSplitScreenGroupOther-src",
- ],
- exclude_srcs: [
- ":WMShellFlickerTestsSplitScreenGroup1-src",
- ":WMShellFlickerTestsSplitScreenGroup2-src",
- ":WMShellFlickerTestsSplitScreenGroup3-src",
- ],
- static_libs: [
- "WMShellFlickerTestsBase",
- "WMShellFlickerTestsSplitScreenBase",
- ],
- data: ["trace_config/*"],
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// End cleanup after gcl merges
-
-////////////////////////////////////////////////////////////////////////////////
// Begin breakdowns for FlickerTestsRotation module
test_module_config {
diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/Android.bp b/libs/WindowManager/Shell/tests/flicker/appcompat/Android.bp
index 29a9f10..b016c9f 100644
--- a/libs/WindowManager/Shell/tests/flicker/appcompat/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/Android.bp
@@ -15,7 +15,7 @@
//
package {
- default_team: "trendy_team_app_compat",
+ default_team: "trendy_team_lse_app_compat",
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "frameworks_base_license"
@@ -24,31 +24,6 @@
default_applicable_licenses: ["frameworks_base_license"],
}
-////////////////////////////////////////////////////////////////////////////////
-// Begin cleanup after gcl merge
-
-filegroup {
- name: "WMShellFlickerTestsAppCompat-src",
- srcs: [
- "src/**/*.kt",
- ],
-}
-
-android_test {
- name: "WMShellFlickerTestsOther",
- defaults: ["WMShellFlickerTestsDefault"],
- manifest: "AndroidManifest.xml",
- package_name: "com.android.wm.shell.flicker",
- instrumentation_target_package: "com.android.wm.shell.flicker",
- test_config_template: "AndroidTestTemplate.xml",
- srcs: [":WMShellFlickerTestsAppCompat-src"],
- static_libs: ["WMShellFlickerTestsBase"],
- data: ["trace_config/*"],
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// End cleanup after gcl merge
-
android_test {
name: "WMShellFlickerTestsAppCompat",
defaults: ["WMShellFlickerTestsDefault"],
diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt
index 16c2d47..27303c1 100644
--- a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt
@@ -31,7 +31,7 @@
/**
* Test launching app in size compat mode.
*
- * To run this test: `atest WMShellFlickerTestsOther:OpenAppInSizeCompatModeTest`
+ * To run this test: `atest WMShellFlickerTestsAppCompat:OpenAppInSizeCompatModeTest`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt
index d85b771..2980d51 100644
--- a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt
@@ -32,7 +32,7 @@
/**
* Test launching app in size compat mode.
*
- * To run this test: `atest WMShellFlickerTestsOther:OpenTransparentActivityTest`
+ * To run this test: `atest WMShellFlickerTestsAppCompat:OpenTransparentActivityTest`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt
index 164534c..2484f67 100644
--- a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt
@@ -36,7 +36,7 @@
/**
* Test quick switching to letterboxed app from launcher
*
- * To run this test: `atest WMShellFlickerTestsOther:QuickSwitchLauncherToLetterboxAppTest`
+ * To run this test: `atest WMShellFlickerTestsAppCompat:QuickSwitchLauncherToLetterboxAppTest`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt
index 034d54b..77423af 100644
--- a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt
@@ -32,7 +32,7 @@
/**
* Test launching a fixed portrait letterboxed app in landscape and repositioning to the right.
*
- * To run this test: `atest WMShellFlickerTestsOther:RepositionFixedPortraitAppTest`
+ * To run this test: `atest WMShellFlickerTestsAppCompat:RepositionFixedPortraitAppTest`
*
* Actions:
*
diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt
index 443fac1..5459ef03 100644
--- a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt
@@ -31,7 +31,7 @@
/**
* Test restarting app in size compat mode.
*
- * To run this test: `atest WMShellFlickerTestsOther:RestartAppInSizeCompatModeTest`
+ * To run this test: `atest WMShellFlickerTestsAppCompat:RestartAppInSizeCompatModeTest`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RotateImmersiveAppInFullscreenTest.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RotateImmersiveAppInFullscreenTest.kt
index 22543aa..5bb9640 100644
--- a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RotateImmersiveAppInFullscreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RotateImmersiveAppInFullscreenTest.kt
@@ -45,7 +45,7 @@
/**
* Test rotating an immersive app in fullscreen.
*
- * To run this test: `atest WMShellFlickerTestsOther:RotateImmersiveAppInFullscreenTest`
+ * To run this test: `atest WMShellFlickerTestsAppCompat:RotateImmersiveAppInFullscreenTest`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/Android.bp b/libs/WindowManager/Shell/tests/flicker/pip/Android.bp
index 4165ed0..ddbc681 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/pip/Android.bp
@@ -29,92 +29,12 @@
srcs: ["src/**/apps/*.kt"],
}
-////////////////////////////////////////////////////////////////////////////////
-// Begin cleanup after gcl merges
-
-filegroup {
- name: "WMShellFlickerTestsPip1-src",
- srcs: [
- "src/**/A*.kt",
- "src/**/B*.kt",
- "src/**/C*.kt",
- "src/**/D*.kt",
- "src/**/F*.kt",
- "src/**/S*.kt",
- ],
-}
-
-filegroup {
- name: "WMShellFlickerTestsPip2-src",
- srcs: [
- "src/**/E*.kt",
- ],
-}
-
-filegroup {
- name: "WMShellFlickerTestsPip3-src",
- srcs: ["src/**/*.kt"],
-}
-
filegroup {
name: "WMShellFlickerTestsPipCommon-src",
srcs: ["src/**/common/*.kt"],
}
android_test {
- name: "WMShellFlickerTestsPip1",
- defaults: ["WMShellFlickerTestsDefault"],
- manifest: "AndroidManifest.xml",
- package_name: "com.android.wm.shell.flicker.pip",
- instrumentation_target_package: "com.android.wm.shell.flicker.pip",
- test_config_template: "AndroidTestTemplate.xml",
- srcs: [
- ":WMShellFlickerTestsPip1-src",
- ":WMShellFlickerTestsPipCommon-src",
- ],
- static_libs: ["WMShellFlickerTestsBase"],
- data: ["trace_config/*"],
-}
-
-android_test {
- name: "WMShellFlickerTestsPip2",
- defaults: ["WMShellFlickerTestsDefault"],
- manifest: "AndroidManifest.xml",
- package_name: "com.android.wm.shell.flicker.pip",
- instrumentation_target_package: "com.android.wm.shell.flicker.pip",
- test_config_template: "AndroidTestTemplate.xml",
- srcs: [
- ":WMShellFlickerTestsPip2-src",
- ":WMShellFlickerTestsPipCommon-src",
- ],
- static_libs: ["WMShellFlickerTestsBase"],
- data: ["trace_config/*"],
-}
-
-android_test {
- name: "WMShellFlickerTestsPip3",
- defaults: ["WMShellFlickerTestsDefault"],
- manifest: "AndroidManifest.xml",
- package_name: "com.android.wm.shell.flicker.pip",
- instrumentation_target_package: "com.android.wm.shell.flicker.pip",
- test_config_template: "AndroidTestTemplate.xml",
- srcs: [
- ":WMShellFlickerTestsPip3-src",
- ":WMShellFlickerTestsPipCommon-src",
- ],
- exclude_srcs: [
- ":WMShellFlickerTestsPip1-src",
- ":WMShellFlickerTestsPip2-src",
- ":WMShellFlickerTestsPipApps-src",
- ],
- static_libs: ["WMShellFlickerTestsBase"],
- data: ["trace_config/*"],
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// End cleanup after gcl merges
-
-android_test {
name: "WMShellFlickerTestsPip",
defaults: ["WMShellFlickerTestsDefault"],
manifest: "AndroidManifest.xml",
@@ -122,6 +42,7 @@
instrumentation_target_package: "com.android.wm.shell.flicker.pip",
test_config_template: "AndroidTestTemplate.xml",
srcs: ["src/**/*.kt"],
+ exclude_srcs: [":WMShellFlickerTestsPipApps-src"],
static_libs: ["WMShellFlickerTestsBase"],
data: ["trace_config/*"],
}
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
index a9ed13a..a248303 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
@@ -38,7 +38,7 @@
/**
* Test entering pip from an app via auto-enter property when navigating to home.
*
- * To run this test: `atest WMShellFlickerTestsPip1:AutoEnterPipOnGoToHomeTest`
+ * To run this test: `atest WMShellFlickerTestsPip:AutoEnterPipOnGoToHomeTest`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt
index d059211..df952c9 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt
@@ -30,7 +30,7 @@
/**
* Test auto entering pip using a source rect hint.
*
- * To run this test: `atest WMShellFlickerTestsPip1:AutoEnterPipWithSourceRectHintTest`
+ * To run this test: `atest WMShellFlickerTestsPip:AutoEnterPipWithSourceRectHintTest`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
index 3ffc9d7..302b8c4 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
@@ -32,7 +32,7 @@
/**
* Test closing a pip window by swiping it to the bottom-center of the screen
*
- * To run this test: `atest WMShellFlickerTestsPip1:ClosePipBySwipingDownTest`
+ * To run this test: `atest WMShellFlickerTestsPip:ClosePipBySwipingDownTest`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
index d177624..77a1edb 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
@@ -30,7 +30,7 @@
/**
* Test closing a pip window via the dismiss button
*
- * To run this test: `atest WMShellFlickerTestsPip1:ClosePipWithDismissButtonTest`
+ * To run this test: `atest WMShellFlickerTestsPip:ClosePipWithDismissButtonTest`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
index a86803d..6e32d64 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
@@ -31,7 +31,7 @@
/**
* Test entering pip from an app via [onUserLeaveHint] and by navigating to home.
*
- * To run this test: `atest WMShellFlickerTestsPip2:EnterPipOnUserLeaveHintTest`
+ * To run this test: `atest WMShellFlickerTestsPip:EnterPipOnUserLeaveHintTest`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
index d0e8215..9a6cb61 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
@@ -46,7 +46,7 @@
/**
* Test entering pip while changing orientation (from app in landscape to pip window in portrait)
*
- * To run this test: `atest WMShellFlickerTestsPip2:EnterPipToOtherOrientation`
+ * To run this test: `atest WMShellFlickerTestsPip:EnterPipToOtherOrientation`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
index d92f55a..6b4751c 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
@@ -28,7 +28,7 @@
/**
* Test entering pip from an app by interacting with the app UI
*
- * To run this test: `atest WMShellFlickerTestsPip2:EnterPipViaAppUiButtonTest`
+ * To run this test: `atest WMShellFlickerTestsPip:EnterPipViaAppUiButtonTest`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
index 8c0817d..8d0bc0f 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
@@ -28,7 +28,7 @@
/**
* Test expanding a pip window back to full screen via the expand button
*
- * To run this test: `atest WMShellFlickerTestsPip2:ExitPipToAppViaExpandButtonTest`
+ * To run this test: `atest WMShellFlickerTestsPip:ExitPipToAppViaExpandButtonTest`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
index 90a9623..939f328 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
@@ -28,7 +28,7 @@
/**
* Test expanding a pip window back to full screen via an intent
*
- * To run this test: `atest WMShellFlickerTestsPip2:ExitPipToAppViaIntentTest`
+ * To run this test: `atest WMShellFlickerTestsPip:ExitPipToAppViaIntentTest`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
index 9306c77..258663b 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
@@ -33,7 +33,7 @@
/**
* Test expanding a pip window by double-clicking it
*
- * To run this test: `atest WMShellFlickerTestsPip2:ExpandPipOnDoubleClickTest`
+ * To run this test: `atest WMShellFlickerTestsPip:ExpandPipOnDoubleClickTest`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt
index cb8ee27..5f8ac2a 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt
@@ -39,7 +39,7 @@
/**
* Test entering pip from an app via auto-enter property when navigating to home from split screen.
*
- * To run this test: `atest WMShellFlickerTestsPip1:FromSplitScreenAutoEnterPipOnGoToHomeTest`
+ * To run this test: `atest WMShellFlickerTestsPip:FromSplitScreenAutoEnterPipOnGoToHomeTest`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt
index d03d779..48c85a8 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt
@@ -40,7 +40,7 @@
/**
* Test entering pip from an app via auto-enter property when navigating to home from split screen.
*
- * To run this test: `atest WMShellFlickerTestsPip1:FromSplitScreenEnterPipOnUserLeaveHintTest`
+ * To run this test: `atest WMShellFlickerTestsPip:FromSplitScreenEnterPipOnUserLeaveHintTest`
*
* Actions:
* ```
@@ -183,12 +183,6 @@
}
/** {@inheritDoc} */
- @FlakyTest(bugId = 312446524)
- @Test
- override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- super.visibleLayersShownMoreThanOneConsecutiveEntry()
-
- /** {@inheritDoc} */
@Test
@FlakyTest(bugId = 336510055)
override fun entireScreenCovered() {
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
index 265eb44..ee62cf5 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
@@ -32,7 +32,7 @@
/**
* Test Pip movement with Launcher shelf height change (increase).
*
- * To run this test: `atest WMShellFlickerTestsPip3:MovePipDownOnShelfHeightChange`
+ * To run this test: `atest WMShellFlickerTestsPip:MovePipDownOnShelfHeightChange`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
index 8d6be64..4d643f7 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
@@ -32,7 +32,7 @@
/**
* Test Pip movement with Launcher shelf height change (decrease).
*
- * To run this test: `atest WMShellFlickerTestsPip3:MovePipUpOnShelfHeightChangeTest`
+ * To run this test: `atest WMShellFlickerTestsPip:MovePipUpOnShelfHeightChangeTest`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt
index 9109eaf..c6cf341 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt
@@ -35,7 +35,7 @@
/**
* Test Pip Stack in bounds after rotations.
*
- * To run this test: `atest WMShellFlickerTestsPip1:ShowPipAndRotateDisplay`
+ * To run this test: `atest WMShellFlickerTestsPip:ShowPipAndRotateDisplay`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt
index 1fc9d99..7b04b76 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt
@@ -40,7 +40,7 @@
/**
* Test entering pip from Maps app by interacting with the app UI
*
- * To run this test: `atest WMShellFlickerTests:MapsEnterPipTest`
+ * To run this test: `atest WMShellFlickerTestsPipAppsCSuite:MapsEnterPipTest`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt
index 68fa7c7..6911946 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt
@@ -38,7 +38,7 @@
/**
* Test entering pip from Netflix app by interacting with the app UI
*
- * To run this test: `atest WMShellFlickerTests:NetflixEnterPipTest`
+ * To run this test: `atest WMShellFlickerTestsPipAppsCSuite:NetflixEnterPipTest`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt
index 7873a85..5e54f30 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt
@@ -34,7 +34,7 @@
/**
* Test entering pip from YouTube app by interacting with the app UI
*
- * To run this test: `atest WMShellFlickerTests:YouTubeEnterPipTest`
+ * To run this test: `atest WMShellFlickerTestsPipAppsCSuite:YouTubeEnterPipTest`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt
index 72be3d8..159cba4 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt
@@ -38,7 +38,7 @@
/**
* Test entering pip from YouTube app by interacting with the app UI
*
- * To run this test: `atest WMShellFlickerTests:YouTubeEnterPipTest`
+ * To run this test: `atest WMShellFlickerTestsPipAppsCSuite:YouTubeEnterPipTest`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipBasicTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipBasicTest.kt
index 8a073ab..6a84b28 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipBasicTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipBasicTest.kt
@@ -25,7 +25,7 @@
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
-/** Test Pip Menu on TV. To run this test: `atest WMShellFlickerTests:TvPipBasicTest` */
+/** Test Pip Menu on TV. To run this test: `atest WMShellFlickerTestsPip:TvPipBasicTest` */
@RequiresDevice
@RunWith(Parameterized::class)
class TvPipBasicTest(private val radioButtonId: String, private val pipWindowRatio: Rational?) :
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt
index d4cd6da..09e8745 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt
@@ -27,7 +27,7 @@
import org.junit.Before
import org.junit.Test
-/** Test Pip Menu on TV. To run this test: `atest WMShellFlickerTests:TvPipMenuTests` */
+/** Test Pip Menu on TV. To run this test: `atest WMShellFlickerTestsPip:TvPipMenuTests` */
@RequiresDevice
class TvPipMenuTests : TvPipTestBase() {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
index f8f0db9..0373bbd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
@@ -26,6 +26,8 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -34,6 +36,12 @@
import android.graphics.Insets;
import android.graphics.Point;
+import android.os.Looper;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.view.IWindowManager;
import android.view.InsetsSource;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
@@ -47,6 +55,7 @@
import com.android.wm.shell.sysui.ShellInit;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -61,11 +70,16 @@
*/
@SmallTest
public class DisplayImeControllerTest extends ShellTestCase {
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
@Mock
private SurfaceControl.Transaction mT;
@Mock
private ShellInit mShellInit;
+ @Mock
+ private IWindowManager mWm;
+ private DisplayImeController mDisplayImeController;
private DisplayImeController.PerDisplay mPerDisplay;
private Executor mExecutor;
@@ -73,7 +87,8 @@
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mExecutor = spy(Runnable::run);
- mPerDisplay = new DisplayImeController(null, mShellInit, null, null, new TransactionPool() {
+ mDisplayImeController = new DisplayImeController(mWm, mShellInit, null, null,
+ new TransactionPool() {
@Override
public SurfaceControl.Transaction acquire() {
return mT;
@@ -84,8 +99,10 @@
}
}, mExecutor) {
@Override
- void removeImeSurface(int displayId) { }
- }.new PerDisplay(DEFAULT_DISPLAY, ROTATION_0);
+ void removeImeSurface(int displayId) {
+ }
+ };
+ mPerDisplay = mDisplayImeController.new PerDisplay(DEFAULT_DISPLAY, ROTATION_0);
}
@Test
@@ -95,12 +112,14 @@
@Test
public void insetsControlChanged_schedulesNoWorkOnExecutor() {
+ Looper.prepare();
mPerDisplay.insetsControlChanged(insetsStateWithIme(false), insetsSourceControl());
verifyZeroInteractions(mExecutor);
}
@Test
public void insetsChanged_schedulesNoWorkOnExecutor() {
+ Looper.prepare();
mPerDisplay.insetsChanged(insetsStateWithIme(false));
verifyZeroInteractions(mExecutor);
}
@@ -117,7 +136,10 @@
verifyZeroInteractions(mExecutor);
}
+ // With the refactor, the control's isInitiallyVisible is used to apply to the IME, therefore
+ // this test is obsolete
@Test
+ @RequiresFlagsDisabled(android.view.inputmethod.Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
public void reappliesVisibilityToChangedLeash() {
verifyZeroInteractions(mT);
mPerDisplay.mImeShowing = false;
@@ -136,6 +158,7 @@
@Test
public void insetsControlChanged_updateImeSourceControl() {
+ Looper.prepare();
mPerDisplay.insetsControlChanged(insetsStateWithIme(false), insetsSourceControl());
assertNotNull(mPerDisplay.mImeSourceControl);
@@ -143,6 +166,19 @@
assertNull(mPerDisplay.mImeSourceControl);
}
+ @Test
+ @RequiresFlagsEnabled(android.view.inputmethod.Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
+ public void setImeInputTargetRequestedVisibility_invokeOnImeRequested() {
+ var mockPp = mock(DisplayImeController.ImePositionProcessor.class);
+ mDisplayImeController.addPositionProcessor(mockPp);
+
+ mPerDisplay.setImeInputTargetRequestedVisibility(true);
+ verify(mockPp).onImeRequested(anyInt(), eq(true));
+
+ mPerDisplay.setImeInputTargetRequestedVisibility(false);
+ verify(mockPp).onImeRequested(anyInt(), eq(false));
+ }
+
private InsetsSourceControl[] insetsSourceControl() {
return new InsetsSourceControl[]{
new InsetsSourceControl(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
index 628c9cdd..3e9c732 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
@@ -98,7 +98,7 @@
private lateinit var mockitoSession: StaticMockitoSession
private lateinit var handler: DesktopActivityOrientationChangeHandler
private lateinit var shellInit: ShellInit
- private lateinit var taskRepository: DesktopModeTaskRepository
+ private lateinit var taskRepository: DesktopRepository
private lateinit var testScope: CoroutineScope
// Mock running tasks are registered here so we can get the list from mock shell task organizer.
private val runningTasks = mutableListOf<RunningTaskInfo>()
@@ -116,7 +116,7 @@
testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
shellInit = spy(ShellInit(testExecutor))
taskRepository =
- DesktopModeTaskRepository(context, shellInit, persistentRepository, testScope)
+ DesktopRepository(context, shellInit, persistentRepository, testScope)
whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenAnswer { runningTasks }
whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() }
whenever(runBlocking { persistentRepository.readDesktop(any(), any()) }).thenReturn(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopFullImmersiveTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopFullImmersiveTransitionHandlerTest.kt
new file mode 100644
index 0000000..cae6095
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopFullImmersiveTransitionHandlerTest.kt
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.desktopmode
+
+import android.os.IBinder
+import android.testing.AndroidTestingRunner
+import android.view.SurfaceControl
+import android.view.WindowManager.TRANSIT_CHANGE
+import android.window.WindowContainerTransaction
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestShellExecutor
+import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
+import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.transition.Transitions
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+/**
+ * Tests for [DesktopFullImmersiveTransitionHandler].
+ *
+ * Usage: atest WMShellUnitTests:DesktopFullImmersiveTransitionHandler
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DesktopFullImmersiveTransitionHandlerTest : ShellTestCase() {
+
+ @Mock private lateinit var mockTransitions: Transitions
+ private lateinit var desktopRepository: DesktopRepository
+ private val transactionSupplier = { SurfaceControl.Transaction() }
+
+ private lateinit var immersiveHandler: DesktopFullImmersiveTransitionHandler
+
+ @Before
+ fun setUp() {
+ desktopRepository = DesktopRepository(
+ context, ShellInit(TestShellExecutor()), mock(), mock()
+ )
+ immersiveHandler = DesktopFullImmersiveTransitionHandler(
+ transitions = mockTransitions,
+ desktopRepository = desktopRepository,
+ transactionSupplier = transactionSupplier
+ )
+ }
+
+ @Test
+ fun enterImmersive_transitionReady_updatesRepository() {
+ val task = createFreeformTask()
+ val wct = WindowContainerTransaction()
+ val mockBinder = mock(IBinder::class.java)
+ whenever(mockTransitions.startTransition(TRANSIT_CHANGE, wct, immersiveHandler))
+ .thenReturn(mockBinder)
+ desktopRepository.setTaskInFullImmersiveState(
+ displayId = task.displayId,
+ taskId = task.taskId,
+ immersive = false
+ )
+
+ immersiveHandler.enterImmersive(task, wct)
+ immersiveHandler.onTransitionReady(mockBinder)
+
+ assertThat(desktopRepository.isTaskInFullImmersiveState(task.taskId)).isTrue()
+ }
+
+ @Test
+ fun exitImmersive_transitionReady_updatesRepository() {
+ val task = createFreeformTask()
+ val wct = WindowContainerTransaction()
+ val mockBinder = mock(IBinder::class.java)
+ whenever(mockTransitions.startTransition(TRANSIT_CHANGE, wct, immersiveHandler))
+ .thenReturn(mockBinder)
+ desktopRepository.setTaskInFullImmersiveState(
+ displayId = task.displayId,
+ taskId = task.taskId,
+ immersive = true
+ )
+
+ immersiveHandler.exitImmersive(task, wct)
+ immersiveHandler.onTransitionReady(mockBinder)
+
+ assertThat(desktopRepository.isTaskInFullImmersiveState(task.taskId)).isFalse()
+ }
+
+ @Test
+ fun enterImmersive_inProgress_ignores() {
+ val task = createFreeformTask()
+ val wct = WindowContainerTransaction()
+ val mockBinder = mock(IBinder::class.java)
+ whenever(mockTransitions.startTransition(TRANSIT_CHANGE, wct, immersiveHandler))
+ .thenReturn(mockBinder)
+
+ immersiveHandler.enterImmersive(task, wct)
+ immersiveHandler.enterImmersive(task, wct)
+
+ verify(mockTransitions, times(1)).startTransition(TRANSIT_CHANGE, wct, immersiveHandler)
+ }
+
+ @Test
+ fun exitImmersive_inProgress_ignores() {
+ val task = createFreeformTask()
+ val wct = WindowContainerTransaction()
+ val mockBinder = mock(IBinder::class.java)
+ whenever(mockTransitions.startTransition(TRANSIT_CHANGE, wct, immersiveHandler))
+ .thenReturn(mockBinder)
+
+ immersiveHandler.exitImmersive(task, wct)
+ immersiveHandler.exitImmersive(task, wct)
+
+ verify(mockTransitions, times(1)).startTransition(TRANSIT_CHANGE, wct, immersiveHandler)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
index 2b60200..07de0716 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
@@ -60,7 +60,7 @@
class DesktopMixedTransitionHandlerTest : ShellTestCase() {
@Mock lateinit var transitions: Transitions
- @Mock lateinit var desktopTaskRepository: DesktopModeTaskRepository
+ @Mock lateinit var desktopRepository: DesktopRepository
@Mock lateinit var freeformTaskTransitionHandler: FreeformTaskTransitionHandler
@Mock lateinit var closeDesktopTaskTransitionHandler: CloseDesktopTaskTransitionHandler
@Mock lateinit var interactionJankMonitor: InteractionJankMonitor
@@ -75,7 +75,7 @@
DesktopMixedTransitionHandler(
context,
transitions,
- desktopTaskRepository,
+ desktopRepository,
freeformTaskTransitionHandler,
closeDesktopTaskTransitionHandler,
interactionJankMonitor,
@@ -144,7 +144,7 @@
fun startAnimation_withClosingDesktopTask_callsCloseTaskHandler() {
val transition = mock<IBinder>()
val transitionInfo = createTransitionInfo(task = createTask(WINDOWING_MODE_FREEFORM))
- whenever(desktopTaskRepository.getActiveNonMinimizedTaskCount(any())).thenReturn(2)
+ whenever(desktopRepository.getActiveNonMinimizedTaskCount(any())).thenReturn(2)
whenever(
closeDesktopTaskTransitionHandler.startAnimation(any(), any(), any(), any(), any())
)
@@ -167,7 +167,7 @@
fun startAnimation_withClosingLastDesktopTask_dispatchesTransition() {
val transition = mock<IBinder>()
val transitionInfo = createTransitionInfo(task = createTask(WINDOWING_MODE_FREEFORM))
- whenever(desktopTaskRepository.getActiveNonMinimizedTaskCount(any())).thenReturn(1)
+ whenever(desktopRepository.getActiveNonMinimizedTaskCount(any())).thenReturn(1)
whenever(transitions.dispatchTransition(any(), any(), any(), any(), any(), any()))
.thenReturn(mock())
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
index 2b7f86f..935e6d0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
@@ -17,8 +17,8 @@
package com.android.wm.shell.desktopmode
import android.app.ActivityManager.RunningTaskInfo
+import android.graphics.PointF
import android.graphics.Rect
-import android.graphics.Region
import android.testing.AndroidTestingRunner
import android.view.SurfaceControl
import androidx.test.filters.SmallTest
@@ -33,6 +33,7 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.kotlin.whenever
@@ -58,13 +59,15 @@
whenever(displayLayout.width()).thenReturn(DISPLAY_BOUNDS.width())
whenever(displayLayout.height()).thenReturn(DISPLAY_BOUNDS.height())
whenever(displayLayout.stableInsets()).thenReturn(STABLE_INSETS)
+ whenever(displayController.getDisplayLayout(anyInt())).thenReturn(displayLayout)
}
@Test
fun testFullscreenRegionCalculation() {
createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FULLSCREEN)
var testRegion = visualIndicator.calculateFullscreenRegion(displayLayout, CAPTION_HEIGHT)
- assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, 2 * STABLE_INSETS.top))
+ assertThat(testRegion.bounds).isEqualTo(Rect(0, Short.MIN_VALUE.toInt(), 2400,
+ 2 * STABLE_INSETS.top))
createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FREEFORM)
testRegion = visualIndicator.calculateFullscreenRegion(displayLayout, CAPTION_HEIGHT)
@@ -75,17 +78,19 @@
val toFullscreenWidth = displayLayout.width() * toFullscreenScale
assertThat(testRegion.bounds).isEqualTo(Rect(
(DISPLAY_BOUNDS.width() / 2f - toFullscreenWidth / 2f).toInt(),
- -50,
+ Short.MIN_VALUE.toInt(),
(DISPLAY_BOUNDS.width() / 2f + toFullscreenWidth / 2f).toInt(),
transitionHeight))
createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_SPLIT)
testRegion = visualIndicator.calculateFullscreenRegion(displayLayout, CAPTION_HEIGHT)
- assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, 2 * STABLE_INSETS.top))
+ assertThat(testRegion.bounds).isEqualTo(Rect(0, Short.MIN_VALUE.toInt(), 2400,
+ 2 * STABLE_INSETS.top))
createVisualIndicator(DesktopModeVisualIndicator.DragStartState.DRAGGED_INTENT)
testRegion = visualIndicator.calculateFullscreenRegion(displayLayout, CAPTION_HEIGHT)
- assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, transitionHeight))
+ assertThat(testRegion.bounds).isEqualTo(Rect(0, Short.MIN_VALUE.toInt(), 2400,
+ transitionHeight))
}
@Test
@@ -133,22 +138,19 @@
}
@Test
- fun testToDesktopRegionCalculation() {
+ fun testDefaultIndicators() {
createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FULLSCREEN)
- val fullscreenRegion = visualIndicator.calculateFullscreenRegion(displayLayout,
- CAPTION_HEIGHT)
- val splitLeftRegion = visualIndicator.calculateSplitLeftRegion(displayLayout,
- TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
- val splitRightRegion = visualIndicator.calculateSplitRightRegion(displayLayout,
- TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
- val desktopRegion = visualIndicator.calculateToDesktopRegion(displayLayout,
- splitLeftRegion, splitRightRegion, fullscreenRegion)
- var testRegion = Region()
- testRegion.union(DISPLAY_BOUNDS)
- testRegion.op(splitLeftRegion, Region.Op.DIFFERENCE)
- testRegion.op(splitRightRegion, Region.Op.DIFFERENCE)
- testRegion.op(fullscreenRegion, Region.Op.DIFFERENCE)
- assertThat(desktopRegion).isEqualTo(testRegion)
+ var result = visualIndicator.updateIndicatorType(PointF(-10000f, 500f))
+ assertThat(result).isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR)
+ createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_SPLIT)
+ result = visualIndicator.updateIndicatorType(PointF(10000f, 500f))
+ assertThat(result).isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR)
+ createVisualIndicator(DesktopModeVisualIndicator.DragStartState.DRAGGED_INTENT)
+ result = visualIndicator.updateIndicatorType(PointF(500f, 10000f))
+ assertThat(result).isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+ createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FREEFORM)
+ result = visualIndicator.updateIndicatorType(PointF(500f, 10000f))
+ assertThat(result).isEqualTo(DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR)
}
private fun createVisualIndicator(dragStartState: DesktopModeVisualIndicator.DragStartState) {
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/DesktopRepositoryTest.kt
similarity index 98%
rename from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
rename to libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
index 97ceecc..55b9724 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/DesktopRepositoryTest.kt
@@ -56,9 +56,9 @@
@SmallTest
@RunWith(AndroidTestingRunner::class)
@ExperimentalCoroutinesApi
-class DesktopModeTaskRepositoryTest : ShellTestCase() {
+class DesktopRepositoryTest : ShellTestCase() {
- private lateinit var repo: DesktopModeTaskRepository
+ private lateinit var repo: DesktopRepository
private lateinit var shellInit: ShellInit
private lateinit var datastoreScope: CoroutineScope
@@ -71,7 +71,7 @@
datastoreScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
shellInit = spy(ShellInit(testExecutor))
- repo = DesktopModeTaskRepository(context, shellInit, persistentRepository, datastoreScope)
+ repo = DesktopRepository(context, shellInit, persistentRepository, datastoreScope)
whenever(runBlocking { persistentRepository.readDesktop(any(), any()) }).thenReturn(
Desktop.getDefaultInstance()
)
@@ -940,7 +940,7 @@
assertThat(repo.isTaskInFullImmersiveState(taskId = 2)).isTrue()
}
- class TestListener : DesktopModeTaskRepository.ActiveTasksListener {
+ class TestListener : DesktopRepository.ActiveTasksListener {
var activeChangesOnDefaultDisplay = 0
var activeChangesOnSecondaryDisplay = 0
override fun onActiveTasksChanged(displayId: Int) {
@@ -952,7 +952,7 @@
}
}
- class TestVisibilityListener : DesktopModeTaskRepository.VisibleTasksListener {
+ class TestVisibilityListener : DesktopRepository.VisibleTasksListener {
var visibleTasksCountOnDefaultDisplay = 0
var visibleTasksCountOnSecondaryDisplay = 0
@@ -980,4 +980,4 @@
private const val DEFAULT_USER_ID = 1000
private const val DEFAULT_DESKTOP_ID = 0
}
-}
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 2ddb1ac..ae4772e 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
@@ -50,6 +50,7 @@
import android.view.DragEvent
import android.view.Gravity
import android.view.SurfaceControl
+import android.view.WindowInsets
import android.view.WindowManager
import android.view.WindowManager.TRANSIT_CHANGE
import android.view.WindowManager.TRANSIT_CLOSE
@@ -75,6 +76,7 @@
import com.android.internal.jank.InteractionJankMonitor
import com.android.window.flags.Flags
import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE
+import com.android.window.flags.Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP
import com.android.wm.shell.MockToken
import com.android.wm.shell.R
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
@@ -142,11 +144,13 @@
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
import org.mockito.Mockito.times
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.argThat
import org.mockito.kotlin.atLeastOnce
import org.mockito.kotlin.capture
import org.mockito.kotlin.eq
@@ -183,6 +187,8 @@
@Mock
lateinit var toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler
@Mock lateinit var dragToDesktopTransitionHandler: DragToDesktopTransitionHandler
+ @Mock
+ lateinit var mockDesktopFullImmersiveTransitionHandler: DesktopFullImmersiveTransitionHandler
@Mock lateinit var launchAdjacentController: LaunchAdjacentController
@Mock lateinit var splitScreenController: SplitScreenController
@Mock lateinit var recentsTransitionHandler: RecentsTransitionHandler
@@ -201,7 +207,7 @@
private lateinit var mockitoSession: StaticMockitoSession
private lateinit var controller: DesktopTasksController
private lateinit var shellInit: ShellInit
- private lateinit var taskRepository: DesktopModeTaskRepository
+ private lateinit var taskRepository: DesktopRepository
private lateinit var desktopTasksLimiter: DesktopTasksLimiter
private lateinit var recentsTransitionStateListener: RecentsTransitionStateListener
private lateinit var testScope: CoroutineScope
@@ -232,7 +238,7 @@
testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
shellInit = spy(ShellInit(testExecutor))
- taskRepository = DesktopModeTaskRepository(context, shellInit, persistentRepository, testScope)
+ taskRepository = DesktopRepository(context, shellInit, persistentRepository, testScope)
desktopTasksLimiter =
DesktopTasksLimiter(
transitions,
@@ -289,6 +295,7 @@
dragAndDropTransitionHandler,
toggleResizeDesktopTaskTransitionHandler,
dragToDesktopTransitionHandler,
+ mockDesktopFullImmersiveTransitionHandler,
taskRepository,
desktopModeLoggerTransitionObserver,
launchAdjacentController,
@@ -2643,13 +2650,17 @@
@Test
fun onDesktopDragMove_endsOutsideValidDragArea_snapsToValidBounds() {
val task = setUpFreeformTask()
+ val spyController = spy(controller)
val mockSurface = mock(SurfaceControl::class.java)
val mockDisplayLayout = mock(DisplayLayout::class.java)
whenever(displayController.getDisplayLayout(task.displayId)).thenReturn(mockDisplayLayout)
whenever(mockDisplayLayout.stableInsets()).thenReturn(Rect(0, 100, 2000, 2000))
- controller.onDragPositioningMove(task, mockSurface, 200f, Rect(100, -100, 500, 1000))
+ spyController.onDragPositioningMove(task, mockSurface, 200f, Rect(100, -100, 500, 1000))
- controller.onDragPositioningEnd(
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR)
+ spyController.onDragPositioningEnd(
task,
mockSurface,
Point(100, -100), /* position */
@@ -3119,6 +3130,54 @@
verify(shellController, times(1)).addUserChangeListener(any())
}
+ @Test
+ fun toggleImmersive_enter_resizesToDisplayBounds() {
+ val task = setUpFreeformTask(DEFAULT_DISPLAY)
+ taskRepository.setTaskInFullImmersiveState(DEFAULT_DISPLAY, task.taskId, false /* immersive */)
+
+ controller.toggleDesktopTaskFullImmersiveState(task)
+
+ verify(mockDesktopFullImmersiveTransitionHandler).enterImmersive(eq(task), argThat { wct ->
+ wct.hasBoundsChange(task.token, Rect())
+ })
+ }
+
+ @Test
+ fun toggleImmersive_exit_resizesToStableBounds() {
+ val task = setUpFreeformTask(DEFAULT_DISPLAY)
+ taskRepository.setTaskInFullImmersiveState(DEFAULT_DISPLAY, task.taskId, true /* immersive */)
+
+ controller.toggleDesktopTaskFullImmersiveState(task)
+
+ verify(mockDesktopFullImmersiveTransitionHandler).exitImmersive(eq(task), argThat { wct ->
+ wct.hasBoundsChange(task.token, STABLE_BOUNDS)
+ })
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ fun onTaskInfoChanged_inImmersiveUnrequestsImmersive_exits() {
+ val task = setUpFreeformTask(DEFAULT_DISPLAY)
+ taskRepository.setTaskInFullImmersiveState(DEFAULT_DISPLAY, task.taskId, immersive = true)
+
+ task.requestedVisibleTypes = WindowInsets.Type.statusBars()
+ controller.onTaskInfoChanged(task)
+
+ verify(mockDesktopFullImmersiveTransitionHandler).exitImmersive(eq(task), any())
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ fun onTaskInfoChanged_notInImmersiveUnrequestsImmersive_noReExit() {
+ val task = setUpFreeformTask(DEFAULT_DISPLAY)
+ taskRepository.setTaskInFullImmersiveState(DEFAULT_DISPLAY, task.taskId, immersive = false)
+
+ task.requestedVisibleTypes = WindowInsets.Type.statusBars()
+ controller.onTaskInfoChanged(task)
+
+ verify(mockDesktopFullImmersiveTransitionHandler, never()).exitImmersive(eq(task), any())
+ }
+
/**
* Assert that an unhandled drag event launches a PendingIntent with the
* windowing mode and bounds we are expecting.
@@ -3484,6 +3543,13 @@
.isEqualTo(windowingMode)
}
+private fun WindowContainerTransaction.hasBoundsChange(
+ token: WindowContainerToken,
+ bounds: Rect
+): Boolean = this.changes.any { change ->
+ change.key == token.asBinder() && change.value.configuration.windowConfiguration.bounds == bounds
+}
+
private fun WindowContainerTransaction?.anyDensityConfigChange(
token: WindowContainerToken
): Boolean {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
index bdcb459..596b76d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
@@ -92,7 +92,7 @@
private lateinit var mockitoSession: StaticMockitoSession
private lateinit var desktopTasksLimiter: DesktopTasksLimiter
- private lateinit var desktopTaskRepo: DesktopModeTaskRepository
+ private lateinit var desktopTaskRepo: DesktopRepository
private lateinit var shellInit: ShellInit
private lateinit var testScope: CoroutineScope
@@ -106,7 +106,7 @@
testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
desktopTaskRepo =
- DesktopModeTaskRepository(context, shellInit, persistentRepository, testScope)
+ DesktopRepository(context, shellInit, persistentRepository, testScope)
desktopTasksLimiter =
DesktopTasksLimiter(transitions, desktopTaskRepo, shellTaskOrganizer, MAX_TASK_LIMIT,
interactionJankMonitor, mContext, handler)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
index 42fcc83..598df34 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
@@ -62,7 +62,7 @@
private val transitions = mock<Transitions>()
private val context = mock<Context>()
private val shellTaskOrganizer = mock<ShellTaskOrganizer>()
- private val taskRepository = mock<DesktopModeTaskRepository>()
+ private val taskRepository = mock<DesktopRepository>()
private lateinit var transitionObserver: DesktopTasksTransitionObserver
private lateinit var shellInit: ShellInit
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt
index 5596ad7..1e105d9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt
@@ -46,6 +46,7 @@
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
import org.junit.Before
+import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -61,10 +62,7 @@
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
-/**
- * Tests of [AppHandleEducationController]
- * Usage: atest AppHandleEducationControllerTest
- */
+/** Tests of [AppHandleEducationController] Usage: atest AppHandleEducationControllerTest */
@SmallTest
@RunWith(AndroidTestingRunner::class)
@OptIn(ExperimentalCoroutinesApi::class)
@@ -220,6 +218,7 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+ @Ignore("b/371527084: revisit testcase after refactoring original logic")
fun showWindowingImageButtonTooltip_appHandleExpanded_shouldCallShowEducationTooltipTwice() =
testScope.runTest {
// After first tooltip is dismissed, app handle is expanded. Should show second education
@@ -237,6 +236,7 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+ @Ignore("b/371527084: revisit testcase after refactoring original logic")
fun showWindowingImageButtonTooltip_appHandleExpandedAfterTimeout_shouldCallShowEducationTooltipOnce() =
testScope.runTest {
// After first tooltip is dismissed, app handle is expanded after timeout. Should not show
@@ -258,6 +258,7 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+ @Ignore("b/371527084: revisit testcase after refactoring original logic")
fun showWindowingImageButtonTooltip_appHandleExpandedTwice_shouldCallShowEducationTooltipTwice() =
testScope.runTest {
// After first tooltip is dismissed, app handle is expanded twice. Should show second
@@ -279,6 +280,7 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+ @Ignore("b/371527084: revisit testcase after refactoring original logic")
fun showWindowingImageButtonTooltip_appHandleNotExpanded_shouldCallShowEducationTooltipOnce() =
testScope.runTest {
// After first tooltip is dismissed, app handle is not expanded. Should not show second
@@ -296,6 +298,7 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+ @Ignore("b/371527084: revisit testcase after refactoring original logic")
fun showExitWindowingButtonTooltip_appHeaderVisible_shouldCallShowEducationTooltipThrice() =
testScope.runTest {
// After first two tooltips are dismissed, app header is visible. Should show third
@@ -313,6 +316,7 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+ @Ignore("b/371527084: revisit testcase after refactoring original logic")
fun showExitWindowingButtonTooltip_appHeaderVisibleAfterTimeout_shouldCallShowEducationTooltipTwice() =
testScope.runTest {
// After first two tooltips are dismissed, app header is visible after timeout. Should not
@@ -334,6 +338,7 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+ @Ignore("b/371527084: revisit testcase after refactoring original logic")
fun showExitWindowingButtonTooltip_appHeaderVisibleTwice_shouldCallShowEducationTooltipThrice() =
testScope.runTest {
// After first two tooltips are dismissed, app header is visible twice. Should show third
@@ -354,6 +359,7 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+ @Ignore("b/371527084: revisit testcase after refactoring original logic")
fun showExitWindowingButtonTooltip_appHeaderExpanded_shouldCallShowEducationTooltipTwice() =
testScope.runTest {
// After first two tooltips are dismissed, app header is visible but expanded. Should not
@@ -393,6 +399,7 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+ @Ignore("b/371527084: revisit testcase after refactoring original logic")
fun setAppHandleEducationTooltipCallbacks_onWindowingImageButtonTooltipClicked_callbackInvoked() =
testScope.runTest {
// After first tooltip is dismissed, app handle is expanded. Should show second education
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
index 3b2c7e6..36e0427 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
@@ -41,7 +41,8 @@
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
import com.android.wm.shell.common.LaunchAdjacentController;
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.desktopmode.DesktopRepository;
+import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
@@ -73,7 +74,9 @@
@Mock
private SurfaceControl mMockSurfaceControl;
@Mock
- private DesktopModeTaskRepository mDesktopModeTaskRepository;
+ private DesktopRepository mDesktopRepository;
+ @Mock
+ private DesktopTasksController mDesktopTasksController;
@Mock
private LaunchAdjacentController mLaunchAdjacentController;
private FreeformTaskListener mFreeformTaskListener;
@@ -89,7 +92,8 @@
mContext,
mShellInit,
mTaskOrganizer,
- Optional.of(mDesktopModeTaskRepository),
+ Optional.of(mDesktopRepository),
+ Optional.of(mDesktopTasksController),
mLaunchAdjacentController,
mWindowDecorViewModel);
}
@@ -102,7 +106,7 @@
mFreeformTaskListener.onFocusTaskChanged(task);
- verify(mDesktopModeTaskRepository)
+ verify(mDesktopRepository)
.addOrMoveFreeformTaskToTop(task.displayId, task.taskId);
}
@@ -114,7 +118,7 @@
mFreeformTaskListener.onFocusTaskChanged(fullscreenTask);
- verify(mDesktopModeTaskRepository, never())
+ verify(mDesktopRepository, never())
.addOrMoveFreeformTaskToTop(fullscreenTask.displayId, fullscreenTask.taskId);
}
@@ -156,7 +160,7 @@
task.displayId = INVALID_DISPLAY;
mFreeformTaskListener.onTaskVanished(task);
- verify(mDesktopModeTaskRepository).minimizeTask(task.displayId, task.taskId);
+ verify(mDesktopRepository).minimizeTask(task.displayId, task.taskId);
}
@Test
@@ -168,13 +172,25 @@
mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl);
- when(mDesktopModeTaskRepository.isClosingTask(task.taskId)).thenReturn(true);
+ when(mDesktopRepository.isClosingTask(task.taskId)).thenReturn(true);
task.isVisible = false;
task.displayId = INVALID_DISPLAY;
mFreeformTaskListener.onTaskVanished(task);
- verify(mDesktopModeTaskRepository, never()).minimizeTask(task.displayId, task.taskId);
- verify(mDesktopModeTaskRepository).removeFreeformTask(task.displayId, task.taskId);
+ verify(mDesktopRepository, never()).minimizeTask(task.displayId, task.taskId);
+ verify(mDesktopRepository).removeFreeformTask(task.displayId, task.taskId);
+ }
+
+ @Test
+ public void onTaskInfoChanged_withDesktopController_forwards() {
+ ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder()
+ .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
+ task.isVisible = true;
+ mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl);
+
+ mFreeformTaskListener.onTaskInfoChanged(task);
+
+ verify(mDesktopTasksController).onTaskInfoChanged(task);
}
@After
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
index 499e339..da95315 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
@@ -17,8 +17,12 @@
package com.android.wm.shell.freeform;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.view.WindowManager.TRANSIT_CHANGE;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -30,6 +34,8 @@
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.IBinder;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.view.SurfaceControl;
import android.window.IWindowContainerToken;
import android.window.TransitionInfo;
@@ -37,30 +43,43 @@
import androidx.test.filters.SmallTest;
+import com.android.window.flags.Flags;
+
+import com.android.wm.shell.desktopmode.DesktopTaskChangeListener;
+import com.android.wm.shell.desktopmode.DesktopFullImmersiveTransitionHandler;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.TransitionInfoBuilder;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
+import java.util.Optional;
+
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.Optional;
+
/**
- * Tests of {@link FreeformTaskTransitionObserver}
+ * Tests for {@link FreeformTaskTransitionObserver}.
*/
@SmallTest
public class FreeformTaskTransitionObserverTest {
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Mock
private ShellInit mShellInit;
@Mock
private Transitions mTransitions;
@Mock
+ private DesktopFullImmersiveTransitionHandler mDesktopFullImmersiveTransitionHandler;
+ @Mock
private WindowDecorViewModel mWindowDecorViewModel;
-
+ @Mock
+ private TaskChangeListener mTaskChangeListener;
private FreeformTaskTransitionObserver mTransitionObserver;
@Before
@@ -69,12 +88,14 @@
PackageManager pm = mock(PackageManager.class);
doReturn(true).when(pm).hasSystemFeature(
- PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT);
+ PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT);
final Context context = mock(Context.class);
doReturn(pm).when(context).getPackageManager();
mTransitionObserver = new FreeformTaskTransitionObserver(
- context, mShellInit, mTransitions, mWindowDecorViewModel);
+ context, mShellInit, mTransitions,
+ Optional.of(mDesktopFullImmersiveTransitionHandler),
+ mWindowDecorViewModel, Optional.of(mTaskChangeListener));
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
final ArgumentCaptor<Runnable> initRunnableCaptor = ArgumentCaptor.forClass(
Runnable.class);
@@ -87,12 +108,12 @@
}
@Test
- public void testRegistersObserverAtInit() {
+ public void init_registersObserver() {
verify(mTransitions).registerObserver(same(mTransitionObserver));
}
@Test
- public void testCreatesWindowDecorOnOpenTransition_freeform() {
+ public void openTransition_createsWindowDecor() {
final TransitionInfo.Change change =
createChange(TRANSIT_OPEN, 1, WINDOWING_MODE_FREEFORM);
final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0)
@@ -109,7 +130,71 @@
}
@Test
- public void testPreparesWindowDecorOnCloseTransition_freeform() {
+ public void openTransition_notifiesOnTaskOpening() {
+ final TransitionInfo.Change change =
+ createChange(TRANSIT_OPEN, 1, WINDOWING_MODE_FREEFORM);
+ final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0)
+ .addChange(change).build();
+
+ final IBinder transition = mock(IBinder.class);
+ final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+ final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+ mTransitionObserver.onTransitionReady(transition, info, startT, finishT);
+ mTransitionObserver.onTransitionStarting(transition);
+
+ verify(mTaskChangeListener).onTaskOpening(change.getTaskInfo());
+ }
+
+ @Test
+ public void toFrontTransition_notifiesOnTaskMovingToFront() {
+ final TransitionInfo.Change change =
+ createChange(TRANSIT_TO_FRONT, /* taskId= */ 1, WINDOWING_MODE_FREEFORM);
+ final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_TO_FRONT, /* flags= */ 0)
+ .addChange(change).build();
+
+ final IBinder transition = mock(IBinder.class);
+ final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+ final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+ mTransitionObserver.onTransitionReady(transition, info, startT, finishT);
+ mTransitionObserver.onTransitionStarting(transition);
+
+ verify(mTaskChangeListener).onTaskMovingToFront(change.getTaskInfo());
+ }
+
+ @Test
+ public void toBackTransition_notifiesOnTaskMovingToBack() {
+ final TransitionInfo.Change change =
+ createChange(TRANSIT_TO_BACK, /* taskId= */ 1, WINDOWING_MODE_FREEFORM);
+ final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_TO_BACK, /* flags= */ 0)
+ .addChange(change).build();
+
+ final IBinder transition = mock(IBinder.class);
+ final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+ final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+ mTransitionObserver.onTransitionReady(transition, info, startT, finishT);
+ mTransitionObserver.onTransitionStarting(transition);
+
+ verify(mTaskChangeListener).onTaskMovingToBack(change.getTaskInfo());
+ }
+
+ @Test
+ public void changeTransition_notifiesOnTaskChanging() {
+ final TransitionInfo.Change change =
+ createChange(TRANSIT_CHANGE, /* taskId= */ 1, WINDOWING_MODE_FREEFORM);
+ final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_CHANGE, /* flags= */ 0)
+ .addChange(change).build();
+
+ final IBinder transition = mock(IBinder.class);
+ final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+ final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+ mTransitionObserver.onTransitionReady(transition, info, startT, finishT);
+ mTransitionObserver.onTransitionStarting(transition);
+
+ verify(mTaskChangeListener).onTaskChanging(change.getTaskInfo());
+ }
+
+ @Test
+ public void closeTransition_preparesWindowDecor() {
final TransitionInfo.Change change =
createChange(TRANSIT_CLOSE, 1, WINDOWING_MODE_FREEFORM);
final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_CLOSE, 0)
@@ -126,7 +211,23 @@
}
@Test
- public void testDoesntCloseWindowDecorDuringCloseTransition() throws Exception {
+ public void closeTransition_notifiesOnTaskClosing() {
+ final TransitionInfo.Change change =
+ createChange(TRANSIT_CLOSE, 1, WINDOWING_MODE_FREEFORM);
+ final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_CLOSE, 0)
+ .addChange(change).build();
+
+ final IBinder transition = mock(IBinder.class);
+ final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+ final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+ mTransitionObserver.onTransitionReady(transition, info, startT, finishT);
+ mTransitionObserver.onTransitionStarting(transition);
+
+ verify(mTaskChangeListener).onTaskClosing(change.getTaskInfo());
+ }
+
+ @Test
+ public void closeTransition_doesntCloseWindowDecorDuringTransition() throws Exception {
final TransitionInfo.Change change =
createChange(TRANSIT_CLOSE, 1, WINDOWING_MODE_FREEFORM);
final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_CLOSE, 0)
@@ -142,7 +243,7 @@
}
@Test
- public void testClosesWindowDecorAfterCloseTransition() throws Exception {
+ public void closeTransition_closesWindowDecorAfterTransition() throws Exception {
final TransitionInfo.Change change =
createChange(TRANSIT_CLOSE, 1, WINDOWING_MODE_FREEFORM);
final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_CLOSE, 0)
@@ -161,7 +262,7 @@
}
@Test
- public void testClosesMergedWindowDecorationAfterTransitionFinishes() throws Exception {
+ public void transitionFinished_closesMergedWindowDecoration() throws Exception {
// The playing transition
final TransitionInfo.Change change1 =
createChange(TRANSIT_OPEN, 1, WINDOWING_MODE_FREEFORM);
@@ -192,7 +293,7 @@
}
@Test
- public void testClosesAllWindowDecorsOnTransitionMergeAfterCloseTransitions() throws Exception {
+ public void closeTransition_closesWindowDecorsOnTransitionMerge() throws Exception {
// The playing transition
final TransitionInfo.Change change1 =
createChange(TRANSIT_CLOSE, 1, WINDOWING_MODE_FREEFORM);
@@ -223,6 +324,19 @@
verify(mWindowDecorViewModel).destroyWindowDecoration(change2.getTaskInfo());
}
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ public void onTransitionReady_forwardsToDesktopImmersiveHandler() {
+ final IBinder transition = mock(IBinder.class);
+ final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_CHANGE, 0).build();
+ final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+ final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+
+ mTransitionObserver.onTransitionReady(transition, info, startT, finishT);
+
+ verify(mDesktopFullImmersiveTransitionHandler).onTransitionReady(transition);
+ }
+
private static TransitionInfo.Change createChange(int mode, int taskId, int windowingMode) {
final ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
taskInfo.taskId = taskId;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index 0364b51..9b73d53 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -70,7 +70,7 @@
import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.TaskStackListenerImpl;
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.shared.GroupedRecentTaskInfo;
import com.android.wm.shell.shared.ShellSharedConstants;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
@@ -109,7 +109,7 @@
@Mock
private ShellCommandHandler mShellCommandHandler;
@Mock
- private DesktopModeTaskRepository mDesktopModeTaskRepository;
+ private DesktopRepository mDesktopRepository;
@Mock
private ActivityTaskManager mActivityTaskManager;
@Mock
@@ -146,7 +146,7 @@
mDisplayInsetsController, mMainExecutor));
mRecentTasksControllerReal = new RecentTasksController(mContext, mShellInit,
mShellController, mShellCommandHandler, mTaskStackListener, mActivityTaskManager,
- Optional.of(mDesktopModeTaskRepository), mTaskStackTransitionObserver,
+ Optional.of(mDesktopRepository), mTaskStackTransitionObserver,
mMainExecutor);
mRecentTasksController = spy(mRecentTasksControllerReal);
mShellTaskOrganizer = new ShellTaskOrganizer(mShellInit, mShellCommandHandler,
@@ -305,8 +305,8 @@
ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4);
setRawList(t1, t2, t3, t4);
- when(mDesktopModeTaskRepository.isActiveTask(1)).thenReturn(true);
- when(mDesktopModeTaskRepository.isActiveTask(3)).thenReturn(true);
+ when(mDesktopRepository.isActiveTask(1)).thenReturn(true);
+ when(mDesktopRepository.isActiveTask(3)).thenReturn(true);
ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(
MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
@@ -344,8 +344,8 @@
new SplitBounds(new Rect(), new Rect(), 1, 2, SNAP_TO_2_50_50);
mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, pair1Bounds);
- when(mDesktopModeTaskRepository.isActiveTask(3)).thenReturn(true);
- when(mDesktopModeTaskRepository.isActiveTask(5)).thenReturn(true);
+ when(mDesktopRepository.isActiveTask(3)).thenReturn(true);
+ when(mDesktopRepository.isActiveTask(5)).thenReturn(true);
ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(
MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
@@ -384,8 +384,8 @@
ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4);
setRawList(t1, t2, t3, t4);
- when(mDesktopModeTaskRepository.isActiveTask(1)).thenReturn(true);
- when(mDesktopModeTaskRepository.isActiveTask(3)).thenReturn(true);
+ when(mDesktopRepository.isActiveTask(1)).thenReturn(true);
+ when(mDesktopRepository.isActiveTask(3)).thenReturn(true);
ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(
MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
@@ -412,10 +412,10 @@
ActivityManager.RecentTaskInfo t5 = makeTaskInfo(5);
setRawList(t1, t2, t3, t4, t5);
- when(mDesktopModeTaskRepository.isActiveTask(1)).thenReturn(true);
- when(mDesktopModeTaskRepository.isActiveTask(3)).thenReturn(true);
- when(mDesktopModeTaskRepository.isActiveTask(5)).thenReturn(true);
- when(mDesktopModeTaskRepository.isMinimizedTask(3)).thenReturn(true);
+ when(mDesktopRepository.isActiveTask(1)).thenReturn(true);
+ when(mDesktopRepository.isActiveTask(3)).thenReturn(true);
+ when(mDesktopRepository.isActiveTask(5)).thenReturn(true);
+ when(mDesktopRepository.isMinimizedTask(3)).thenReturn(true);
ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(
MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
@@ -452,8 +452,8 @@
t2.lastNonFullscreenBounds = new Rect(150, 250, 350, 450);
setRawList(t1, t2);
- when(mDesktopModeTaskRepository.isActiveTask(1)).thenReturn(true);
- when(mDesktopModeTaskRepository.isActiveTask(2)).thenReturn(true);
+ when(mDesktopRepository.isActiveTask(1)).thenReturn(true);
+ when(mDesktopRepository.isActiveTask(2)).thenReturn(true);
ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(
MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
index 769acf7..0effc3e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
@@ -48,7 +48,7 @@
import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.TaskStackListenerImpl;
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
@@ -82,7 +82,7 @@
@Mock
private ShellCommandHandler mShellCommandHandler;
@Mock
- private DesktopModeTaskRepository mDesktopModeTaskRepository;
+ private DesktopRepository mDesktopRepository;
@Mock
private ActivityTaskManager mActivityTaskManager;
@Mock
@@ -120,7 +120,7 @@
mDisplayInsetsController, mMainExecutor));
mRecentTasksControllerReal = new RecentTasksController(mContext, mShellInit,
mShellController, mShellCommandHandler, mTaskStackListener, mActivityTaskManager,
- Optional.of(mDesktopModeTaskRepository), mTaskStackTransitionObserver,
+ Optional.of(mDesktopRepository), mTaskStackTransitionObserver,
mMainExecutor);
mRecentTasksController = spy(mRecentTasksControllerReal);
mShellTaskOrganizer = new ShellTaskOrganizer(mShellInit, mShellCommandHandler,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/BubbleInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/BubbleInfoTest.kt
index 641063c..205defe 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/BubbleInfoTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/BubbleInfoTest.kt
@@ -16,6 +16,8 @@
package com.android.wm.shell.shared.bubbles
+import android.graphics.drawable.Icon
+import android.net.Uri
import android.os.Parcel
import android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE
import android.testing.AndroidTestingRunner
@@ -42,7 +44,12 @@
"title",
"Some app",
true,
- true
+ true,
+ ParcelableFlyoutMessage(
+ Icon.createWithContentUri(Uri.parse("content://image/123")),
+ "sender",
+ "message"
+ )
)
val parcel = Parcel.obtain()
bubbleInfo.writeToParcel(parcel, PARCELABLE_WRITE_RETURN_VALUE)
@@ -60,5 +67,10 @@
assertThat(bubbleInfo.appName).isEqualTo(bubbleInfoFromParcel.appName)
assertThat(bubbleInfo.isImportantConversation)
.isEqualTo(bubbleInfoFromParcel.isImportantConversation)
+ with(bubbleInfo.parcelableFlyoutMessage!!) {
+ assertThat(icon!!.uri.toString()).isEqualTo("content://image/123")
+ assertThat(title).isEqualTo("sender")
+ assertThat(message).isEqualTo("message")
+ }
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index 5ae4ca8..4aa7e18 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -87,7 +87,7 @@
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository
+import com.android.wm.shell.desktopmode.DesktopRepository
import com.android.wm.shell.desktopmode.DesktopTasksController
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition
import com.android.wm.shell.desktopmode.DesktopTasksLimiter
@@ -161,7 +161,7 @@
@Mock private lateinit var mockTaskOrganizer: ShellTaskOrganizer
@Mock private lateinit var mockDisplayController: DisplayController
@Mock private lateinit var mockSplitScreenController: SplitScreenController
- @Mock private lateinit var mockDesktopRepository: DesktopModeTaskRepository
+ @Mock private lateinit var mockDesktopRepository: DesktopRepository
@Mock private lateinit var mockDisplayLayout: DisplayLayout
@Mock private lateinit var displayInsetsController: DisplayInsetsController
@Mock private lateinit var mockSyncQueue: SyncTransactionQueue
@@ -1221,9 +1221,48 @@
assertEquals(decor.mTaskInfo.token.asBinder(), wct.getHierarchyOps().get(0).getContainer())
}
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ fun testMaximizeButtonClick_requestingImmersive_togglesDesktopImmersiveState() {
+ val onClickListenerCaptor = forClass(View.OnClickListener::class.java)
+ as ArgumentCaptor<View.OnClickListener>
+ val decor = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_FREEFORM,
+ onCaptionButtonClickListener = onClickListenerCaptor,
+ requestingImmersive = true,
+ )
+ val view = mock(View::class.java)
+ whenever(view.id).thenReturn(R.id.maximize_window)
+
+ onClickListenerCaptor.value.onClick(view)
+
+ verify(mockDesktopTasksController)
+ .toggleDesktopTaskFullImmersiveState(decor.mTaskInfo)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ fun testMaximizeButtonClick_notRequestingImmersive_togglesDesktopTaskSize() {
+ val onClickListenerCaptor = forClass(View.OnClickListener::class.java)
+ as ArgumentCaptor<View.OnClickListener>
+ val decor = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_FREEFORM,
+ onCaptionButtonClickListener = onClickListenerCaptor,
+ requestingImmersive = false,
+ )
+ val view = mock(View::class.java)
+ whenever(view.id).thenReturn(R.id.maximize_window)
+
+ onClickListenerCaptor.value.onClick(view)
+
+ verify(mockDesktopTasksController)
+ .toggleDesktopTaskSize(decor.mTaskInfo)
+ }
+
private fun createOpenTaskDecoration(
@WindowingMode windowingMode: Int,
taskSurface: SurfaceControl = SurfaceControl(),
+ requestingImmersive: Boolean = false,
onMaxOrRestoreListenerCaptor: ArgumentCaptor<Function0<Unit>> =
forClass(Function0::class.java) as ArgumentCaptor<Function0<Unit>>,
onLeftSnapClickListenerCaptor: ArgumentCaptor<Function0<Unit>> =
@@ -1243,7 +1282,10 @@
onCaptionButtonTouchListener: ArgumentCaptor<View.OnTouchListener> =
forClass(View.OnTouchListener::class.java) as ArgumentCaptor<View.OnTouchListener>
): DesktopModeWindowDecoration {
- val decor = setUpMockDecorationForTask(createTask(windowingMode = windowingMode))
+ val decor = setUpMockDecorationForTask(createTask(
+ windowingMode = windowingMode,
+ requestingImmersive = requestingImmersive
+ ))
onTaskOpening(decor.mTaskInfo, taskSurface)
verify(decor).setOnMaximizeOrRestoreClickListener(onMaxOrRestoreListenerCaptor.capture())
verify(decor).setOnLeftSnapClickListener(onLeftSnapClickListenerCaptor.capture())
@@ -1282,6 +1324,7 @@
activityType: Int = ACTIVITY_TYPE_STANDARD,
focused: Boolean = true,
activityInfo: ActivityInfo = ActivityInfo(),
+ requestingImmersive: Boolean = false
): RunningTaskInfo {
return TestRunningTaskInfoBuilder()
.setDisplayId(displayId)
@@ -1292,6 +1335,11 @@
topActivityInfo = activityInfo
isFocused = focused
isResizeable = true
+ requestedVisibleTypes = if (requestingImmersive) {
+ statusBars().inv()
+ } else {
+ statusBars()
+ }
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index ed752fd..35be80e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -106,7 +106,7 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.desktopmode.CaptionState;
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -163,7 +163,7 @@
@Mock
private ShellTaskOrganizer mMockShellTaskOrganizer;
@Mock
- private DesktopModeTaskRepository mMockDesktopRepository;
+ private DesktopRepository mMockDesktopRepository;
@Mock
private Choreographer mMockChoreographer;
@Mock
@@ -1046,6 +1046,7 @@
any(),
openInBrowserCaptor.capture(),
any(),
+ any(),
any()
);
openInBrowserCaptor.getValue().invoke(new Intent(Intent.ACTION_MAIN, TEST_URI1));
@@ -1074,6 +1075,7 @@
any(),
openInBrowserCaptor.capture(),
any(),
+ any(),
any()
);
@@ -1124,6 +1126,7 @@
any(),
any(),
any(),
+ any(),
closeClickListener.capture(),
any()
);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
index cabd472..1820133 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
@@ -240,7 +240,7 @@
null /* openInBrowserLink */, captionWidth = HANDLE_WIDTH, captionHeight = 50,
captionX = captionX
)
- handleMenu.show(mock(), mock(), mock(), mock(), mock(), mock(), mock(), mock())
+ handleMenu.show(mock(), mock(), mock(), mock(), mock(), mock(), mock(), mock(), mock())
return handleMenu
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationTooltipControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationTooltipControllerTest.kt
index 6749776..741dfb8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationTooltipControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationTooltipControllerTest.kt
@@ -30,12 +30,15 @@
import android.view.WindowManager
import android.widget.TextView
import android.window.WindowContainerTransaction
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.toArgb
import androidx.test.filters.SmallTest
import com.android.wm.shell.R
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationTooltipController.TooltipArrowDirection
+import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationTooltipController.TooltipColorScheme
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
@@ -240,14 +243,42 @@
/* fromRotation= */ ROTATION_90,
/* toRotation= */ ROTATION_180,
/* newDisplayAreaInfo= */ null,
- WindowContainerTransaction())
+ WindowContainerTransaction(),
+ )
verify(mockPopupWindow, times(1)).releaseView()
verify(mockDisplayController, atLeastOnce()).removeDisplayChangingController(any())
}
+ @Test
+ fun showEducationTooltip_setTooltipColorScheme_correctColorsAreSet() {
+ val tooltipColorScheme =
+ TooltipColorScheme(
+ container = Color.Red.toArgb(), text = Color.Blue.toArgb(), icon = Color.Green.toArgb())
+ val tooltipViewConfig = createTooltipConfig(tooltipColorScheme = tooltipColorScheme)
+
+ tooltipController.showEducationTooltip(tooltipViewConfig = tooltipViewConfig, taskId = 123)
+
+ verify(mockViewContainerFactory, times(1))
+ .create(
+ windowManagerWrapper = any(),
+ taskId = anyInt(),
+ x = anyInt(),
+ y = anyInt(),
+ width = anyInt(),
+ height = anyInt(),
+ flags = anyInt(),
+ view = tooltipViewArgumentCaptor.capture())
+ val tooltipTextView =
+ tooltipViewArgumentCaptor.lastValue.findViewById<TextView>(R.id.tooltip_text)
+ assertThat(tooltipTextView.textColors.defaultColor).isEqualTo(Color.Blue.toArgb())
+ }
+
private fun createTooltipConfig(
@LayoutRes tooltipViewLayout: Int = R.layout.desktop_windowing_education_top_arrow_tooltip,
+ tooltipColorScheme: TooltipColorScheme =
+ TooltipColorScheme(
+ container = Color.Red.toArgb(), text = Color.Red.toArgb(), icon = Color.Red.toArgb()),
tooltipViewGlobalCoordinates: Point = Point(0, 0),
tooltipText: String = "This is a tooltip",
arrowDirection: TooltipArrowDirection = TooltipArrowDirection.UP,
@@ -256,6 +287,7 @@
) =
DesktopWindowingEducationTooltipController.EducationViewConfig(
tooltipViewLayout = tooltipViewLayout,
+ tooltipColorScheme = tooltipColorScheme,
tooltipViewGlobalCoordinates = tooltipViewGlobalCoordinates,
tooltipText = tooltipText,
arrowDirection = arrowDirection,
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp
index a39f30b..385fbfe 100644
--- a/libs/androidfw/Android.bp
+++ b/libs/androidfw/Android.bp
@@ -75,6 +75,7 @@
"BigBufferStream.cpp",
"ChunkIterator.cpp",
"ConfigDescription.cpp",
+ "CursorWindow.cpp",
"FileStream.cpp",
"Idmap.cpp",
"LoadedArsc.cpp",
@@ -113,7 +114,6 @@
srcs: [
"BackupData.cpp",
"BackupHelpers.cpp",
- "CursorWindow.cpp",
],
shared_libs: [
"libbase",
@@ -147,11 +147,6 @@
"libz",
],
},
- host_linux: {
- srcs: [
- "CursorWindow.cpp",
- ],
- },
windows: {
enabled: true,
},
diff --git a/libs/androidfw/CursorWindow.cpp b/libs/androidfw/CursorWindow.cpp
index cbb1e8f..abf2b0a 100644
--- a/libs/androidfw/CursorWindow.cpp
+++ b/libs/androidfw/CursorWindow.cpp
@@ -139,6 +139,7 @@
return UNKNOWN_ERROR;
}
+#ifdef __linux__
status_t CursorWindow::createFromParcel(Parcel* parcel, CursorWindow** outWindow) {
*outWindow = nullptr;
@@ -240,6 +241,7 @@
fail_silent:
return UNKNOWN_ERROR;
}
+#endif
status_t CursorWindow::clear() {
if (mReadOnly) {
diff --git a/libs/androidfw/include/androidfw/CursorWindow.h b/libs/androidfw/include/androidfw/CursorWindow.h
index c2eac12..0996355 100644
--- a/libs/androidfw/include/androidfw/CursorWindow.h
+++ b/libs/androidfw/include/androidfw/CursorWindow.h
@@ -23,7 +23,9 @@
#include <string>
#include "android-base/stringprintf.h"
+#ifdef __linux__
#include "binder/Parcel.h"
+#endif
#include "utils/String8.h"
#include "android-base/mapped_file.h"
@@ -82,9 +84,11 @@
~CursorWindow();
static status_t create(const String8& name, size_t size, CursorWindow** outCursorWindow);
+#ifdef __linux__
static status_t createFromParcel(Parcel* parcel, CursorWindow** outCursorWindow);
status_t writeToParcel(Parcel* parcel);
+#endif
inline String8 name() { return mName; }
inline size_t size() { return mSize; }
diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp
index ede385a..9cd6e25 100644
--- a/libs/hwui/hwui/MinikinUtils.cpp
+++ b/libs/hwui/hwui/MinikinUtils.cpp
@@ -48,6 +48,7 @@
minikinPaint.localeListId = paint->getMinikinLocaleListId();
minikinPaint.fontStyle = resolvedFace->fStyle;
minikinPaint.fontFeatureSettings = paint->getFontFeatureSettings();
+ minikinPaint.fontVariationSettings = paint->getFontVariationOverride();
const std::optional<minikin::FamilyVariant>& familyVariant = paint->getFamilyVariant();
if (familyVariant.has_value()) {
diff --git a/libs/hwui/jni/text/TextShaper.cpp b/libs/hwui/jni/text/TextShaper.cpp
index 6c05346..4563386 100644
--- a/libs/hwui/jni/text/TextShaper.cpp
+++ b/libs/hwui/jni/text/TextShaper.cpp
@@ -156,16 +156,47 @@
return layout->layout.getFakery(i).isFakeItalic();
}
+float findValueFromVariationSettings(const minikin::FontFakery& fakery, minikin::AxisTag tag) {
+ for (const minikin::FontVariation& fv : fakery.variationSettings()) {
+ if (fv.axisTag == tag) {
+ return fv.value;
+ }
+ }
+ return std::numeric_limits<float>::quiet_NaN();
+}
+
// CriticalNative
static jfloat TextShaper_Result_getWeightOverride(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) {
const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr);
- return layout->layout.getFakery(i).wghtAdjustment();
+ if (text_feature::typeface_redesign()) {
+ float value =
+ findValueFromVariationSettings(layout->layout.getFakery(i), minikin::TAG_wght);
+ if (!std::isnan(value)) {
+ return value;
+ } else {
+ const std::shared_ptr<minikin::Font>& font = layout->layout.getFontRef(i);
+ return font->style().weight();
+ }
+ } else {
+ return layout->layout.getFakery(i).wghtAdjustment();
+ }
}
// CriticalNative
static jfloat TextShaper_Result_getItalicOverride(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) {
const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr);
- return layout->layout.getFakery(i).italAdjustment();
+ if (text_feature::typeface_redesign()) {
+ float value =
+ findValueFromVariationSettings(layout->layout.getFakery(i), minikin::TAG_ital);
+ if (!std::isnan(value)) {
+ return value;
+ } else {
+ const std::shared_ptr<minikin::Font>& font = layout->layout.getFontRef(i);
+ return font->style().isItalic();
+ }
+ } else {
+ return layout->layout.getFakery(i).italAdjustment();
+ }
}
// CriticalNative
diff --git a/media/java/android/media/session/PlaybackState.java b/media/java/android/media/session/PlaybackState.java
index 47637b8..290d49b 100644
--- a/media/java/android/media/session/PlaybackState.java
+++ b/media/java/android/media/session/PlaybackState.java
@@ -15,10 +15,8 @@
*/
package android.media.session;
-import static com.android.media.flags.Flags.FLAG_ENABLE_NOTIFYING_ACTIVITY_MANAGER_WITH_MEDIA_SESSION_STATUS_CHANGE;
import android.annotation.DrawableRes;
-import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.LongDef;
import android.annotation.Nullable;
@@ -187,13 +185,21 @@
*/
public static final long ACTION_SET_PLAYBACK_SPEED = 1 << 22;
- /**
- * @hide
- */
- @IntDef({STATE_NONE, STATE_STOPPED, STATE_PAUSED, STATE_PLAYING, STATE_FAST_FORWARDING,
- STATE_REWINDING, STATE_BUFFERING, STATE_ERROR, STATE_CONNECTING,
- STATE_SKIPPING_TO_PREVIOUS, STATE_SKIPPING_TO_NEXT, STATE_SKIPPING_TO_QUEUE_ITEM,
- STATE_PLAYBACK_SUPPRESSED})
+ /** @hide */
+ @IntDef({
+ STATE_NONE,
+ STATE_STOPPED,
+ STATE_PAUSED,
+ STATE_PLAYING,
+ STATE_FAST_FORWARDING,
+ STATE_REWINDING,
+ STATE_BUFFERING,
+ STATE_ERROR,
+ STATE_CONNECTING,
+ STATE_SKIPPING_TO_PREVIOUS,
+ STATE_SKIPPING_TO_NEXT,
+ STATE_SKIPPING_TO_QUEUE_ITEM
+ })
@Retention(RetentionPolicy.SOURCE)
public @interface State {}
@@ -290,19 +296,6 @@
public static final int STATE_SKIPPING_TO_QUEUE_ITEM = 11;
/**
- * State indicating that playback is paused due to an external transient interruption, like a
- * phone call.
- *
- * <p>This state is different from {@link #STATE_PAUSED} in that it is deemed transitory,
- * possibly allowing the service associated to the session in this state to run in the
- * foreground.
- *
- * @see Builder#setState
- */
- @FlaggedApi(FLAG_ENABLE_NOTIFYING_ACTIVITY_MANAGER_WITH_MEDIA_SESSION_STATUS_CHANGE)
- public static final int STATE_PLAYBACK_SUPPRESSED = 12;
-
- /**
* Use this value for the position to indicate the position is not known.
*/
public static final long PLAYBACK_POSITION_UNKNOWN = -1;
@@ -401,7 +394,6 @@
* <li> {@link PlaybackState#STATE_SKIPPING_TO_PREVIOUS}</li>
* <li> {@link PlaybackState#STATE_SKIPPING_TO_NEXT}</li>
* <li> {@link PlaybackState#STATE_SKIPPING_TO_QUEUE_ITEM}</li>
- * <li> {@link PlaybackState#STATE_PLAYBACK_SUPPRESSED}</li>
* </ul>
*/
@State
@@ -525,7 +517,6 @@
* <li>{@link #STATE_SKIPPING_TO_NEXT}</li>
* <li>{@link #STATE_SKIPPING_TO_PREVIOUS}</li>
* <li>{@link #STATE_SKIPPING_TO_QUEUE_ITEM}</li>
- * <li>{@link #STATE_PLAYBACK_SUPPRESSED}</li>
* </ul>
*/
public boolean isActive() {
@@ -538,7 +529,6 @@
case PlaybackState.STATE_BUFFERING:
case PlaybackState.STATE_CONNECTING:
case PlaybackState.STATE_PLAYING:
- case PlaybackState.STATE_PLAYBACK_SUPPRESSED:
return true;
}
return false;
@@ -584,8 +574,6 @@
return "SKIPPING_TO_NEXT";
case STATE_SKIPPING_TO_QUEUE_ITEM:
return "SKIPPING_TO_QUEUE_ITEM";
- case STATE_PLAYBACK_SUPPRESSED:
- return "STATE_PLAYBACK_SUPPRESSED";
default:
return "UNKNOWN";
}
@@ -823,7 +811,6 @@
* <li> {@link PlaybackState#STATE_SKIPPING_TO_PREVIOUS}</li>
* <li> {@link PlaybackState#STATE_SKIPPING_TO_NEXT}</li>
* <li> {@link PlaybackState#STATE_SKIPPING_TO_QUEUE_ITEM}</li>
- * <li> {@link PlaybackState#STATE_PLAYBACK_SUPPRESSED}</li>
* </ul>
*
* @param state The current state of playback.
@@ -868,7 +855,6 @@
* <li> {@link PlaybackState#STATE_SKIPPING_TO_PREVIOUS}</li>
* <li> {@link PlaybackState#STATE_SKIPPING_TO_NEXT}</li>
* <li> {@link PlaybackState#STATE_SKIPPING_TO_QUEUE_ITEM}</li>
- * <li> {@link PlaybackState#STATE_PLAYBACK_SUPPRESSED}</li>
* </ul>
*
* @param state The current state of playback.
diff --git a/media/java/android/media/tv/tuner/filter/MediaEvent.java b/media/java/android/media/tv/tuner/filter/MediaEvent.java
index 4676dff..84a13ab 100644
--- a/media/java/android/media/tv/tuner/filter/MediaEvent.java
+++ b/media/java/android/media/tv/tuner/filter/MediaEvent.java
@@ -17,12 +17,14 @@
package android.media.tv.tuner.filter;
import android.annotation.BytesLong;
+import android.annotation.FlaggedApi;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.media.AudioPresentation;
import android.media.MediaCodec.LinearBlock;
+import android.media.tv.flags.Flags;
import java.util.Collections;
import java.util.List;
@@ -57,12 +59,16 @@
private final int mScIndexMask;
private final AudioDescriptor mExtraMetaData;
private final List<AudioPresentation> mAudioPresentations;
+ private final int mNumDataPieces;
+ private final int mIndexInDataGroup;
+ private final int mDataGroupId;
// This constructor is used by JNI code only
private MediaEvent(int streamId, boolean isPtsPresent, long pts, boolean isDtsPresent, long dts,
long dataLength, long offset, LinearBlock buffer, boolean isSecureMemory, long dataId,
int mpuSequenceNumber, boolean isPrivateData, int scIndexMask,
- AudioDescriptor extraMetaData, List<AudioPresentation> audioPresentations) {
+ AudioDescriptor extraMetaData, List<AudioPresentation> audioPresentations,
+ int numDataPieces, int indexInDataGroup, int dataGroupId) {
mStreamId = streamId;
mIsPtsPresent = isPtsPresent;
mPts = pts;
@@ -78,6 +84,9 @@
mScIndexMask = scIndexMask;
mExtraMetaData = extraMetaData;
mAudioPresentations = audioPresentations;
+ mNumDataPieces = numDataPieces;
+ mIndexInDataGroup = indexInDataGroup;
+ mDataGroupId = dataGroupId;
}
/**
@@ -235,6 +244,67 @@
}
/**
+ * Gets the number of data pieces into which the original data was split.
+ *
+ * <p>The {@link #getNumDataPieces()}, {@link #getIndexInDataGroup()} and
+ * {@link #getDataGroupId()} methods should be used together to reassemble the original data if
+ * it was split into pieces. Use {@link #getLinearBlock()} to get the memory where the data
+ * pieces are stored.
+ *
+ * @return 0 or 1 if this MediaEvent object contains the complete data; otherwise the number of
+ * pieces into which the original data was split.
+ * @see #getIndexInDataGroup()
+ * @see #getDataGroupId()
+ * @see #getLinearBlock()
+ */
+ @FlaggedApi(Flags.FLAG_TUNER_W_APIS)
+ @IntRange(from = 0)
+ public int getNumDataPieces() {
+ return mNumDataPieces;
+ }
+
+ /**
+ * Gets the index of the data piece. The index in the data group indicates the order in which
+ * this {@link MediaEvent}'s data piece should be reassembled. The result should be within the
+ * range [0, {@link #getNumDataPieces()}).
+ *
+ * <p>The {@link #getNumDataPieces()}, {@link #getIndexInDataGroup()} and
+ * {@link #getDataGroupId()} methods should be used together to reassemble the original data if
+ * it was split into pieces. Use {@link #getLinearBlock()} to get the memory where the data
+ * pieces are stored.
+ *
+ * @return The index in the data group.
+ * @see #getNumDataPieces()
+ * @see #getDataGroupId()
+ * @see #getLinearBlock()
+ */
+ @FlaggedApi(Flags.FLAG_TUNER_W_APIS)
+ @IntRange(from = 0)
+ public int getIndexInDataGroup() {
+ return mIndexInDataGroup;
+ }
+
+ /**
+ * Gets the group ID for reassembling the complete data. {@link MediaEvent}s that have the same
+ * data group ID contain different pieces of the same data. This value should be ignored if
+ * {@link #getNumDataPieces()} returns 0 or 1.
+ *
+ * <p>The {@link #getNumDataPieces()}, {@link #getIndexInDataGroup()} and
+ * {@link #getDataGroupId()} methods should be used together to reassemble the original data if
+ * it was split into pieces. Use {@link #getLinearBlock()} to get the memory where the data
+ * pieces are stored.
+ *
+ * @return The data group ID.
+ * @see #getNumDataPieces()
+ * @see #getIndexInDataGroup()
+ * @see #getLinearBlock()
+ */
+ @FlaggedApi(Flags.FLAG_TUNER_W_APIS)
+ public int getDataGroupId() {
+ return mDataGroupId;
+ }
+
+ /**
* Finalize the MediaEvent object.
* @hide
*/
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 00b0e57..49e7941 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -686,12 +686,16 @@
} else if (mediaEvent.scIndexMask.getTag() == DemuxFilterScIndexMask::Tag::scVvc) {
sc = mediaEvent.scIndexMask.get<DemuxFilterScIndexMask::Tag::scVvc>();
}
+ jint numDataPieces = mediaEvent.numDataPieces;
+ jint indexInDataGroup = mediaEvent.indexInDataGroup;
+ jint dataGroupId = mediaEvent.dataGroupId;
ScopedLocalRef obj(env, env->NewObject(mMediaEventClass, mMediaEventInitID, streamId,
isPtsPresent, pts, isDtsPresent, dts, dataLength,
offset, nullptr, isSecureMemory, avDataId,
mpuSequenceNumber, isPesPrivateData, sc,
- audioDescriptor.get(), presentationsJObj.get()));
+ audioDescriptor.get(), presentationsJObj.get(),
+ numDataPieces, indexInDataGroup, dataGroupId));
// Protect mFilterClient from being set to null.
android::Mutex::Autolock autoLock(mLock);
@@ -1048,7 +1052,7 @@
"<init>",
"(IZJZJJJLandroid/media/MediaCodec$LinearBlock;"
"ZJIZILandroid/media/tv/tuner/filter/AudioDescriptor;"
- "Ljava/util/List;)V");
+ "Ljava/util/List;III)V");
mAudioDescriptorInitID = env->GetMethodID(mAudioDescriptorClass, "<init>", "(BBCBBB)V");
mPesEventInitID = env->GetMethodID(mPesEventClass, "<init>", "(III)V");
mTsRecordEventInitID = env->GetMethodID(mTsRecordEventClass, "<init>", "(IIIJJI)V");
diff --git a/nfc/java/android/nfc/flags.aconfig b/nfc/java/android/nfc/flags.aconfig
index cc9a97c..6a7e693 100644
--- a/nfc/java/android/nfc/flags.aconfig
+++ b/nfc/java/android/nfc/flags.aconfig
@@ -157,3 +157,11 @@
description: "Enable EUICC card emulation"
bug: "321314635"
}
+
+flag {
+ name: "nfc_state_change_security_log_event_enabled"
+ is_exported: true
+ namespace: "nfc"
+ description: "Enabling security log for nfc state change"
+ bug: "319934052"
+}
diff --git a/packages/SettingsLib/ActionButtonsPreference/res/drawable/half_rounded_left_bk.xml b/packages/SettingsLib/ActionButtonsPreference/res/drawable/half_rounded_left_bk.xml
index 0d4ef3c..8d6f0da4 100644
--- a/packages/SettingsLib/ActionButtonsPreference/res/drawable/half_rounded_left_bk.xml
+++ b/packages/SettingsLib/ActionButtonsPreference/res/drawable/half_rounded_left_bk.xml
@@ -1,18 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2021 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
+ Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
diff --git a/packages/SettingsLib/ActionButtonsPreference/res/drawable/left_rounded_ripple.xml b/packages/SettingsLib/ActionButtonsPreference/res/drawable/left_rounded_ripple.xml
index 1584b45..f82388c 100644
--- a/packages/SettingsLib/ActionButtonsPreference/res/drawable/left_rounded_ripple.xml
+++ b/packages/SettingsLib/ActionButtonsPreference/res/drawable/left_rounded_ripple.xml
@@ -1,18 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2021 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
+ Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
-->
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
diff --git a/packages/SettingsLib/ActionButtonsPreference/res/drawable/right_rounded_ripple.xml b/packages/SettingsLib/ActionButtonsPreference/res/drawable/right_rounded_ripple.xml
index 15f2b5c..3b0cca0 100644
--- a/packages/SettingsLib/ActionButtonsPreference/res/drawable/right_rounded_ripple.xml
+++ b/packages/SettingsLib/ActionButtonsPreference/res/drawable/right_rounded_ripple.xml
@@ -1,18 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2021 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
+ Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
-->
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
diff --git a/packages/SettingsLib/ActionButtonsPreference/res/drawable/rounded_ripple.xml b/packages/SettingsLib/ActionButtonsPreference/res/drawable/rounded_ripple.xml
index 90eefbe..bdf9114 100644
--- a/packages/SettingsLib/ActionButtonsPreference/res/drawable/rounded_ripple.xml
+++ b/packages/SettingsLib/ActionButtonsPreference/res/drawable/rounded_ripple.xml
@@ -1,18 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2021 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
+ Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
-->
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
diff --git a/packages/SettingsLib/ActionButtonsPreference/res/drawable/square_bk.xml b/packages/SettingsLib/ActionButtonsPreference/res/drawable/square_bk.xml
index f0da7b4..d56c843 100644
--- a/packages/SettingsLib/ActionButtonsPreference/res/drawable/square_bk.xml
+++ b/packages/SettingsLib/ActionButtonsPreference/res/drawable/square_bk.xml
@@ -1,18 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2021 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
+ Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
diff --git a/packages/SettingsLib/ActionButtonsPreference/res/drawable/square_ripple.xml b/packages/SettingsLib/ActionButtonsPreference/res/drawable/square_ripple.xml
index 06be00d..9270cbe 100644
--- a/packages/SettingsLib/ActionButtonsPreference/res/drawable/square_ripple.xml
+++ b/packages/SettingsLib/ActionButtonsPreference/res/drawable/square_ripple.xml
@@ -1,18 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2021 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
+ Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
-->
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
diff --git a/packages/SettingsLib/ActionButtonsPreference/res/values/arrays.xml b/packages/SettingsLib/ActionButtonsPreference/res/values/arrays.xml
index fe88845..180564d 100644
--- a/packages/SettingsLib/ActionButtonsPreference/res/values/arrays.xml
+++ b/packages/SettingsLib/ActionButtonsPreference/res/values/arrays.xml
@@ -1,18 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2021 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
+ Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
diff --git a/packages/SettingsLib/IntroPreference/res/layout/settingslib_expressive_preference_intro.xml b/packages/SettingsLib/IntroPreference/res/layout/settingslib_expressive_preference_intro.xml
index 203a395..2edc001 100644
--- a/packages/SettingsLib/IntroPreference/res/layout/settingslib_expressive_preference_intro.xml
+++ b/packages/SettingsLib/IntroPreference/res/layout/settingslib_expressive_preference_intro.xml
@@ -31,7 +31,6 @@
<TextView
android:id="@android:id/title"
- android:text="Title"
style="@style/SettingsLibEntityHeaderTitle"/>
<com.android.settingslib.widget.CollapsableTextView
diff --git a/packages/SettingsLib/SelectorWithWidgetPreference/res/values/attrs.xml b/packages/SettingsLib/SelectorWithWidgetPreference/res/values/attrs.xml
index 7ffde25..b90de6b 100644
--- a/packages/SettingsLib/SelectorWithWidgetPreference/res/values/attrs.xml
+++ b/packages/SettingsLib/SelectorWithWidgetPreference/res/values/attrs.xml
@@ -1,17 +1,18 @@
-<?xml version="1.0" encoding="utf-8"?><!--
- ~ Copyright (C) 2024 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
-->
<resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v31/settingslib_list_divider.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v31/settingslib_list_divider.xml
new file mode 100644
index 0000000..eb588d9
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v31/settingslib_list_divider.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="@color/settingslib_list_divider_color" />
+ <size
+ android:height="1dp"
+ android:width="1dp" />
+</shape>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_chevron.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_chevron.xml
index 16ca18a..9447653 100644
--- a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_chevron.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_chevron.xml
@@ -1,18 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2024 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_list_divider.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_list_divider.xml
new file mode 100644
index 0000000..c17f7ee
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_list_divider.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="@color/settingslib_materialColorOutline" />
+ <size
+ android:height="1dp"
+ android:width="1dp" />
+</shape>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v31/settingslib_preference_frame.xml b/packages/SettingsLib/SettingsTheme/res/layout-v31/settingslib_preference_frame.xml
index 433d264..128f7a1 100644
--- a/packages/SettingsLib/SettingsTheme/res/layout-v31/settingslib_preference_frame.xml
+++ b/packages/SettingsLib/SettingsTheme/res/layout-v31/settingslib_preference_frame.xml
@@ -29,7 +29,6 @@
android:layout_height="wrap_content"
android:layout_gravity="start"
android:textAlignment="viewStart"
- android:text="Title"
android:maxLines="2"
android:textAppearance="?android:attr/textAppearanceListItem"
android:ellipsize="marquee"/>
@@ -41,7 +40,6 @@
android:layout_below="@android:id/title"
android:layout_alignLeft="@android:id/title"
android:layout_alignStart="@android:id/title"
- android:text="Summary summary summary"
android:layout_gravity="start"
android:textAlignment="viewStart"
android:textColor="?android:attr/textColorSecondary"
diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v33/settingslib_preference.xml b/packages/SettingsLib/SettingsTheme/res/layout-v33/settingslib_preference.xml
deleted file mode 100644
index 4e23b65..0000000
--- a/packages/SettingsLib/SettingsTheme/res/layout-v33/settingslib_preference.xml
+++ /dev/null
@@ -1,48 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2022 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- -->
-
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:minHeight="?android:attr/listPreferredItemHeightSmall"
- android:gravity="center_vertical"
- android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
- android:paddingStart="?android:attr/listPreferredItemPaddingStart"
- android:paddingRight="?android:attr/listPreferredItemPaddingRight"
- android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
- android:background="?android:attr/selectableItemBackground"
- android:clipToPadding="false"
- android:baselineAligned="false">
-
- <include layout="@layout/settingslib_icon_frame"/>
-
- <include layout="@layout/settingslib_preference_frame"/>
-
- <!-- Preference should place its actual preference widget here. -->
- <LinearLayout
- android:id="@android:id/widget_frame"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:gravity="end|center_vertical"
- android:paddingLeft="16dp"
- android:paddingStart="16dp"
- android:paddingRight="0dp"
- android:paddingEnd="0dp"
- android:orientation="vertical"/>
-
-</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v33/settingslib_preference_frame.xml b/packages/SettingsLib/SettingsTheme/res/layout-v33/settingslib_preference_frame.xml
index f93e1b9..599e817 100644
--- a/packages/SettingsLib/SettingsTheme/res/layout-v33/settingslib_preference_frame.xml
+++ b/packages/SettingsLib/SettingsTheme/res/layout-v33/settingslib_preference_frame.xml
@@ -29,7 +29,6 @@
android:layout_height="wrap_content"
android:layout_gravity="start"
android:textAlignment="viewStart"
- android:text="Title"
android:maxLines="2"
android:hyphenationFrequency="normalFast"
android:lineBreakWordStyle="phrase"
@@ -47,7 +46,6 @@
android:textAlignment="viewStart"
android:textColor="?android:attr/textColorSecondary"
android:maxLines="10"
- android:text="Summary summary summary"
android:hyphenationFrequency="normalFast"
android:lineBreakWordStyle="phrase"
style="@style/PreferenceSummaryTextStyle"/>
diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_collapsable_textview.xml b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_collapsable_textview.xml
index 4cc3c89..ea7baa4 100644
--- a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_collapsable_textview.xml
+++ b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_collapsable_textview.xml
@@ -25,6 +25,7 @@
android:orientation="vertical"
android:animateLayoutChanges="true"
android:background="?android:attr/selectableItemBackground"
+ android:filterTouchesWhenObscured="false"
android:clipToPadding="false">
<TextView
diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference.xml b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference.xml
index 2475dfd..511e2bb 100644
--- a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference.xml
+++ b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference.xml
@@ -25,7 +25,8 @@
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:background="?android:attr/selectableItemBackground"
android:clipToPadding="false"
- android:baselineAligned="false">
+ android:baselineAligned="false"
+ android:filterTouchesWhenObscured="false">
<include layout="@layout/settingslib_expressive_preference_icon_frame"/>
diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_icon_frame.xml b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_icon_frame.xml
index f5017a5..ccdf37d 100644
--- a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_icon_frame.xml
+++ b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_icon_frame.xml
@@ -22,7 +22,8 @@
android:minWidth="@dimen/settingslib_expressive_space_medium3"
android:minHeight="@dimen/settingslib_expressive_space_medium3"
android:gravity="center"
- android:layout_marginEnd="-8dp">
+ android:layout_marginEnd="-8dp"
+ android:filterTouchesWhenObscured="false">
<androidx.preference.internal.PreferenceImageView
android:id="@android:id/icon"
diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_switch.xml b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_switch.xml
index 4cbdfd5..4abcd22 100644
--- a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_switch.xml
+++ b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_switch.xml
@@ -19,4 +19,5 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:theme="@style/Theme.Material3.DynamicColors.DayNight"
android:id="@+id/switchWidget"
+ android:filterTouchesWhenObscured="false"
style="@style/SettingslibSwitchStyle.Expressive"/>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_text_frame.xml b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_text_frame.xml
index e3e689b..cc42dab 100644
--- a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_text_frame.xml
+++ b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_text_frame.xml
@@ -20,7 +20,8 @@
android:layout_width="@dimen/settingslib_expressive_space_none"
android:layout_height="wrap_content"
android:layout_weight="1"
- android:padding="@dimen/settingslib_expressive_space_small1">
+ android:paddingVertical="@dimen/settingslib_expressive_space_small1"
+ android:filterTouchesWhenObscured="false">
<TextView
android:id="@android:id/title"
@@ -28,8 +29,10 @@
android:layout_height="wrap_content"
android:layout_gravity="start"
android:textAlignment="viewStart"
- android:textAppearance="?android:attr/textAppearanceListItem"
android:maxLines="2"
+ android:hyphenationFrequency="normalFast"
+ android:lineBreakWordStyle="phrase"
+ android:textAppearance="?android:attr/textAppearanceListItem"
android:ellipsize="marquee"/>
<TextView
@@ -43,5 +46,7 @@
android:textAlignment="viewStart"
android:textAppearance="?android:attr/textAppearanceListItemSecondary"
android:textColor="?android:attr/textColorSecondary"
- android:maxLines="10"/>
-</RelativeLayout>
+ android:maxLines="10"
+ android:hyphenationFrequency="normalFast"
+ android:lineBreakWordStyle="phrase"/>
+</RelativeLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_two_target_divider.xml b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_two_target_divider.xml
index 3f75181..9be9ec3 100644
--- a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_two_target_divider.xml
+++ b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_two_target_divider.xml
@@ -1,18 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2024 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
+ 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.
-->
<LinearLayout
@@ -24,7 +24,8 @@
android:orientation="horizontal"
android:paddingStart="?android:attr/listPreferredItemPaddingEnd"
android:paddingLeft="?android:attr/listPreferredItemPaddingEnd"
- android:paddingVertical="@dimen/settingslib_expressive_space_extrasmall7">
+ android:paddingVertical="@dimen/settingslib_expressive_space_extrasmall7"
+ android:filterTouchesWhenObscured="false">
<ImageView
android:layout_width="wrap_content"
diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_preference_category_no_title.xml b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_preference_category_no_title.xml
index 7f466f6..f69fcd2 100644
--- a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_preference_category_no_title.xml
+++ b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_preference_category_no_title.xml
@@ -18,5 +18,6 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:baselineAligned="false">
+ android:baselineAligned="false"
+ android:filterTouchesWhenObscured="false">
</LinearLayout>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml
index 0a36a4f..313748d 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml
@@ -56,4 +56,6 @@
<!--Deprecated. After sdk 35, don't use it-->
<color name="settingslib_colorSurface">@color/settingslib_surface_dark</color>
+
+ <color name="settingslib_list_divider_color">@android:color/system_neutral1_700</color>
</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml
index 7706e0e..b99ee51 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml
@@ -90,4 +90,6 @@
<color name="settingslib_spinner_title_color">@android:color/system_neutral1_900</color>
<color name="settingslib_spinner_dropdown_color">@android:color/system_neutral2_700</color>
+
+ <color name="settingslib_list_divider_color">@android:color/system_neutral1_200</color>
</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml
index 698f21d..3ccbbc0 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml
@@ -27,6 +27,7 @@
<item name="android:switchStyle">@style/Switch.SettingsLib</item>
<item name="switchStyle">@style/SwitchCompat.SettingsLib</item>
<item name="android:progressBarStyleHorizontal">@style/HorizontalProgressBar.SettingsLib</item>
+ <item name="android:listDivider">@drawable/settingslib_list_divider</item>
</style>
<style name="Theme.SettingsBase" parent="Theme.SettingsBase_v31" />
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/strings.xml b/packages/SettingsLib/SettingsTheme/res/values/strings.xml
similarity index 94%
rename from packages/SettingsLib/SettingsTheme/res/values-v35/strings.xml
rename to packages/SettingsLib/SettingsTheme/res/values/strings.xml
index 2273406..c36dcb8 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v35/strings.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values/strings.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- Copyright (C) 2021 The Android Open Source Project
+ Copyright (C) 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
diff --git a/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/CollapsableTextView.kt b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/CollapsableTextView.kt
index f1cc4e95..7eb9840 100644
--- a/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/CollapsableTextView.kt
+++ b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/CollapsableTextView.kt
@@ -248,8 +248,6 @@
val url: String = "",
val clickListener: View.OnClickListener) : URLSpan(url) {
override fun onClick(widget: View) {
- if (clickListener != null) {
- clickListener.onClick(widget)
- }
+ clickListener.onClick(widget)
}
}
diff --git a/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsThemeHelper.kt b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsThemeHelper.kt
index 10e5267..74f5441 100644
--- a/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsThemeHelper.kt
+++ b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsThemeHelper.kt
@@ -18,7 +18,6 @@
import android.content.Context
import android.os.Build
-import android.os.SystemProperties
object SettingsThemeHelper {
private const val IS_EXPRESSIVE_DESIGN_ENABLED = "is_expressive_design_enabled"
@@ -50,8 +49,7 @@
expressiveThemeState =
if (
(Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) &&
- (SystemProperties.getBoolean(IS_EXPRESSIVE_DESIGN_ENABLED, false) ||
- getPropBoolean(context, IS_EXPRESSIVE_DESIGN_ENABLED, false))
+ getPropBoolean(context, IS_EXPRESSIVE_DESIGN_ENABLED, false)
) {
ExpressiveThemeState.ENABLED
} else {
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 8b9ec38..739c7d6 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1651,6 +1651,9 @@
<!-- Name of the usb audio device mic. [CHAR LIMIT=50] -->
<string name="media_transfer_usb_device_mic_name">USB microphone</string>
+ <!-- Name of the bluetooth audio device mic. [CHAR LIMIT=50] -->
+ <string name="media_transfer_bt_device_mic_name">BT microphone</string>
+
<!-- Label for Wifi hotspot switch on. Toggles hotspot on [CHAR LIMIT=30] -->
<string name="wifi_hotspot_switch_on_text">On</string>
<!-- Label for Wifi hotspot switch off. Toggles hotspot off [CHAR LIMIT=30] -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ServiceListing.java b/packages/SettingsLib/src/com/android/settingslib/applications/ServiceListing.java
index c8bcabf..261c722 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ServiceListing.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ServiceListing.java
@@ -138,23 +138,37 @@
}
final PackageManager pmWrapper = mContext.getPackageManager();
+ // Add requesting apps, with full validation
List<ResolveInfo> installedServices = pmWrapper.queryIntentServicesAsUser(
new Intent(mIntentAction), flags, user);
for (ResolveInfo resolveInfo : installedServices) {
ServiceInfo info = resolveInfo.serviceInfo;
- if (!mPermission.equals(info.permission)) {
- Slog.w(mTag, "Skipping " + mNoun + " service "
- + info.packageName + "/" + info.name
- + ": it does not require the permission "
- + mPermission);
- continue;
+ if (!mEnabledServices.contains(info.getComponentName())) {
+ if (!mPermission.equals(info.permission)) {
+ Slog.w(mTag, "Skipping " + mNoun + " service "
+ + info.packageName + "/" + info.name
+ + ": it does not require the permission "
+ + mPermission);
+ continue;
+ }
+ if (mValidator != null && !mValidator.test(info)) {
+ continue;
+ }
+ mServices.add(info);
}
- if (mValidator != null && !mValidator.test(info)) {
- continue;
- }
- mServices.add(info);
}
+
+ // Add all apps with access, in case prior approval was granted without full validation
+ for (ComponentName cn : mEnabledServices) {
+ List<ResolveInfo> enabledServices = pmWrapper.queryIntentServicesAsUser(
+ new Intent().setComponent(cn), flags, user);
+ for (ResolveInfo resolveInfo : enabledServices) {
+ ServiceInfo info = resolveInfo.serviceInfo;
+ mServices.add(info);
+ }
+ }
+
for (Callback callback : mCallbacks) {
callback.onServicesReloaded(mServices);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java
index e44a134..1d17b00 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java
@@ -15,6 +15,7 @@
*/
package com.android.settingslib.media;
+import static android.media.AudioDeviceInfo.TYPE_BLUETOOTH_SCO;
import static android.media.AudioDeviceInfo.TYPE_BUILTIN_MIC;
import static android.media.AudioDeviceInfo.TYPE_USB_ACCESSORY;
import static android.media.AudioDeviceInfo.TYPE_USB_DEVICE;
@@ -90,7 +91,8 @@
TYPE_WIRED_HEADSET,
TYPE_USB_DEVICE,
TYPE_USB_HEADSET,
- TYPE_USB_ACCESSORY ->
+ TYPE_USB_ACCESSORY,
+ TYPE_BLUETOOTH_SCO ->
true;
default -> false;
};
@@ -103,6 +105,8 @@
R.string.media_transfer_wired_device_mic_name);
case TYPE_USB_DEVICE, TYPE_USB_HEADSET, TYPE_USB_ACCESSORY -> mContext.getString(
R.string.media_transfer_usb_device_mic_name);
+ case TYPE_BLUETOOTH_SCO -> mContext.getString(
+ R.string.media_transfer_bt_device_mic_name);
default -> mContext.getString(R.string.media_transfer_this_device_name_desktop);
};
return name.toString();
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ServiceListingTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ServiceListingTest.java
index 7ff0988..feef559 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ServiceListingTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ServiceListingTest.java
@@ -21,6 +21,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyList;
+import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -29,6 +30,7 @@
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
@@ -42,6 +44,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatcher;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
@@ -72,19 +75,26 @@
.build();
}
+ private ArgumentMatcher<Intent> filterEquals(Intent intent) {
+ return (test) -> {
+ return intent.filterEquals(test);
+ };
+ }
+
@Test
public void testValidator() {
ServiceInfo s1 = new ServiceInfo();
s1.permission = "testPermission";
s1.packageName = "pkg";
+ s1.name = "Service1";
ServiceInfo s2 = new ServiceInfo();
s2.permission = "testPermission";
s2.packageName = "pkg2";
+ s2.name = "service2";
ResolveInfo r1 = new ResolveInfo();
r1.serviceInfo = s1;
ResolveInfo r2 = new ResolveInfo();
r2.serviceInfo = s2;
-
when(mPm.queryIntentServicesAsUser(any(), anyInt(), anyInt())).thenReturn(
ImmutableList.of(r1, r2));
@@ -118,9 +128,11 @@
ServiceInfo s1 = new ServiceInfo();
s1.permission = "testPermission";
s1.packageName = "pkg";
+ s1.name = "Service1";
ServiceInfo s2 = new ServiceInfo();
s2.permission = "testPermission";
s2.packageName = "pkg2";
+ s2.name = "service2";
ResolveInfo r1 = new ResolveInfo();
r1.serviceInfo = s1;
ResolveInfo r2 = new ResolveInfo();
@@ -193,4 +205,56 @@
assertThat(Settings.Secure.getString(RuntimeEnvironment.application.getContentResolver(),
TEST_SETTING)).contains(testComponent2.flattenToString());
}
+
+ @Test
+ public void testHasPermissionWithoutMeetingCurrentRegs() {
+ ServiceInfo s1 = new ServiceInfo();
+ s1.permission = "testPermission";
+ s1.packageName = "pkg";
+ s1.name = "Service1";
+ ServiceInfo s2 = new ServiceInfo();
+ s2.permission = "testPermission";
+ s2.packageName = "pkg2";
+ s2.name = "service2";
+ ResolveInfo r1 = new ResolveInfo();
+ r1.serviceInfo = s1;
+ ResolveInfo r2 = new ResolveInfo();
+ r2.serviceInfo = s2;
+
+ ComponentName approvedComponent = new ComponentName(s2.packageName, s2.name);
+
+ Settings.Secure.putString(
+ mContext.getContentResolver(), TEST_SETTING, approvedComponent.flattenToString());
+
+ when(mPm.queryIntentServicesAsUser(argThat(
+ filterEquals(new Intent(TEST_INTENT))), anyInt(), anyInt()))
+ .thenReturn(ImmutableList.of(r1));
+ when(mPm.queryIntentServicesAsUser(argThat(
+ filterEquals(new Intent().setComponent(approvedComponent))),
+ anyInt(), anyInt()))
+ .thenReturn(ImmutableList.of(r2));
+
+ mServiceListing = new ServiceListing.Builder(mContext)
+ .setTag("testTag")
+ .setSetting(TEST_SETTING)
+ .setNoun("testNoun")
+ .setIntentAction(TEST_INTENT)
+ .setValidator(info -> {
+ if (info.packageName.equals("pkg")) {
+ return true;
+ }
+ return false;
+ })
+ .setPermission("testPermission")
+ .build();
+ ServiceListing.Callback callback = mock(ServiceListing.Callback.class);
+ mServiceListing.addCallback(callback);
+ mServiceListing.reload();
+
+ verify(mPm, times(2)).queryIntentServicesAsUser(any(), anyInt(), anyInt());
+ ArgumentCaptor<List<ServiceInfo>> captor = ArgumentCaptor.forClass(List.class);
+ verify(callback, times(1)).onServicesReloaded(captor.capture());
+
+ assertThat(captor.getValue()).containsExactlyElementsIn(ImmutableList.of(s2, s1));
+ }
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java
index 2f0aa1c..30e4637 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java
@@ -37,6 +37,7 @@
private final int BUILTIN_MIC_ID = 1;
private final int WIRED_HEADSET_ID = 2;
private final int USB_HEADSET_ID = 3;
+ private final int BT_HEADSET_ID = 4;
private final int MAX_VOLUME = 1;
private final int CURRENT_VOLUME = 0;
private final boolean IS_VOLUME_FIXED = true;
@@ -108,4 +109,19 @@
assertThat(usbMediaDevice.getName())
.isEqualTo(mContext.getString(R.string.media_transfer_usb_device_mic_name));
}
+
+ @Test
+ public void getName_returnCorrectName_btHeadset() {
+ InputMediaDevice btMediaDevice =
+ InputMediaDevice.create(
+ mContext,
+ String.valueOf(BT_HEADSET_ID),
+ AudioDeviceInfo.TYPE_BLUETOOTH_SCO,
+ MAX_VOLUME,
+ CURRENT_VOLUME,
+ IS_VOLUME_FIXED);
+ assertThat(btMediaDevice).isNotNull();
+ assertThat(btMediaDevice.getName())
+ .isEqualTo(mContext.getString(R.string.media_transfer_bt_device_mic_name));
+ }
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index 0773bd7..8f58e8c 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -434,7 +434,7 @@
VALIDATORS.put(Global.Wearable.PHONE_SWITCHING_STATUS,
new InclusiveIntegerRangeValidator(
Global.Wearable.PHONE_SWITCHING_STATUS_NOT_STARTED,
- Global.Wearable.PHONE_SWITCHING_STATUS_IN_PROGRESS_MIGRATION_SUCCESS));
+ Global.Wearable.PHONE_SWITCHING_STATUS_ACCOUNTS_MATCHED));
VALIDATORS.put(Global.Wearable.REDUCE_MOTION, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.Wearable.RTL_SWIPE_TO_DISMISS_ENABLED_DEV, BOOLEAN_VALIDATOR);
VALIDATORS.put(
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index b491b5a..d39b564 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -223,7 +223,6 @@
Settings.Global.ENABLE_DELETION_HELPER_NO_THRESHOLD_TOGGLE,
Settings.Global.ENABLE_DISKSTATS_LOGGING,
Settings.Global.ENABLE_EPHEMERAL_FEATURE,
- Settings.Global.ENABLE_USE_APP_INFO_NOT_LAUNCHED,
Settings.Global.DYNAMIC_POWER_SAVINGS_ENABLED,
Settings.Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD,
Settings.Global.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS,
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index f3c5a18..456fedf 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -948,6 +948,9 @@
<!-- Permission required for CTS test - CtsNfcTestCases -->
<uses-permission android:name="android.permission.NFC_SET_CONTROLLER_ALWAYS_ON" />
+ <!-- Permission required for CTS test - CtsAppTestCases -->
+ <uses-permission android:name="android.permission.KILL_UID" />
+
<application
android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 6c3ba68..c6238e8 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1401,10 +1401,13 @@
}
flag {
- name: "compose_haptic_sliders"
+ name: "haptics_for_compose_sliders"
namespace: "systemui"
description: "Adding haptic component infrastructure to sliders in Compose."
bug: "341968766"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
flag {
@@ -1470,3 +1473,13 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "check_lockscreen_gone_transition"
+ namespace: "systemui"
+ description: "Run notification pipeline when the lockscreen is not in gone transition for avoiding janky frames during unlocking animation"
+ bug: "358301118"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt
index f5d01d7..907c39d 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt
@@ -944,9 +944,26 @@
}
override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
- startController.onTransitionAnimationEnd(isExpandingFullyAbove)
- endController.onTransitionAnimationEnd(isExpandingFullyAbove)
- onLaunchAnimationEnd()
+ // onLaunchAnimationEnd is called by an Animator at the end of the animation,
+ // on a Choreographer animation tick. The following calls will move the animated
+ // content from the dialog overlay back to its original position, and this
+ // change must be reflected in the next frame given that we then sync the next
+ // frame of both the content and dialog ViewRoots. However, in case that content
+ // is rendered by Compose, whose compositions are also scheduled on a
+ // Choreographer frame, any state change made *right now* won't be reflected in
+ // the next frame given that a Choreographer frame can't schedule another and
+ // have it happen in the same frame. So we post the forwarded calls to
+ // [Controller.onLaunchAnimationEnd], leaving this Choreographer frame, ensuring
+ // that the move of the content back to its original window will be reflected in
+ // the next frame right after [onLaunchAnimationEnd] is called.
+ //
+ // TODO(b/330672236): Move this to TransitionAnimator.
+ dialog.context.mainExecutor.execute {
+ startController.onTransitionAnimationEnd(isExpandingFullyAbove)
+ endController.onTransitionAnimationEnd(isExpandingFullyAbove)
+
+ onLaunchAnimationEnd()
+ }
}
override fun onTransitionAnimationProgress(
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
index 859fc4e0..fc4cf1d 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
@@ -379,26 +379,13 @@
Log.d(TAG, "Animation ended")
}
- // onAnimationEnd is called at the end of the animation, on a Choreographer
- // animation tick. During dialog launches, the following calls will move the
- // animated content from the dialog overlay back to its original position, and
- // this change must be reflected in the next frame given that we then sync the
- // next frame of both the content and dialog ViewRoots. During SysUI activity
- // launches, we will instantly collapse the shade at the end of the transition.
- // However, if those are rendered by Compose, whose compositions are also
- // scheduled on a Choreographer frame, any state change made *right now* won't
- // be reflected in the next frame given that a Choreographer frame can't
- // schedule another and have it happen in the same frame. So we post the
- // forwarded calls to [Controller.onLaunchAnimationEnd] in the main executor,
- // leaving this Choreographer frame, ensuring that any state change applied by
- // onTransitionAnimationEnd() will be reflected in the same frame.
- mainExecutor.execute {
- controller.onTransitionAnimationEnd(isExpandingFullyAbove)
- transitionContainerOverlay.remove(windowBackgroundLayer)
+ // TODO(b/330672236): Post this to the main thread instead so that it does not
+ // flicker with Flexiglass enabled.
+ controller.onTransitionAnimationEnd(isExpandingFullyAbove)
+ transitionContainerOverlay.remove(windowBackgroundLayer)
- if (moveBackgroundLayerWhenAppVisibilityChanges && controller.isLaunching) {
- openingWindowSyncViewOverlay?.remove(windowBackgroundLayer)
- }
+ if (moveBackgroundLayerWhenAppVisibilityChanges && controller.isLaunching) {
+ openingWindowSyncViewOverlay?.remove(windowBackgroundLayer)
}
}
}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/Bounceable.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/Bounceable.kt
new file mode 100644
index 0000000..3f2f84b
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/Bounceable.kt
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation
+
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.layout
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Dp
+import kotlin.math.roundToInt
+
+/** A component that can bounce in one dimension, for instance when it is tapped. */
+interface Bounceable {
+ val bounce: Dp
+}
+
+/**
+ * Bounce a composable in the given [orientation] when this [bounceable], the [previousBounceable]
+ * or [nextBounceable] is bouncing.
+ *
+ * Important: This modifier should be used on composables that have a fixed size in [orientation],
+ * i.e. they should be placed *after* modifiers like Modifier.fillMaxWidth() or Modifier.height().
+ *
+ * @param bounceable the [Bounceable] associated to the current composable that will make this
+ * composable size grow when bouncing.
+ * @param previousBounceable the [Bounceable] associated to the previous composable in [orientation]
+ * that will make this composable shrink when bouncing.
+ * @param nextBounceable the [Bounceable] associated to the next composable in [orientation] that
+ * will make this composable shrink when bouncing.
+ * @param orientation the orientation in which this bounceable should grow/shrink.
+ * @param bounceEnd whether this bounceable should bounce on the end (right in LTR layouts, left in
+ * RTL layouts) side. This can be used for grids for which the last item does not align perfectly
+ * with the end of the grid.
+ */
+fun Modifier.bounceable(
+ bounceable: Bounceable,
+ previousBounceable: Bounceable?,
+ nextBounceable: Bounceable?,
+ orientation: Orientation,
+ bounceEnd: Boolean = nextBounceable != null,
+): Modifier {
+ return layout { measurable, constraints ->
+ // The constraints in the orientation should be fixed, otherwise there is no way to know
+ // what the size of our child node will be without this animation code.
+ checkFixedSize(constraints, orientation)
+
+ var sizePrevious = 0f
+ var sizeNext = 0f
+
+ if (previousBounceable != null) {
+ sizePrevious += bounceable.bounce.toPx() - previousBounceable.bounce.toPx()
+ }
+
+ if (nextBounceable != null) {
+ sizeNext += bounceable.bounce.toPx() - nextBounceable.bounce.toPx()
+ } else if (bounceEnd) {
+ sizeNext += bounceable.bounce.toPx()
+ }
+
+ when (orientation) {
+ Orientation.Horizontal -> {
+ val idleWidth = constraints.maxWidth
+ val animatedWidth = (idleWidth + sizePrevious + sizeNext).roundToInt()
+ val animatedConstraints =
+ constraints.copy(minWidth = animatedWidth, maxWidth = animatedWidth)
+
+ val placeable = measurable.measure(animatedConstraints)
+
+ // Important: we still place the element using the idle size coming from the
+ // constraints, otherwise the parent will automatically center this node given the
+ // size that it expects us to be. This allows us to then place the element where we
+ // want it to be.
+ layout(idleWidth, placeable.height) {
+ placeable.placeRelative(-sizePrevious.roundToInt(), 0)
+ }
+ }
+ Orientation.Vertical -> {
+ val idleHeight = constraints.maxHeight
+ val animatedHeight = (idleHeight + sizePrevious + sizeNext).roundToInt()
+ val animatedConstraints =
+ constraints.copy(minHeight = animatedHeight, maxHeight = animatedHeight)
+
+ val placeable = measurable.measure(animatedConstraints)
+ layout(placeable.width, idleHeight) {
+ placeable.placeRelative(0, -sizePrevious.roundToInt())
+ }
+ }
+ }
+ }
+}
+
+private fun checkFixedSize(constraints: Constraints, orientation: Orientation) {
+ when (orientation) {
+ Orientation.Horizontal -> {
+ check(constraints.hasFixedWidth) {
+ "Modifier.bounceable() should receive a fixed width from its parent. Make sure " +
+ "that it is used *after* a fixed-width Modifier in the horizontal axis (like" +
+ " Modifier.fillMaxWidth() or Modifier.width())."
+ }
+ }
+ Orientation.Vertical -> {
+ check(constraints.hasFixedHeight) {
+ "Modifier.bounceable() should receive a fixed height from its parent. Make sure " +
+ "that it is used *after* a fixed-height Modifier in the vertical axis (like" +
+ " Modifier.fillMaxHeight() or Modifier.height())."
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/BounceableTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/BounceableTest.kt
new file mode 100644
index 0000000..335e9f8
--- /dev/null
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/BounceableTest.kt
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation
+
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.assertHeightIsEqualTo
+import androidx.compose.ui.test.assertPositionInRootIsEqualTo
+import androidx.compose.ui.test.assertWidthIsEqualTo
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.times
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class BounceableTest {
+ @get:Rule val rule = createComposeRule()
+
+ @Test
+ fun bounceable_horizontal() {
+ var bounceables by mutableStateOf(List(4) { bounceable(0.dp) })
+
+ rule.setContent {
+ Row(Modifier.size(100.dp, 50.dp)) {
+ repeat(bounceables.size) { i ->
+ Box(
+ Modifier.weight(1f)
+ .fillMaxHeight()
+ .bounceable(bounceables, i, orientation = Orientation.Horizontal)
+ )
+ }
+ }
+ }
+
+ // All bounceables have a width of (100dp / bounceables.size) = 25dp and height of 50dp.
+ repeat(bounceables.size) { i ->
+ rule
+ .onNodeWithTag(bounceableTag(i))
+ .assertWidthIsEqualTo(25.dp)
+ .assertHeightIsEqualTo(50.dp)
+ .assertPositionInRootIsEqualTo(i * 25.dp, 0.dp)
+ }
+
+ // If all bounceables have the same bounce, it's the same as if they didn't have any.
+ bounceables = List(4) { bounceable(10.dp) }
+ repeat(bounceables.size) { i ->
+ rule
+ .onNodeWithTag(bounceableTag(i))
+ .assertWidthIsEqualTo(25.dp)
+ .assertHeightIsEqualTo(50.dp)
+ .assertPositionInRootIsEqualTo(i * 25.dp, 0.dp)
+ }
+
+ // Bounce the first and third one.
+ bounceables =
+ listOf(
+ bounceable(bounce = 5.dp),
+ bounceable(bounce = 0.dp),
+ bounceable(bounce = 10.dp),
+ bounceable(bounce = 0.dp),
+ )
+
+ // First one has a width of 25dp + 5dp, located in (0, 0).
+ rule
+ .onNodeWithTag(bounceableTag(0))
+ .assertWidthIsEqualTo(30.dp)
+ .assertHeightIsEqualTo(50.dp)
+ .assertPositionInRootIsEqualTo(0.dp, 0.dp)
+
+ // Second one has a width of 25dp - 5dp - 10dp, located in (30, 0).
+ rule
+ .onNodeWithTag(bounceableTag(1))
+ .assertWidthIsEqualTo(10.dp)
+ .assertHeightIsEqualTo(50.dp)
+ .assertPositionInRootIsEqualTo(30.dp, 0.dp)
+
+ // Third one has a width of 25 + 2 * 10dp, located in (40, 0).
+ rule
+ .onNodeWithTag(bounceableTag(2))
+ .assertWidthIsEqualTo(45.dp)
+ .assertHeightIsEqualTo(50.dp)
+ .assertPositionInRootIsEqualTo(40.dp, 0.dp)
+
+ // First one has a width of 25dp - 10dp, located in (85, 0).
+ rule
+ .onNodeWithTag(bounceableTag(3))
+ .assertWidthIsEqualTo(15.dp)
+ .assertHeightIsEqualTo(50.dp)
+ .assertPositionInRootIsEqualTo(85.dp, 0.dp)
+ }
+
+ @Test
+ fun bounceable_vertical() {
+ var bounceables by mutableStateOf(List(4) { bounceable(0.dp) })
+
+ rule.setContent {
+ Column(Modifier.size(50.dp, 100.dp)) {
+ repeat(bounceables.size) { i ->
+ Box(
+ Modifier.weight(1f)
+ .fillMaxWidth()
+ .bounceable(bounceables, i, Orientation.Vertical)
+ )
+ }
+ }
+ }
+
+ // All bounceables have a height of (100dp / bounceables.size) = 25dp and width of 50dp.
+ repeat(bounceables.size) { i ->
+ rule
+ .onNodeWithTag(bounceableTag(i))
+ .assertWidthIsEqualTo(50.dp)
+ .assertHeightIsEqualTo(25.dp)
+ .assertPositionInRootIsEqualTo(0.dp, i * 25.dp)
+ }
+
+ // If all bounceables have the same bounce, it's the same as if they didn't have any.
+ bounceables = List(4) { bounceable(10.dp) }
+ repeat(bounceables.size) { i ->
+ rule
+ .onNodeWithTag(bounceableTag(i))
+ .assertWidthIsEqualTo(50.dp)
+ .assertHeightIsEqualTo(25.dp)
+ .assertPositionInRootIsEqualTo(0.dp, i * 25.dp)
+ }
+
+ // Bounce the first and third one.
+ bounceables =
+ listOf(
+ bounceable(bounce = 5.dp),
+ bounceable(bounce = 0.dp),
+ bounceable(bounce = 10.dp),
+ bounceable(bounce = 0.dp),
+ )
+
+ // First one has a height of 25dp + 5dp, located in (0, 0).
+ rule
+ .onNodeWithTag(bounceableTag(0))
+ .assertWidthIsEqualTo(50.dp)
+ .assertHeightIsEqualTo(30.dp)
+ .assertPositionInRootIsEqualTo(0.dp, 0.dp)
+
+ // Second one has a height of 25dp - 5dp - 10dp, located in (0, 30).
+ rule
+ .onNodeWithTag(bounceableTag(1))
+ .assertWidthIsEqualTo(50.dp)
+ .assertHeightIsEqualTo(10.dp)
+ .assertPositionInRootIsEqualTo(0.dp, 30.dp)
+
+ // Third one has a height of 25 + 2 * 10dp, located in (0, 40).
+ rule
+ .onNodeWithTag(bounceableTag(2))
+ .assertWidthIsEqualTo(50.dp)
+ .assertHeightIsEqualTo(45.dp)
+ .assertPositionInRootIsEqualTo(0.dp, 40.dp)
+
+ // First one has a height of 25dp - 10dp, located in (0, 85).
+ rule
+ .onNodeWithTag(bounceableTag(3))
+ .assertWidthIsEqualTo(50.dp)
+ .assertHeightIsEqualTo(15.dp)
+ .assertPositionInRootIsEqualTo(0.dp, 85.dp)
+ }
+
+ private fun bounceable(bounce: Dp): Bounceable {
+ return object : Bounceable {
+ override val bounce: Dp = bounce
+ }
+ }
+
+ private fun Modifier.bounceable(
+ bounceables: List<Bounceable>,
+ i: Int,
+ orientation: Orientation,
+ ): Modifier {
+ val previous = if (i > 0) bounceables[i - 1] else null
+ val next = if (i < bounceables.lastIndex) bounceables[i + 1] else null
+ return this.bounceable(bounceables[i], previous, next, orientation)
+ .testTag(bounceableTag(i))
+ }
+
+ private fun bounceableTag(i: Int) = "bounceable$i"
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractorKosmos.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/DreamSceneModule.kt
similarity index 63%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractorKosmos.kt
copy to packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/DreamSceneModule.kt
index 3a7d7ba..2f82369 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractorKosmos.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/DreamSceneModule.kt
@@ -14,10 +14,15 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.row.domain.interactor
+package com.android.systemui.scene
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.statusbar.notification.row.data.repository.NotificationRowRepository
+import com.android.systemui.dream.ui.composable.DreamScene
+import com.android.systemui.scene.ui.composable.Scene
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoSet
-fun Kosmos.getNotificationRowInteractor(repository: NotificationRowRepository) =
- NotificationRowInteractor(repository = repository)
+@Module
+interface DreamSceneModule {
+ @Binds @IntoSet fun dreamScene(scene: DreamScene): Scene
+}
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 557257d..571b366 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
@@ -162,7 +162,6 @@
initialScene = currentSceneKey,
canChangeScene = { _ -> viewModel.canChangeScene() },
transitions = sceneTransitions,
- enableInterruptions = false,
)
}
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 bcd3337..7fb4c53 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
@@ -148,6 +148,7 @@
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
@@ -165,6 +166,7 @@
import com.android.internal.R.dimen.system_app_widget_background_radius
import com.android.systemui.Flags
import com.android.systemui.Flags.communalTimerFlickerFix
+import com.android.systemui.Flags.communalWidgetResizing
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.model.CommunalContentSize
import com.android.systemui.communal.shared.model.CommunalScenes
@@ -176,6 +178,7 @@
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.communal.ui.viewmodel.ResizeInfo
import com.android.systemui.communal.util.DensityUtils.Companion.adjustedDp
import com.android.systemui.communal.widgets.SmartspaceAppWidgetHostView
import com.android.systemui.communal.widgets.WidgetConfigurator
@@ -639,6 +642,38 @@
}
}
+@Composable
+private fun ResizableItemFrameWrapper(
+ key: String,
+ gridState: LazyGridState,
+ minItemSpan: Int,
+ gridContentPadding: PaddingValues,
+ verticalArrangement: Arrangement.Vertical,
+ enabled: Boolean,
+ modifier: Modifier = Modifier,
+ alpha: () -> Float = { 1f },
+ onResize: (info: ResizeInfo) -> Unit = {},
+ content: @Composable (modifier: Modifier) -> Unit,
+) {
+ if (!communalWidgetResizing()) {
+ content(modifier)
+ } else {
+ ResizableItemFrame(
+ key = key,
+ gridState = gridState,
+ minItemSpan = minItemSpan,
+ gridContentPadding = gridContentPadding,
+ verticalArrangement = verticalArrangement,
+ enabled = enabled,
+ alpha = alpha,
+ modifier = modifier,
+ onResize = onResize,
+ ) {
+ content(Modifier)
+ }
+ }
+}
+
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun BoxScope.CommunalHubLazyGrid(
@@ -695,13 +730,14 @@
gridModifier = gridModifier.height(hubDimensions.GridHeight)
}
+ val itemArrangement = Arrangement.spacedBy(Dimensions.ItemSpacing)
LazyHorizontalGrid(
modifier = gridModifier,
state = gridState,
rows = GridCells.Fixed(CommunalContentSize.FULL.span),
contentPadding = contentPadding,
- horizontalArrangement = Arrangement.spacedBy(Dimensions.ItemSpacing),
- verticalArrangement = Arrangement.spacedBy(Dimensions.ItemSpacing),
+ horizontalArrangement = itemArrangement,
+ verticalArrangement = itemArrangement,
) {
itemsIndexed(
items = list,
@@ -710,35 +746,54 @@
span = { _, item -> GridItemSpan(item.size.span) },
) { index, item ->
val size = SizeF(Dimensions.CardWidth.value, item.size.dp().value)
- val cardModifier = Modifier.requiredSize(width = size.width.dp, height = size.height.dp)
+ val selected = item.key == selectedKey.value
+ val dpSize = DpSize(size.width.dp, size.height.dp)
+
if (viewModel.isEditMode && dragDropState != null) {
- val selected = item.key == selectedKey.value
- DraggableItem(
+ val outlineAlpha by
+ animateFloatAsState(
+ targetValue = if (selected) 1f else 0f,
+ animationSpec = spring(stiffness = Spring.StiffnessMediumLow),
+ label = "Widget resizing outline alpha",
+ )
+ ResizableItemFrameWrapper(
+ key = item.key,
+ gridState = gridState,
+ minItemSpan = CommunalContentSize.HALF.span,
+ gridContentPadding = contentPadding,
+ verticalArrangement = itemArrangement,
+ enabled = selected,
+ alpha = { outlineAlpha },
modifier =
- if (dragDropState.draggingItemIndex == index) {
- Modifier
- } else {
+ Modifier.requiredSize(dpSize).thenIf(
+ dragDropState.draggingItemIndex != index
+ ) {
Modifier.animateItem(
placementSpec = spring(stiffness = Spring.StiffnessMediumLow)
)
},
- dragDropState = dragDropState,
- selected = selected,
- enabled = item.isWidgetContent(),
- index = index,
- ) { isDragging ->
- CommunalContent(
- modifier = cardModifier,
- model = item,
- viewModel = viewModel,
- size = size,
- selected = selected && !isDragging,
- widgetConfigurator = widgetConfigurator,
+ onResize = { resizeInfo -> contentListState.resize(index, resizeInfo) },
+ ) { modifier ->
+ DraggableItem(
+ modifier = modifier,
+ dragDropState = dragDropState,
+ selected = selected,
+ enabled = item.isWidgetContent(),
index = index,
- contentListState = contentListState,
- interactionHandler = interactionHandler,
- widgetSection = widgetSection,
- )
+ ) { isDragging ->
+ CommunalContent(
+ modifier = Modifier.fillMaxSize(),
+ model = item,
+ viewModel = viewModel,
+ size = size,
+ selected = selected && !isDragging,
+ widgetConfigurator = widgetConfigurator,
+ index = index,
+ contentListState = contentListState,
+ interactionHandler = interactionHandler,
+ widgetSection = widgetSection,
+ )
+ }
}
} else {
CommunalContent(
@@ -746,7 +801,7 @@
viewModel = viewModel,
size = size,
selected = false,
- modifier = cardModifier.animateItem(),
+ modifier = Modifier.requiredSize(dpSize).animateItem(),
index = index,
contentListState = contentListState,
interactionHandler = interactionHandler,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
index 1137357..6e30575 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
@@ -21,8 +21,12 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.toMutableStateList
+import com.android.systemui.Flags.communalWidgetResizing
import com.android.systemui.communal.domain.model.CommunalContentModel
+import com.android.systemui.communal.shared.model.CommunalContentSize
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
+import com.android.systemui.communal.ui.viewmodel.DragHandle
+import com.android.systemui.communal.ui.viewmodel.ResizeInfo
import com.android.systemui.communal.widgets.WidgetConfigurator
@Composable
@@ -35,15 +39,11 @@
ContentListState(
communalContent,
{ componentName, user, rank ->
- viewModel.onAddWidget(
- componentName,
- user,
- rank,
- widgetConfigurator,
- )
+ viewModel.onAddWidget(componentName, user, rank, widgetConfigurator)
},
viewModel::onDeleteWidget,
viewModel::onReorderWidgets,
+ viewModel::onResizeWidget,
)
}
}
@@ -59,6 +59,7 @@
private val onAddWidget: (componentName: ComponentName, user: UserHandle, rank: Int) -> Unit,
private val onDeleteWidget: (id: Int, componentName: ComponentName, rank: Int) -> Unit,
private val onReorderWidgets: (widgetIdToRankMap: Map<Int, Int>) -> Unit,
+ private val onResizeWidget: (id: Int, spanY: Int, widgetIdToRankMap: Map<Int, Int>) -> Unit,
) {
var list = communalContent.toMutableStateList()
private set
@@ -77,6 +78,36 @@
}
}
+ /** Resize a widget, possibly re-ordering widgets if needed. */
+ fun resize(index: Int, resizeInfo: ResizeInfo) {
+ val item = list[index]
+ val currentSpan = item.size.span
+ val newSpan = currentSpan + resizeInfo.spans
+ // Only widgets can be resized
+ if (
+ !communalWidgetResizing() ||
+ currentSpan == newSpan ||
+ item !is CommunalContentModel.WidgetContent.Widget
+ ) {
+ return
+ }
+ list[index] = item.copy(size = CommunalContentSize.toSize(newSpan))
+ val prevItem = list.getOrNull(index - 1)
+ // Check if we have to update indices of items to accommodate the resize.
+ val widgetIdToRankMap: Map<Int, Int> =
+ if (
+ resizeInfo.isExpanding &&
+ resizeInfo.fromHandle == DragHandle.TOP &&
+ prevItem is CommunalContentModel.WidgetContent.Widget
+ ) {
+ onMove(index - 1, index)
+ mapOf(prevItem.appWidgetId to index, item.appWidgetId to index - 1)
+ } else {
+ emptyMap()
+ }
+ onResizeWidget(item.appWidgetId, newSpan, widgetIdToRankMap)
+ }
+
/**
* Persists the new order with all the movements happened during drag operations & the new
* widget drop (if applicable).
@@ -91,7 +122,7 @@
fun onSaveList(
newItemComponentName: ComponentName? = null,
newItemUser: UserHandle? = null,
- newItemIndex: Int? = null
+ newItemIndex: Int? = null,
) {
// New widget added to the grid. Other widgets are shifted as needed at the database level.
if (newItemComponentName != null && newItemUser != null && newItemIndex != null) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
index 20ee131..101385f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
@@ -42,6 +42,7 @@
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.toOffset
import androidx.compose.ui.unit.toSize
+import com.android.systemui.Flags.communalWidgetResizing
import com.android.systemui.communal.ui.compose.extensions.firstItemAtOffset
import com.android.systemui.communal.ui.compose.extensions.plus
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
@@ -65,7 +66,7 @@
state = gridState,
contentListState = contentListState,
scope = scope,
- updateDragPositionForRemove = updateDragPositionForRemove
+ updateDragPositionForRemove = updateDragPositionForRemove,
)
}
LaunchedEffect(state) {
@@ -90,7 +91,7 @@
private val state: LazyGridState,
private val contentListState: ContentListState,
private val scope: CoroutineScope,
- private val updateDragPositionForRemove: (offset: Offset) -> Boolean
+ private val updateDragPositionForRemove: (offset: Offset) -> Boolean,
) {
var draggingItemIndex by mutableStateOf<Int?>(null)
private set
@@ -122,12 +123,12 @@
offset: Offset,
screenWidth: Int,
layoutDirection: LayoutDirection,
- contentOffset: Offset
+ contentOffset: Offset,
): Boolean {
val normalizedOffset =
Offset(
if (layoutDirection == LayoutDirection.Ltr) offset.x else screenWidth - offset.x,
- offset.y
+ offset.y,
)
state.layoutInfo.visibleItemsInfo
.filter { item -> contentListState.isItemEditable(item.index) }
@@ -248,7 +249,7 @@
offset,
screenWidth,
layoutDirection,
- contentOffset
+ contentOffset,
)
) {
viewModel.onReorderWidgetStart()
@@ -261,7 +262,7 @@
onDragCancel = {
dragDropState.onDragInterrupted()
viewModel.onReorderWidgetCancel()
- }
+ },
)
}
)
@@ -276,7 +277,7 @@
enabled: Boolean,
selected: Boolean,
modifier: Modifier = Modifier,
- content: @Composable (isDragging: Boolean) -> Unit
+ content: @Composable (isDragging: Boolean) -> Unit,
) {
if (!enabled) {
return content(false)
@@ -286,7 +287,7 @@
val itemAlpha: Float by
animateFloatAsState(
targetValue = if (dragDropState.isDraggingToRemove) 0.5f else 1f,
- label = "DraggableItemAlpha"
+ label = "DraggableItemAlpha",
)
val direction = LocalLayoutDirection.current
val draggingModifier =
@@ -303,12 +304,17 @@
// Animate the highlight alpha manually as alpha modifier (and AnimatedVisibility) clips the
// widget to bounds, which cuts off the highlight as we are drawing outside the widget bounds.
+ val highlightSelected = !communalWidgetResizing() && selected
val alpha by
animateFloatAsState(
targetValue =
- if ((dragging || selected) && !dragDropState.isDraggingToRemove) 1f else 0f,
+ if ((dragging || highlightSelected) && !dragDropState.isDraggingToRemove) {
+ 1f
+ } else {
+ 0f
+ },
animationSpec = spring(stiffness = Spring.StiffnessMediumLow),
- label = "Widget outline alpha"
+ label = "Widget outline alpha",
)
Box(modifier) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResizeableItemFrame.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResizeableItemFrame.kt
new file mode 100644
index 0000000..ef62eb7
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResizeableItemFrame.kt
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.ui.compose
+
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.gestures.AnchoredDraggableState
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.anchoredDraggable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.lazy.grid.LazyGridState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.rememberUpdatedState
+import androidx.compose.runtime.snapshotFlow
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.CornerRadius
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.SolidColor
+import androidx.compose.ui.graphics.drawscope.Stroke
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.util.fastIsFinite
+import com.android.compose.theme.LocalAndroidColorScheme
+import com.android.systemui.communal.ui.viewmodel.DragHandle
+import com.android.systemui.communal.ui.viewmodel.ResizeInfo
+import com.android.systemui.communal.ui.viewmodel.ResizeableItemFrameViewModel
+import com.android.systemui.lifecycle.rememberViewModel
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.combine
+
+@Composable
+private fun UpdateGridLayoutInfo(
+ viewModel: ResizeableItemFrameViewModel,
+ key: String,
+ gridState: LazyGridState,
+ minItemSpan: Int,
+ gridContentPadding: PaddingValues,
+ verticalArrangement: Arrangement.Vertical,
+) {
+ val density = LocalDensity.current
+ LaunchedEffect(
+ density,
+ viewModel,
+ key,
+ gridState,
+ minItemSpan,
+ gridContentPadding,
+ verticalArrangement,
+ ) {
+ val verticalItemSpacingPx = with(density) { verticalArrangement.spacing.toPx() }
+ val verticalContentPaddingPx =
+ with(density) {
+ (gridContentPadding.calculateTopPadding() +
+ gridContentPadding.calculateBottomPadding())
+ .toPx()
+ }
+
+ combine(
+ snapshotFlow { gridState.layoutInfo.maxSpan },
+ snapshotFlow { gridState.layoutInfo.viewportSize.height },
+ snapshotFlow {
+ gridState.layoutInfo.visibleItemsInfo.firstOrNull { it.key == key }
+ },
+ ::Triple,
+ )
+ .collectLatest { (maxItemSpan, viewportHeightPx, itemInfo) ->
+ viewModel.setGridLayoutInfo(
+ verticalItemSpacingPx,
+ verticalContentPaddingPx,
+ viewportHeightPx,
+ maxItemSpan,
+ minItemSpan,
+ itemInfo?.row,
+ itemInfo?.span,
+ )
+ }
+ }
+}
+
+@Composable
+private fun BoxScope.DragHandle(
+ handle: DragHandle,
+ dragState: AnchoredDraggableState<Int>,
+ outlinePadding: Dp,
+ brush: Brush,
+ alpha: () -> Float,
+ modifier: Modifier = Modifier,
+) {
+ val directionalModifier = if (handle == DragHandle.TOP) -1 else 1
+ val alignment = if (handle == DragHandle.TOP) Alignment.TopCenter else Alignment.BottomCenter
+ Box(
+ modifier
+ .align(alignment)
+ .graphicsLayer {
+ translationY =
+ directionalModifier * (size.height / 2 + outlinePadding.toPx()) +
+ (dragState.offset.takeIf { it.fastIsFinite() } ?: 0f)
+ }
+ .anchoredDraggable(dragState, Orientation.Vertical)
+ ) {
+ Canvas(modifier = Modifier.fillMaxSize()) {
+ if (dragState.anchors.size > 1) {
+ drawCircle(
+ brush = brush,
+ radius = outlinePadding.toPx(),
+ center = Offset(size.width / 2, size.height / 2),
+ alpha = alpha(),
+ )
+ }
+ }
+ }
+}
+
+/**
+ * Draws a frame around the content with drag handles on the top and bottom of the content.
+ *
+ * @param index The index of this item in the [LazyGridState].
+ * @param gridState The [LazyGridState] for the grid containing this item.
+ * @param minItemSpan The minimum span that an item may occupy. Items are resized in multiples of
+ * this span.
+ * @param gridContentPadding The content padding used for the grid, needed for determining offsets.
+ * @param verticalArrangement The vertical arrangement of the grid items.
+ * @param modifier Optional modifier to apply to the frame.
+ * @param enabled Whether resizing is enabled.
+ * @param outlinePadding The padding to apply around the entire frame, in [Dp]
+ * @param outlineColor Optional color to make the outline around the content.
+ * @param cornerRadius Optional radius to give to the outline around the content.
+ * @param strokeWidth Optional stroke width to draw the outline with.
+ * @param alpha Optional function to provide an alpha value for the outline. Can be used to fade the
+ * outline in and out. This is wrapped in a function for performance, as the value is only
+ * accessed during the draw phase.
+ * @param onResize Optional callback which gets executed when the item is resized to a new span.
+ * @param content The content to draw inside the frame.
+ */
+@Composable
+fun ResizableItemFrame(
+ key: String,
+ gridState: LazyGridState,
+ minItemSpan: Int,
+ gridContentPadding: PaddingValues,
+ verticalArrangement: Arrangement.Vertical,
+ modifier: Modifier = Modifier,
+ enabled: Boolean = true,
+ outlinePadding: Dp = 8.dp,
+ outlineColor: Color = LocalAndroidColorScheme.current.primary,
+ cornerRadius: Dp = 37.dp,
+ strokeWidth: Dp = 3.dp,
+ alpha: () -> Float = { 1f },
+ onResize: (info: ResizeInfo) -> Unit = {},
+ content: @Composable () -> Unit,
+) {
+ val brush = SolidColor(outlineColor)
+ val onResizeUpdated by rememberUpdatedState(onResize)
+ val viewModel =
+ rememberViewModel(traceName = "ResizeableItemFrame.viewModel") {
+ ResizeableItemFrameViewModel()
+ }
+
+ val dragHandleHeight = verticalArrangement.spacing - outlinePadding * 2
+
+ // Draw content surrounded by drag handles at top and bottom. Allow drag handles
+ // to overlap content.
+ Box(modifier) {
+ content()
+
+ if (enabled) {
+ DragHandle(
+ handle = DragHandle.TOP,
+ dragState = viewModel.topDragState,
+ outlinePadding = outlinePadding,
+ brush = brush,
+ alpha = alpha,
+ modifier = Modifier.fillMaxWidth().height(dragHandleHeight),
+ )
+
+ DragHandle(
+ handle = DragHandle.BOTTOM,
+ dragState = viewModel.bottomDragState,
+ outlinePadding = outlinePadding,
+ brush = brush,
+ alpha = alpha,
+ modifier = Modifier.fillMaxWidth().height(dragHandleHeight),
+ )
+
+ // Draw outline around the element.
+ Canvas(modifier = Modifier.matchParentSize()) {
+ val paddingPx = outlinePadding.toPx()
+ val topOffset = viewModel.topDragState.offset.takeIf { it.fastIsFinite() } ?: 0f
+ val bottomOffset =
+ viewModel.bottomDragState.offset.takeIf { it.fastIsFinite() } ?: 0f
+ drawRoundRect(
+ brush,
+ alpha = alpha(),
+ topLeft = Offset(-paddingPx, topOffset + -paddingPx),
+ size =
+ Size(
+ width = size.width + paddingPx * 2,
+ height = -topOffset + bottomOffset + size.height + paddingPx * 2,
+ ),
+ cornerRadius = CornerRadius(cornerRadius.toPx()),
+ style = Stroke(width = strokeWidth.toPx()),
+ )
+ }
+
+ UpdateGridLayoutInfo(
+ viewModel,
+ key,
+ gridState,
+ minItemSpan,
+ gridContentPadding,
+ verticalArrangement,
+ )
+ LaunchedEffect(viewModel) {
+ viewModel.resizeInfo.collectLatest { info -> onResizeUpdated(info) }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/dream/ui/composable/DreamScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/dream/ui/composable/DreamScene.kt
new file mode 100644
index 0000000..f4374c6
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/dream/ui/composable/DreamScene.kt
@@ -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.systemui.dream.ui.composable
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.SceneScope
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dreams.ui.viewmodel.DreamUserActionsViewModel
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.ui.composable.Scene
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** The dream scene shows when a dream activity is showing. */
+@SysUISingleton
+class DreamScene
+@Inject
+constructor(private val actionsViewModelFactory: DreamUserActionsViewModel.Factory) :
+ ExclusiveActivatable(), Scene {
+ override val key = Scenes.Dream
+
+ private val actionsViewModel: DreamUserActionsViewModel by lazy {
+ actionsViewModelFactory.create()
+ }
+
+ override val userActions: Flow<Map<UserAction, UserActionResult>> = actionsViewModel.actions
+
+ override suspend fun onActivated(): Nothing {
+ actionsViewModel.activate()
+ }
+
+ @Composable
+ override fun SceneScope.Content(modifier: Modifier) {
+ Box(modifier = modifier.fillMaxSize()) {
+ // Render a sleep emoji to make the scene appear visible.
+ Text(
+ modifier = Modifier.padding(16.dp).align(Alignment.BottomStart),
+ text = "\uD83D\uDCA4",
+ )
+ }
+ }
+}
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 97d89a2..afa92f2 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
@@ -83,11 +83,7 @@
}
val state = remember {
- MutableSceneTransitionLayoutState(
- currentScene,
- ClockTransition.defaultClockTransitions,
- enableInterruptions = false,
- )
+ MutableSceneTransitionLayoutState(currentScene, ClockTransition.defaultClockTransitions)
}
// Update state whenever currentSceneKey has changed.
@@ -102,7 +98,7 @@
scene(splitShadeLargeClockScene) {
LargeClockWithSmartSpace(
smartSpacePaddingTop = smartSpacePaddingTop,
- shouldOffSetClockToOneHalf = !hasCustomPositionUpdatedAnimation
+ shouldOffSetClockToOneHalf = !hasCustomPositionUpdatedAnimation,
)
}
@@ -114,21 +110,15 @@
}
scene(smallClockScene) {
- SmallClockWithSmartSpace(
- smartSpacePaddingTop = smartSpacePaddingTop,
- )
+ SmallClockWithSmartSpace(smartSpacePaddingTop = smartSpacePaddingTop)
}
scene(largeClockScene) {
- LargeClockWithSmartSpace(
- smartSpacePaddingTop = smartSpacePaddingTop,
- )
+ LargeClockWithSmartSpace(smartSpacePaddingTop = smartSpacePaddingTop)
}
scene(WeatherClockScenes.largeClockScene) {
- WeatherLargeClockWithSmartSpace(
- smartSpacePaddingTop = smartSpacePaddingTop,
- )
+ WeatherLargeClockWithSmartSpace(smartSpacePaddingTop = smartSpacePaddingTop)
}
scene(WeatherClockScenes.splitShadeLargeClockScene) {
@@ -154,7 +144,7 @@
SmallClock(
burnInParams = burnIn.parameters,
onTopChanged = burnIn.onSmallClockTopChanged,
- modifier = Modifier.wrapContentSize()
+ modifier = Modifier.wrapContentSize(),
)
}
with(smartSpaceSection) {
@@ -202,7 +192,7 @@
y = 0,
)
}
- }
+ },
)
}
}
@@ -226,10 +216,7 @@
Column(modifier = modifier) {
val currentClock = currentClockState.value ?: return@Column
with(weatherClockSection) {
- Time(
- clock = currentClock,
- burnInParams = burnIn.parameters,
- )
+ Time(clock = currentClock, burnInParams = burnIn.parameters)
}
val density = LocalDensity.current
val context = LocalContext.current
@@ -242,7 +229,7 @@
modifier =
Modifier.heightIn(
min = getDimen(context, "enhanced_smartspace_height", density)
- )
+ ),
)
}
with(weatherClockSection) {
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 2745f6e..4c6834c 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
@@ -47,7 +47,6 @@
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
@@ -60,7 +59,6 @@
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.Color
@@ -70,7 +68,6 @@
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.boundsInWindow
import androidx.compose.ui.layout.onGloballyPositioned
-import androidx.compose.ui.layout.onPlaced
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.layout.positionInWindow
import androidx.compose.ui.platform.LocalConfiguration
@@ -82,6 +79,7 @@
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.lerp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.compose.animation.scene.ContentKey
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.LowestZIndexContentPicker
import com.android.compose.animation.scene.NestedScrollBehavior
@@ -93,8 +91,9 @@
import com.android.systemui.res.R
import com.android.systemui.scene.session.ui.composable.SaveableSession
import com.android.systemui.scene.session.ui.composable.rememberSession
+import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.shade.shared.model.ShadeMode
+import com.android.systemui.shade.shared.flag.DualShade
import com.android.systemui.shade.ui.composable.ShadeHeader
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimRounding
@@ -112,18 +111,16 @@
val NotificationStackPlaceholder = ElementKey("NotificationStackPlaceholder")
val HeadsUpNotificationPlaceholder =
ElementKey("HeadsUpNotificationPlaceholder", contentPicker = LowestZIndexContentPicker)
- val ShelfSpace = ElementKey("ShelfSpace")
val NotificationStackCutoffGuideline = ElementKey("NotificationStackCutoffGuideline")
}
-
- // Expansion fraction thresholds (between 0-1f) at which the corresponding value should be
- // at its maximum, given they are at their minimum value at expansion = 0f.
- object TransitionThresholds {
- const val EXPANSION_FOR_MAX_CORNER_RADIUS = 0.1f
- const val EXPANSION_FOR_MAX_SCRIM_ALPHA = 0.3f
- }
}
+private val notificationsShadeContentKey: ContentKey
+ get() = if (DualShade.isEnabled) Overlays.NotificationsShade else Scenes.Shade
+
+private val quickSettingsShadeContentKey: ContentKey
+ get() = if (DualShade.isEnabled) Overlays.QuickSettingsShade else Scenes.QuickSettings
+
/**
* Adds the space where heads up notifications can appear in the scene. This should generally be the
* entire size of the scene.
@@ -146,7 +143,7 @@
// This element is sometimes opted out of the shared element system, so there
// can be multiple instances of it during a transition. Thus we need to
// determine which instance should feed its bounds to NSSL to avoid providing
- // conflicting values
+ // conflicting values.
val useBounds = useHunBounds()
if (useBounds) {
val positionInWindow = coordinates.positionInWindow()
@@ -157,8 +154,8 @@
" bounds=$boundsInWindow"
}
// Note: boundsInWindow doesn't scroll off the screen, so use
- // positionInWindow
- // for top bound, which can scroll off screen while snoozing
+ // positionInWindow for top bound, which can scroll off screen while
+ // snoozing.
stackScrollView.setHeadsUpTop(positionInWindow.y)
stackScrollView.setHeadsUpBottom(boundsInWindow.bottom)
}
@@ -285,7 +282,8 @@
shouldFillMaxSize: Boolean = true,
shouldReserveSpaceForNavBar: Boolean = true,
shouldIncludeHeadsUpSpace: Boolean = true,
- shadeMode: ShadeMode,
+ shouldShowScrim: Boolean = true,
+ supportNestedScrolling: Boolean,
onEmptySpaceClick: (() -> Unit)? = null,
modifier: Modifier = Modifier,
) {
@@ -293,6 +291,7 @@
val density = LocalDensity.current
val screenCornerRadius = LocalScreenCornerRadius.current
val scrimCornerRadius = dimensionResource(R.dimen.notification_scrim_corner_radius)
+ val scrimBackgroundColor = MaterialTheme.colorScheme.surface
val scrollState =
shadeSession.rememberSaveableSession(saver = ScrollState.Saver, key = null) {
ScrollState(initial = 0)
@@ -427,8 +426,14 @@
// completes.
if (
scrimOffset.value < 0 &&
- layoutState.isTransitioning(from = Scenes.Shade, to = Scenes.Gone) ||
- layoutState.isTransitioning(from = Scenes.Shade, to = Scenes.Lockscreen)
+ (layoutState.isTransitioning(
+ from = notificationsShadeContentKey,
+ to = Scenes.Gone,
+ ) ||
+ layoutState.isTransitioning(
+ from = notificationsShadeContentKey,
+ to = Scenes.Lockscreen,
+ ))
) {
IntOffset(x = 0, y = (scrimOffset.value * expansionFraction).roundToInt())
} else if (
@@ -498,7 +503,7 @@
(expansionFraction / EXPANSION_FOR_MAX_SCRIM_ALPHA).coerceAtMost(1f)
} else 1f
}
- .background(MaterialTheme.colorScheme.surface)
+ .thenIf(shouldShowScrim) { Modifier.background(scrimBackgroundColor) }
.thenIf(shouldFillMaxSize) { Modifier.fillMaxSize() }
.debugBackground(viewModel, DEBUG_BOX_COLOR)
) {
@@ -508,7 +513,7 @@
topBehavior = NestedScrollBehavior.EdgeWithPreview,
isExternalOverscrollGesture = { isCurrentGestureOverscroll.value },
)
- .thenIf(shadeMode == ShadeMode.Single) {
+ .thenIf(supportNestedScrolling) {
Modifier.nestedScroll(scrimNestedScrollConnection)
}
.stackVerticalOverscroll(coroutineScope) { scrollState.canScrollForward }
@@ -550,38 +555,6 @@
}
/**
- * This may be added to the lockscreen to provide a space to the start of the lock icon where the
- * short shelf has room to flow vertically below the lock icon, but to its start, allowing more
- * notifications to fit in the stack itself. (see: b/213934746)
- *
- * NOTE: this is totally unused for now; it is here to clarify the future plan
- */
-@Composable
-fun SceneScope.NotificationShelfSpace(
- viewModel: NotificationsPlaceholderViewModel,
- modifier: Modifier = Modifier,
-) {
- Text(
- text = "Shelf Space",
- modifier
- .element(key = Notifications.Elements.ShelfSpace)
- .fillMaxWidth()
- .onPlaced { coordinates: LayoutCoordinates ->
- debugLog(viewModel) {
- ("SHELF onPlaced:" +
- " size=${coordinates.size}" +
- " bounds=${coordinates.boundsInWindow()}")
- }
- }
- .clip(RoundedCornerShape(24.dp))
- .background(MaterialTheme.colorScheme.primaryContainer)
- .padding(16.dp),
- style = MaterialTheme.typography.titleLarge,
- color = MaterialTheme.colorScheme.onPrimaryContainer,
- )
-}
-
-/**
* A 0 height horizontal spacer to be placed at the bottom-most position in the current scene, where
* the notification contents (stack, footer, shelf) should be drawn.
*/
@@ -673,15 +646,19 @@
}
}
+private fun TransitionState.isOnLockscreen(): Boolean {
+ return currentScene == Scenes.Lockscreen && currentOverlays.isEmpty()
+}
+
private fun shouldUseLockscreenStackBounds(state: TransitionState): Boolean {
- return state is TransitionState.Idle && state.currentScene == Scenes.Lockscreen
+ return state is TransitionState.Idle && state.isOnLockscreen()
}
private fun shouldUseLockscreenHunBounds(state: TransitionState): Boolean {
return when (state) {
- is TransitionState.Idle -> state.currentScene == Scenes.Lockscreen
+ is TransitionState.Idle -> state.isOnLockscreen()
is TransitionState.Transition ->
- state.isTransitioning(from = Scenes.QuickSettings, to = Scenes.Lockscreen)
+ state.isTransitioning(from = quickSettingsShadeContentKey, to = Scenes.Lockscreen)
}
}
@@ -690,7 +667,7 @@
shouldPunchHoleBehindScrim: Boolean,
): Boolean {
return shouldPunchHoleBehindScrim ||
- state.isTransitioning(from = Scenes.Shade, to = Scenes.Lockscreen)
+ state.isTransitioning(from = notificationsShadeContentKey, to = Scenes.Lockscreen)
}
private fun calculateCornerRadius(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt
index a22becc..5b99670 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt
@@ -33,7 +33,6 @@
import com.android.systemui.scene.session.ui.composable.SaveableSession
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.ui.composable.Overlay
-import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.shade.ui.composable.ExpandedShadeHeader
import com.android.systemui.shade.ui.composable.OverlayShade
import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
@@ -69,9 +68,7 @@
}
@Composable
- override fun ContentScope.Content(
- modifier: Modifier,
- ) {
+ override fun ContentScope.Content(modifier: Modifier) {
val viewModel =
rememberViewModel("NotificationsShadeOverlay-viewModel") {
contentViewModelFactory.create()
@@ -81,10 +78,7 @@
viewModel.notificationsPlaceholderViewModelFactory.create()
}
- OverlayShade(
- modifier = modifier,
- onScrimClicked = viewModel::onScrimClicked,
- ) {
+ OverlayShade(modifier = modifier, onScrimClicked = viewModel::onScrimClicked) {
Column {
ExpandedShadeHeader(
viewModelFactory = viewModel.shadeHeaderViewModelFactory,
@@ -102,7 +96,8 @@
shouldPunchHoleBehindScrim = false,
shouldFillMaxSize = false,
shouldReserveSpaceForNavBar = false,
- shadeMode = ShadeMode.Dual,
+ shouldShowScrim = false,
+ supportNestedScrolling = false,
modifier = Modifier.fillMaxWidth(),
)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index 6304979..d75a776 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
@@ -100,7 +100,6 @@
import com.android.systemui.scene.session.ui.composable.SaveableSession
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.composable.Scene
-import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.shade.ui.composable.CollapsedShadeHeader
import com.android.systemui.shade.ui.composable.ExpandedShadeHeader
import com.android.systemui.shade.ui.composable.Shade
@@ -114,11 +113,9 @@
import javax.inject.Inject
import javax.inject.Named
import kotlin.math.roundToInt
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
/** The Quick Settings (AKA "QS") scene shows the quick setting tiles. */
-@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class QuickSettingsScene
@Inject
@@ -420,17 +417,26 @@
.navigationBarsPadding()
.padding(horizontal = shadeHorizontalPadding),
)
+
+ // The minimum possible value for the top of the notification stack. In other words: how
+ // high is the notification stack allowed to get when the scene is at rest. It may still be
+ // translated farther upwards by a transition animation but, at rest, the top edge of its
+ // bounds must be limited to be at or below this value.
+ //
+ // A 1 pixel is added to compensate for any kind of rounding errors to make sure 100% that
+ // the notification stack is entirely "below" the entire screen.
+ val minNotificationStackTop = screenHeight.roundToInt() + 1
NotificationScrollingStack(
shadeSession = shadeSession,
stackScrollView = notificationStackScrollView,
viewModel = notificationsPlaceholderViewModel,
- maxScrimTop = { screenHeight },
+ maxScrimTop = { minNotificationStackTop.toFloat() },
shouldPunchHoleBehindScrim = shouldPunchHoleBehindScrim,
shouldIncludeHeadsUpSpace = false,
- shadeMode = ShadeMode.Single,
+ supportNestedScrolling = true,
modifier =
Modifier.fillMaxWidth()
- .offset { IntOffset(x = 0, y = screenHeight.roundToInt()) }
+ .offset { IntOffset(x = 0, y = minNotificationStackTop) }
.padding(horizontal = shadeHorizontalPadding),
)
NotificationStackCutoffGuideline(
@@ -439,7 +445,7 @@
modifier =
Modifier.align(Alignment.BottomCenter)
.navigationBarsPadding()
- .offset { IntOffset(x = 0, y = screenHeight.roundToInt()) }
+ .offset { IntOffset(x = 0, y = minNotificationStackTop) }
.padding(horizontal = shadeHorizontalPadding),
)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
index 340ac32..8728521 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
@@ -15,11 +15,13 @@
import com.android.systemui.scene.ui.composable.transitions.bouncerToLockscreenPreview
import com.android.systemui.scene.ui.composable.transitions.communalToBouncerTransition
import com.android.systemui.scene.ui.composable.transitions.communalToShadeTransition
+import com.android.systemui.scene.ui.composable.transitions.dreamToGoneTransition
import com.android.systemui.scene.ui.composable.transitions.goneToQuickSettingsTransition
import com.android.systemui.scene.ui.composable.transitions.goneToShadeTransition
import com.android.systemui.scene.ui.composable.transitions.goneToSplitShadeTransition
import com.android.systemui.scene.ui.composable.transitions.lockscreenToBouncerTransition
import com.android.systemui.scene.ui.composable.transitions.lockscreenToCommunalTransition
+import com.android.systemui.scene.ui.composable.transitions.lockscreenToDreamTransition
import com.android.systemui.scene.ui.composable.transitions.lockscreenToGoneTransition
import com.android.systemui.scene.ui.composable.transitions.lockscreenToQuickSettingsTransition
import com.android.systemui.scene.ui.composable.transitions.lockscreenToShadeTransition
@@ -52,6 +54,7 @@
// Scene transitions
from(Scenes.Bouncer, to = Scenes.Gone) { bouncerToGoneTransition() }
+ from(Scenes.Dream, to = Scenes.Gone) { dreamToGoneTransition() }
from(Scenes.Gone, to = Scenes.Shade) { goneToShadeTransition() }
from(Scenes.Gone, to = Scenes.Shade, key = ToSplitShade) { goneToSplitShadeTransition() }
from(Scenes.Gone, to = Scenes.Shade, key = SlightlyFasterShadeCollapse) {
@@ -72,6 +75,7 @@
lockscreenToBouncerTransition()
}
from(Scenes.Lockscreen, to = Scenes.Communal) { lockscreenToCommunalTransition() }
+ from(Scenes.Lockscreen, to = Scenes.Dream) { lockscreenToDreamTransition() }
from(Scenes.Lockscreen, to = Scenes.Shade) { lockscreenToShadeTransition() }
from(Scenes.Lockscreen, to = Scenes.Shade, key = ToSplitShade) {
lockscreenToSplitShadeTransition()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractorKosmos.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromDreamToGoneTransition.kt
similarity index 63%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractorKosmos.kt
copy to packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromDreamToGoneTransition.kt
index 3a7d7ba..60dc4c05 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractorKosmos.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromDreamToGoneTransition.kt
@@ -14,10 +14,14 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.row.domain.interactor
+package com.android.systemui.scene.ui.composable.transitions
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.statusbar.notification.row.data.repository.NotificationRowRepository
+import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.scene.shared.model.Scenes
-fun Kosmos.getNotificationRowInteractor(repository: NotificationRowRepository) =
- NotificationRowInteractor(repository = repository)
+fun TransitionBuilder.dreamToGoneTransition() {
+ spec = tween(durationMillis = 1000)
+
+ fade(Scenes.Dream.rootElementKey)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractorKosmos.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToDreamTransition.kt
similarity index 62%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractorKosmos.kt
copy to packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToDreamTransition.kt
index 3a7d7ba..7092e3f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractorKosmos.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToDreamTransition.kt
@@ -14,10 +14,14 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.row.domain.interactor
+package com.android.systemui.scene.ui.composable.transitions
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.statusbar.notification.row.data.repository.NotificationRowRepository
+import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.scene.shared.model.Scenes
-fun Kosmos.getNotificationRowInteractor(repository: NotificationRowRepository) =
- NotificationRowInteractor(repository = repository)
+fun TransitionBuilder.lockscreenToDreamTransition() {
+ spec = tween(durationMillis = 1000)
+
+ fade(Scenes.Lockscreen.rootElementKey)
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt
index 337f53a5..23c4f12 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt
@@ -21,26 +21,20 @@
import androidx.compose.animation.core.tween
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.TransitionBuilder
-import com.android.compose.animation.scene.UserActionDistance
import com.android.systemui.notifications.ui.composable.Notifications
import com.android.systemui.shade.ui.composable.OverlayShade
import com.android.systemui.shade.ui.composable.Shade
import com.android.systemui.shade.ui.composable.ShadeHeader
import kotlin.time.Duration.Companion.milliseconds
-fun TransitionBuilder.toNotificationsShadeTransition(
- durationScale: Double = 1.0,
-) {
+fun TransitionBuilder.toNotificationsShadeTransition(durationScale: Double = 1.0) {
spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
swipeSpec =
spring(
stiffness = Spring.StiffnessMediumLow,
visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
)
- distance = UserActionDistance { fromSceneSize, orientation ->
- fromSceneSize.height.toFloat() * 2 / 3f
- }
-
+ scaleSize(OverlayShade.Elements.Panel, height = 0f)
translate(OverlayShade.Elements.Panel, Edge.Top)
fractionRange(end = .5f) { fade(OverlayShade.Elements.Scrim) }
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 c6c42fc..3ec057b 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
@@ -40,7 +40,6 @@
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
@@ -89,7 +88,6 @@
import com.android.systemui.media.controls.ui.controller.MediaCarouselController
import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
import com.android.systemui.media.controls.ui.view.MediaHost
-import com.android.systemui.media.controls.ui.view.MediaHostState
import com.android.systemui.media.controls.ui.view.MediaHostState.Companion.COLLAPSED
import com.android.systemui.media.controls.ui.view.MediaHostState.Companion.EXPANDED
import com.android.systemui.media.dagger.MediaModule.QS_PANEL
@@ -117,7 +115,6 @@
import javax.inject.Inject
import javax.inject.Named
import kotlin.math.roundToInt
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
object Shade {
@@ -128,23 +125,13 @@
}
object Dimensions {
- val ScrimCornerSize = 32.dp
val HorizontalPadding = 16.dp
val ScrimOverscrollLimit = 32.dp
const val ScrimVisibilityThreshold = 5f
}
-
- object Shapes {
- val Scrim =
- RoundedCornerShape(
- topStart = Dimensions.ScrimCornerSize,
- topEnd = Dimensions.ScrimCornerSize,
- )
- }
}
/** The shade scene shows scrolling list of notifications and some of the quick setting tiles. */
-@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class ShadeScene
@Inject
@@ -197,11 +184,11 @@
)
init {
- qqsMediaHost.expansion = MediaHostState.EXPANDED
+ qqsMediaHost.expansion = EXPANDED
qqsMediaHost.showsOnlyActiveMedia = true
qqsMediaHost.init(MediaHierarchyManager.LOCATION_QQS)
- qsMediaHost.expansion = MediaHostState.EXPANDED
+ qsMediaHost.expansion = EXPANDED
qsMediaHost.showsOnlyActiveMedia = false
qsMediaHost.init(MediaHierarchyManager.LOCATION_QS)
}
@@ -329,8 +316,7 @@
modifier =
modifier.thenIf(shouldPunchHoleBehindScrim) {
// Render the scene to an offscreen buffer so that BlendMode.DstOut only clears this
- // scene
- // (and not the one under it) during a scene transition.
+ // scene (and not the one under it) during a scene transition.
Modifier.graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen)
}
) {
@@ -382,8 +368,8 @@
stackScrollView = notificationStackScrollView,
viewModel = notificationsPlaceholderViewModel,
maxScrimTop = { maxNotifScrimTop.toFloat() },
- shadeMode = ShadeMode.Single,
shouldPunchHoleBehindScrim = shouldPunchHoleBehindScrim,
+ supportNestedScrolling = true,
onEmptySpaceClick =
viewModel::onEmptySpaceClicked.takeIf { isEmptySpaceClickable },
modifier =
@@ -601,7 +587,7 @@
maxScrimTop = { 0f },
shouldPunchHoleBehindScrim = false,
shouldReserveSpaceForNavBar = false,
- shadeMode = ShadeMode.Split,
+ supportNestedScrolling = false,
onEmptySpaceClick =
viewModel::onEmptySpaceClicked.takeIf { isEmptySpaceClickable },
modifier =
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
index dc3135d..aa70a0c 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
@@ -42,10 +42,8 @@
import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
import androidx.compose.ui.node.DelegatingNode
import androidx.compose.ui.node.ModifierNodeElement
-import androidx.compose.ui.node.ObserverModifierNode
import androidx.compose.ui.node.PointerInputModifierNode
import androidx.compose.ui.node.currentValueOf
-import androidx.compose.ui.node.observeReads
import androidx.compose.ui.platform.LocalViewConfiguration
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.Velocity
@@ -79,7 +77,6 @@
@Stable
internal fun Modifier.multiPointerDraggable(
orientation: Orientation,
- enabled: () -> Boolean,
startDragImmediately: (startedPosition: Offset) -> Boolean,
onDragStarted: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
onFirstPointerDown: () -> Unit = {},
@@ -89,7 +86,6 @@
this.then(
MultiPointerDraggableElement(
orientation,
- enabled,
startDragImmediately,
onDragStarted,
onFirstPointerDown,
@@ -100,7 +96,6 @@
private data class MultiPointerDraggableElement(
private val orientation: Orientation,
- private val enabled: () -> Boolean,
private val startDragImmediately: (startedPosition: Offset) -> Boolean,
private val onDragStarted:
(startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
@@ -111,7 +106,6 @@
override fun create(): MultiPointerDraggableNode =
MultiPointerDraggableNode(
orientation = orientation,
- enabled = enabled,
startDragImmediately = startDragImmediately,
onDragStarted = onDragStarted,
onFirstPointerDown = onFirstPointerDown,
@@ -121,7 +115,6 @@
override fun update(node: MultiPointerDraggableNode) {
node.orientation = orientation
- node.enabled = enabled
node.startDragImmediately = startDragImmediately
node.onDragStarted = onDragStarted
node.onFirstPointerDown = onFirstPointerDown
@@ -131,27 +124,23 @@
internal class MultiPointerDraggableNode(
orientation: Orientation,
- enabled: () -> Boolean,
var startDragImmediately: (startedPosition: Offset) -> Boolean,
var onDragStarted:
(startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
var onFirstPointerDown: () -> Unit,
- var swipeDetector: SwipeDetector = DefaultSwipeDetector,
+ swipeDetector: SwipeDetector = DefaultSwipeDetector,
private val dispatcher: NestedScrollDispatcher,
) :
DelegatingNode(),
PointerInputModifierNode,
CompositionLocalConsumerModifierNode,
- ObserverModifierNode,
SpaceVectorConverter {
private val pointerTracker = delegate(SuspendingPointerInputModifierNode { pointerTracker() })
private val pointerInput = delegate(SuspendingPointerInputModifierNode { pointerInput() })
private val velocityTracker = VelocityTracker()
- private var previousEnabled: Boolean = false
- var enabled: () -> Boolean = enabled
+ var swipeDetector: SwipeDetector = swipeDetector
set(value) {
- // Reset the pointer input whenever enabled changed.
if (value != field) {
field = value
pointerInput.resetPointerInputHandler()
@@ -178,21 +167,6 @@
}
}
- override fun onAttach() {
- previousEnabled = enabled()
- onObservedReadsChanged()
- }
-
- override fun onObservedReadsChanged() {
- observeReads {
- val newEnabled = enabled()
- if (newEnabled != previousEnabled) {
- pointerInput.resetPointerInputHandler()
- }
- previousEnabled = newEnabled
- }
- }
-
override fun onCancelPointerInput() {
pointerTracker.onCancelPointerInput()
pointerInput.onCancelPointerInput()
@@ -254,9 +228,7 @@
velocityTracker.resetTracking()
velocityTracker.addPointerInputChange(firstPointerDown)
startedPosition = firstPointerDown.position
- if (enabled()) {
- onFirstPointerDown()
- }
+ onFirstPointerDown()
}
// Changes with at least one pointer
@@ -295,10 +267,6 @@
}
private suspend fun PointerInputScope.pointerInput() {
- if (!enabled()) {
- return
- }
-
val currentContext = currentCoroutineContext()
awaitPointerEventScope {
while (currentContext.isActive) {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
index 98d4aaa..061583a 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
@@ -41,22 +41,62 @@
draggableHandler: DraggableHandlerImpl,
swipeDetector: SwipeDetector,
): Modifier {
- return this.then(SwipeToSceneElement(draggableHandler, swipeDetector))
+ return if (draggableHandler.enabled()) {
+ this.then(SwipeToSceneElement(draggableHandler, swipeDetector))
+ } else {
+ this
+ }
+}
+
+private fun DraggableHandlerImpl.enabled(): Boolean {
+ return isDrivingTransition || contentForSwipes().shouldEnableSwipes(orientation)
+}
+
+private fun DraggableHandlerImpl.contentForSwipes(): Content {
+ return layoutImpl.contentForUserActions()
+}
+
+/** Whether swipe should be enabled in the given [orientation]. */
+private fun Content.shouldEnableSwipes(orientation: Orientation): Boolean {
+ if (userActions.isEmpty()) {
+ return false
+ }
+
+ return userActions.keys.any { it is Swipe.Resolved && it.direction.orientation == orientation }
}
private data class SwipeToSceneElement(
val draggableHandler: DraggableHandlerImpl,
val swipeDetector: SwipeDetector,
-) : ModifierNodeElement<SwipeToSceneNode>() {
- override fun create(): SwipeToSceneNode = SwipeToSceneNode(draggableHandler, swipeDetector)
+) : ModifierNodeElement<SwipeToSceneRootNode>() {
+ override fun create(): SwipeToSceneRootNode =
+ SwipeToSceneRootNode(draggableHandler, swipeDetector)
- override fun update(node: SwipeToSceneNode) {
- node.draggableHandler = draggableHandler
+ override fun update(node: SwipeToSceneRootNode) {
+ node.update(draggableHandler, swipeDetector)
+ }
+}
+
+private class SwipeToSceneRootNode(
+ draggableHandler: DraggableHandlerImpl,
+ swipeDetector: SwipeDetector,
+) : DelegatingNode() {
+ private var delegate = delegate(SwipeToSceneNode(draggableHandler, swipeDetector))
+
+ fun update(draggableHandler: DraggableHandlerImpl, swipeDetector: SwipeDetector) {
+ if (draggableHandler == delegate.draggableHandler) {
+ // Simple update, just update the swipe detector directly and keep the node.
+ delegate.swipeDetector = swipeDetector
+ } else {
+ // The draggableHandler changed, force recreate the underlying SwipeToSceneNode.
+ undelegate(delegate)
+ delegate = delegate(SwipeToSceneNode(draggableHandler, swipeDetector))
+ }
}
}
private class SwipeToSceneNode(
- draggableHandler: DraggableHandlerImpl,
+ val draggableHandler: DraggableHandlerImpl,
swipeDetector: SwipeDetector,
) : DelegatingNode(), PointerInputModifierNode {
private val dispatcher = NestedScrollDispatcher()
@@ -64,7 +104,6 @@
delegate(
MultiPointerDraggableNode(
orientation = draggableHandler.orientation,
- enabled = ::enabled,
startDragImmediately = ::startDragImmediately,
onDragStarted = draggableHandler::onDragStarted,
onFirstPointerDown = ::onFirstPointerDown,
@@ -73,18 +112,10 @@
)
)
- private var _draggableHandler = draggableHandler
- var draggableHandler: DraggableHandlerImpl
- get() = _draggableHandler
+ var swipeDetector: SwipeDetector
+ get() = multiPointerDraggableNode.swipeDetector
set(value) {
- if (_draggableHandler != value) {
- _draggableHandler = value
-
- // Make sure to update the delegate orientation. Note that this will automatically
- // reset the underlying pointer input handler, so previous gestures will be
- // cancelled.
- multiPointerDraggableNode.orientation = value.orientation
- }
+ multiPointerDraggableNode.swipeDetector = value
}
private val nestedScrollHandlerImpl =
@@ -124,22 +155,6 @@
override fun onCancelPointerInput() = multiPointerDraggableNode.onCancelPointerInput()
- private fun enabled(): Boolean {
- return draggableHandler.isDrivingTransition ||
- contentForSwipes().shouldEnableSwipes(multiPointerDraggableNode.orientation)
- }
-
- private fun contentForSwipes(): Content {
- return draggableHandler.layoutImpl.contentForUserActions()
- }
-
- /** Whether swipe should be enabled in the given [orientation]. */
- private fun Content.shouldEnableSwipes(orientation: Orientation): Boolean {
- return userActions.keys.any {
- it is Swipe.Resolved && it.direction.orientation == orientation
- }
- }
-
private fun startDragImmediately(startedPosition: Offset): Boolean {
// Immediately start the drag if the user can't swipe in the other direction and the gesture
// handler can intercept it.
@@ -152,7 +167,7 @@
Orientation.Vertical -> Orientation.Horizontal
Orientation.Horizontal -> Orientation.Vertical
}
- return contentForSwipes().shouldEnableSwipes(oppositeOrientation)
+ return draggableHandler.contentForSwipes().shouldEnableSwipes(oppositeOrientation)
}
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
index 493f3a1..c8f6e6d 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
@@ -45,6 +45,7 @@
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Velocity
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.modifiers.thenIf
import com.android.compose.nestedscroll.SuspendedValue
import com.google.common.truth.Truth.assertThat
import kotlin.properties.Delegates
@@ -94,19 +95,20 @@
Box(
Modifier.size(with(LocalDensity.current) { Size(size, size).toDpSize() })
.nestedScrollDispatcher()
- .multiPointerDraggable(
- orientation = Orientation.Vertical,
- enabled = { enabled },
- startDragImmediately = { false },
- onDragStarted = { _, _, _ ->
- started = true
- SimpleDragController(
- onDrag = { dragged = true },
- onStop = { stopped = true },
- )
- },
- dispatcher = defaultDispatcher,
- )
+ .thenIf(enabled) {
+ Modifier.multiPointerDraggable(
+ orientation = Orientation.Vertical,
+ startDragImmediately = { false },
+ onDragStarted = { _, _, _ ->
+ started = true
+ SimpleDragController(
+ onDrag = { dragged = true },
+ onStop = { stopped = true },
+ )
+ },
+ dispatcher = defaultDispatcher,
+ )
+ }
)
}
@@ -164,7 +166,6 @@
.nestedScrollDispatcher()
.multiPointerDraggable(
orientation = Orientation.Vertical,
- enabled = { true },
// We want to start a drag gesture immediately
startDragImmediately = { true },
onDragStarted = { _, _, _ ->
@@ -238,7 +239,6 @@
.nestedScrollDispatcher()
.multiPointerDraggable(
orientation = Orientation.Vertical,
- enabled = { true },
startDragImmediately = { false },
onDragStarted = { _, _, _ ->
started = true
@@ -358,7 +358,6 @@
.nestedScrollDispatcher()
.multiPointerDraggable(
orientation = Orientation.Vertical,
- enabled = { true },
startDragImmediately = { false },
onDragStarted = { _, _, _ ->
started = true
@@ -464,7 +463,6 @@
.nestedScrollDispatcher()
.multiPointerDraggable(
orientation = Orientation.Vertical,
- enabled = { true },
startDragImmediately = { false },
onDragStarted = { _, _, _ ->
verticalStarted = true
@@ -477,7 +475,6 @@
)
.multiPointerDraggable(
orientation = Orientation.Horizontal,
- enabled = { true },
startDragImmediately = { false },
onDragStarted = { _, _, _ ->
horizontalStarted = true
@@ -570,7 +567,6 @@
.nestedScrollDispatcher()
.multiPointerDraggable(
orientation = Orientation.Vertical,
- enabled = { true },
startDragImmediately = { false },
swipeDetector =
object : SwipeDetector {
@@ -672,7 +668,6 @@
.nestedScrollDispatcher()
.multiPointerDraggable(
orientation = Orientation.Vertical,
- enabled = { true },
startDragImmediately = { false },
onDragStarted = { _, _, _ ->
SimpleDragController(
@@ -744,7 +739,6 @@
.nestedScrollDispatcher()
.multiPointerDraggable(
orientation = Orientation.Vertical,
- enabled = { true },
startDragImmediately = { false },
onDragStarted = { _, _, _ ->
SimpleDragController(
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
index 25e8713..28d0a47 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
@@ -22,11 +22,15 @@
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.size
+import androidx.compose.material3.Button
+import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
@@ -36,9 +40,14 @@
import androidx.compose.ui.platform.LocalViewConfiguration
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.test.assertPositionInRootIsEqualTo
+import androidx.compose.ui.test.assertTextEquals
import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.test.swipeRight
+import androidx.compose.ui.test.swipeUp
import androidx.compose.ui.test.swipeWithVelocity
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntSize
@@ -844,4 +853,62 @@
assertThat(transition.progress).isEqualTo(1f)
assertThat(availableOnPostScroll).isEqualTo(ovescrollPx)
}
+
+ @Test
+ fun sceneWithoutSwipesDoesNotConsumeGestures() {
+ val buttonTag = "button"
+
+ rule.setContent {
+ Box {
+ var count by remember { mutableStateOf(0) }
+ Button(onClick = { count++ }, Modifier.testTag(buttonTag).align(Alignment.Center)) {
+ Text("Count: $count")
+ }
+
+ SceneTransitionLayout(remember { MutableSceneTransitionLayoutState(SceneA) }) {
+ scene(SceneA) { Box(Modifier.fillMaxSize()) }
+ }
+ }
+ }
+
+ rule.onNodeWithTag(buttonTag).assertTextEquals("Count: 0")
+
+ // Click on the root at its center, where the button is located. Clicks should go through
+ // the STL and reach the button given that there is no swipes for the current scene.
+ repeat(3) { rule.onRoot().performClick() }
+ rule.onNodeWithTag(buttonTag).assertTextEquals("Count: 3")
+ }
+
+ @Test
+ fun swipeToSceneSupportsUpdates() {
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+
+ rule.setContent {
+ SceneTransitionLayout(state) {
+ // SceneA only has vertical actions, so only one vertical Modifier.swipeToScene()
+ // is composed.
+ scene(SceneA, mapOf(Swipe.Up to SceneB)) { Box(Modifier.fillMaxSize()) }
+
+ // SceneB only has horizontal actions, so only one vertical Modifier.swipeToScene()
+ // is composed, which will be force update it with a new draggableHandler.
+ scene(SceneB, mapOf(Swipe.Right to SceneC)) { Box(Modifier.fillMaxSize()) }
+ scene(SceneC) { Box(Modifier.fillMaxSize()) }
+ }
+ }
+
+ assertThat(state.transitionState).isIdle()
+ assertThat(state.transitionState).hasCurrentScene(SceneA)
+
+ // Swipe up to scene B.
+ rule.onRoot().performTouchInput { swipeUp() }
+ rule.waitForIdle()
+ assertThat(state.transitionState).isIdle()
+ assertThat(state.transitionState).hasCurrentScene(SceneB)
+
+ // Swipe right to scene C.
+ rule.onRoot().performTouchInput { swipeRight() }
+ rule.waitForIdle()
+ assertThat(state.transitionState).isIdle()
+ assertThat(state.transitionState).hasCurrentScene(SceneC)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
index 94d3b2c..176824f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
@@ -61,6 +61,7 @@
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.policy.UserSwitcherController;
+import com.android.systemui.statusbar.policy.UserSwitcherController.UserSwitchCallback;
import com.android.systemui.user.data.source.UserRecord;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.settings.GlobalSettings;
@@ -70,6 +71,8 @@
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@@ -96,6 +99,9 @@
private UserSwitcherController mUserSwitcherController;
@Mock
private FalsingA11yDelegate mFalsingA11yDelegate;
+ @Captor
+ private ArgumentCaptor<UserSwitchCallback> mUserSwitchCallbackCaptor =
+ ArgumentCaptor.forClass(UserSwitchCallback.class);
private KeyguardSecurityContainer mKeyguardSecurityContainer;
private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
@@ -360,6 +366,18 @@
}
@Test
+ public void goingOutOfUserSwitcherRemovesCallback() {
+ // WHEN UserSwitcherViewMode is initialized
+ setupUserSwitcher();
+ verify(mUserSwitcherController).addUserSwitchCallback(mUserSwitchCallbackCaptor.capture());
+
+ // Back to default mode, as SIM PIN would be
+ initMode(MODE_DEFAULT);
+ verify(mUserSwitcherController).removeUserSwitchCallback(
+ mUserSwitchCallbackCaptor.getValue());
+ }
+
+ @Test
public void testOnDensityOrFontScaleChanged() {
setupUserSwitcher();
View oldUserSwitcher = mKeyguardSecurityContainer.findViewById(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
index 3d30ecc..8ae9d2e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
@@ -25,8 +25,10 @@
import android.graphics.Bitmap
import android.os.UserHandle
import android.os.userManager
+import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_COMMUNAL_WIDGET_RESIZING
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.data.repository.fakePackageChangeRepository
import com.android.systemui.common.shared.model.PackageInstallSession
@@ -156,6 +158,7 @@
appWidgetId = communalWidgetItemEntry.widgetId,
providerInfo = providerInfoA,
rank = communalItemRankEntry.rank,
+ spanY = communalWidgetItemEntry.spanY,
)
)
@@ -188,11 +191,13 @@
appWidgetId = 1,
providerInfo = providerInfoA,
rank = 1,
+ spanY = 3,
),
CommunalWidgetContentModel.Available(
appWidgetId = 2,
providerInfo = providerInfoB,
rank = 2,
+ spanY = 3,
),
)
}
@@ -219,11 +224,13 @@
appWidgetId = 1,
providerInfo = providerInfoA,
rank = 1,
+ spanY = 3,
),
CommunalWidgetContentModel.Available(
appWidgetId = 2,
providerInfo = providerInfoB,
rank = 2,
+ spanY = 3,
),
)
@@ -238,11 +245,13 @@
// Verify that provider info updated
providerInfo = providerInfoC,
rank = 1,
+ spanY = 3,
),
CommunalWidgetContentModel.Available(
appWidgetId = 2,
providerInfo = providerInfoB,
rank = 2,
+ spanY = 3,
),
)
}
@@ -681,6 +690,7 @@
appWidgetId = 1,
providerInfo = providerInfoA,
rank = 1,
+ spanY = 3,
),
CommunalWidgetContentModel.Pending(
appWidgetId = 2,
@@ -688,6 +698,7 @@
componentName = ComponentName("pk_2", "cls_2"),
icon = fakeIcon,
user = mainUser,
+ spanY = 3,
),
)
}
@@ -723,6 +734,7 @@
componentName = ComponentName("pk_1", "cls_1"),
icon = fakeIcon,
user = mainUser,
+ spanY = 3,
)
)
@@ -740,20 +752,23 @@
appWidgetId = 1,
providerInfo = providerInfoA,
rank = 1,
+ spanY = 3,
)
)
}
@Test
+ @EnableFlags(FLAG_COMMUNAL_WIDGET_RESIZING)
fun updateWidgetSpanY_updatesWidgetInDaoAndRequestsBackup() =
testScope.runTest {
val widgetId = 1
val newSpanY = 6
+ val widgetIdToRankMap = emptyMap<Int, Int>()
- underTest.updateWidgetSpanY(widgetId, newSpanY)
+ underTest.resizeWidget(widgetId, newSpanY, widgetIdToRankMap)
runCurrent()
- verify(communalWidgetDao).updateWidgetSpanY(widgetId, newSpanY)
+ verify(communalWidgetDao).resizeWidget(widgetId, newSpanY, widgetIdToRankMap)
verify(backupManager).dataChanged()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index b96e40f..611a61a6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -24,6 +24,7 @@
import android.os.UserHandle
import android.os.UserManager
import android.os.userManager
+import android.platform.test.annotations.EnableFlags
import android.provider.Settings
import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED
import android.widget.RemoteViews
@@ -31,6 +32,7 @@
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
+import com.android.systemui.Flags.FLAG_COMMUNAL_WIDGET_RESIZING
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.broadcastDispatcher
import com.android.systemui.communal.data.model.CommunalSmartspaceTimer
@@ -1078,6 +1080,108 @@
assertThat(managedProfileController.isWorkModeEnabled()).isTrue()
}
+ @Test
+ @EnableFlags(FLAG_COMMUNAL_WIDGET_RESIZING)
+ fun resizeWidget_withoutUpdatingOrder() =
+ testScope.runTest {
+ val userInfos = listOf(MAIN_USER_INFO)
+ userRepository.setUserInfos(userInfos)
+ userTracker.set(userInfos = userInfos, selectedUserIndex = 0)
+ runCurrent()
+
+ // Widgets available.
+ widgetRepository.addWidget(
+ appWidgetId = 1,
+ userId = MAIN_USER_INFO.id,
+ rank = 0,
+ spanY = CommunalContentSize.HALF.span,
+ )
+ widgetRepository.addWidget(
+ appWidgetId = 2,
+ userId = MAIN_USER_INFO.id,
+ rank = 1,
+ spanY = CommunalContentSize.HALF.span,
+ )
+ widgetRepository.addWidget(
+ appWidgetId = 3,
+ userId = MAIN_USER_INFO.id,
+ rank = 2,
+ spanY = CommunalContentSize.HALF.span,
+ )
+
+ val widgetContent by collectLastValue(underTest.widgetContent)
+
+ assertThat(widgetContent?.map { it.appWidgetId to it.size })
+ .containsExactly(
+ 1 to CommunalContentSize.HALF,
+ 2 to CommunalContentSize.HALF,
+ 3 to CommunalContentSize.HALF,
+ )
+ .inOrder()
+
+ underTest.resizeWidget(2, CommunalContentSize.FULL.span, emptyMap())
+
+ // Widget 2 should have been resized to FULL
+ assertThat(widgetContent?.map { it.appWidgetId to it.size })
+ .containsExactly(
+ 1 to CommunalContentSize.HALF,
+ 2 to CommunalContentSize.FULL,
+ 3 to CommunalContentSize.HALF,
+ )
+ .inOrder()
+ }
+
+ @Test
+ @EnableFlags(FLAG_COMMUNAL_WIDGET_RESIZING)
+ fun resizeWidget_andUpdateOrder() =
+ testScope.runTest {
+ val userInfos = listOf(MAIN_USER_INFO)
+ userRepository.setUserInfos(userInfos)
+ userTracker.set(userInfos = userInfos, selectedUserIndex = 0)
+ runCurrent()
+
+ // Widgets available.
+ widgetRepository.addWidget(
+ appWidgetId = 1,
+ userId = MAIN_USER_INFO.id,
+ rank = 0,
+ spanY = CommunalContentSize.HALF.span,
+ )
+ widgetRepository.addWidget(
+ appWidgetId = 2,
+ userId = MAIN_USER_INFO.id,
+ rank = 1,
+ spanY = CommunalContentSize.HALF.span,
+ )
+ widgetRepository.addWidget(
+ appWidgetId = 3,
+ userId = MAIN_USER_INFO.id,
+ rank = 2,
+ spanY = CommunalContentSize.HALF.span,
+ )
+
+ val widgetContent by collectLastValue(underTest.widgetContent)
+
+ assertThat(widgetContent?.map { it.appWidgetId to it.size })
+ .containsExactly(
+ 1 to CommunalContentSize.HALF,
+ 2 to CommunalContentSize.HALF,
+ 3 to CommunalContentSize.HALF,
+ )
+ .inOrder()
+
+ underTest.resizeWidget(2, CommunalContentSize.FULL.span, mapOf(2 to 0, 1 to 1))
+
+ // Widget 2 should have been resized to FULL and moved to the front of the list
+ assertThat(widgetContent?.map { it.appWidgetId to it.size })
+ .containsExactly(
+ 2 to CommunalContentSize.FULL,
+ 1 to CommunalContentSize.HALF,
+ 3 to CommunalContentSize.HALF,
+ )
+ .inOrder()
+ }
+
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/communal/ui/viewmodel/ResizeableItemFrameViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModelTest.kt
new file mode 100644
index 0000000..f0d88ab
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModelTest.kt
@@ -0,0 +1,340 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.ui.viewmodel
+
+import androidx.compose.foundation.gestures.DraggableAnchors
+import androidx.compose.runtime.snapshots.Snapshot
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlin.time.Duration
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ResizeableItemFrameViewModelTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val underTest = kosmos.resizeableItemFrameViewModel
+
+ /** Total viewport height of the entire grid */
+ private val viewportHeightPx = 100
+ /** Total amount of vertical padding around the viewport */
+ private val verticalContentPaddingPx = 20f
+
+ private val singleSpanGrid =
+ GridLayout(
+ verticalItemSpacingPx = 10f,
+ verticalContentPaddingPx = verticalContentPaddingPx,
+ viewportHeightPx = viewportHeightPx,
+ maxItemSpan = 1,
+ minItemSpan = 1,
+ currentSpan = 1,
+ currentRow = 0,
+ )
+
+ @Before
+ fun setUp() {
+ underTest.activateIn(testScope)
+ }
+
+ @Test
+ fun testDefaultState() {
+ val topState = underTest.topDragState
+ assertThat(topState.currentValue).isEqualTo(0)
+ assertThat(topState.offset).isEqualTo(0f)
+ assertThat(topState.anchors.toList()).containsExactly(0 to 0f)
+
+ val bottomState = underTest.bottomDragState
+ assertThat(bottomState.currentValue).isEqualTo(0)
+ assertThat(bottomState.offset).isEqualTo(0f)
+ assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f)
+ }
+
+ @Test
+ fun testSingleSpanGrid() =
+ testScope.runTest(timeout = Duration.INFINITE) {
+ updateGridLayout(singleSpanGrid)
+
+ val topState = underTest.topDragState
+ assertThat(topState.currentValue).isEqualTo(0)
+ assertThat(topState.anchors.toList()).containsExactly(0 to 0f)
+
+ val bottomState = underTest.bottomDragState
+ assertThat(bottomState.currentValue).isEqualTo(0)
+ assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f)
+ }
+
+ /**
+ * Verifies element in first row which is already at the minimum size can only be expanded
+ * downwards.
+ */
+ @Test
+ fun testTwoSpanGrid_elementInFirstRow_sizeSingleSpan() =
+ testScope.runTest {
+ updateGridLayout(singleSpanGrid.copy(maxItemSpan = 2))
+
+ val topState = underTest.topDragState
+ assertThat(topState.currentValue).isEqualTo(0)
+ assertThat(topState.anchors.toList()).containsExactly(0 to 0f)
+
+ val bottomState = underTest.bottomDragState
+ assertThat(bottomState.currentValue).isEqualTo(0)
+ assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f, 1 to 45f)
+ }
+
+ /**
+ * Verifies element in second row which is already at the minimum size can only be expanded
+ * upwards.
+ */
+ @Test
+ fun testTwoSpanGrid_elementInSecondRow_sizeSingleSpan() =
+ testScope.runTest {
+ updateGridLayout(singleSpanGrid.copy(maxItemSpan = 2, currentRow = 1))
+
+ val topState = underTest.topDragState
+ assertThat(topState.currentValue).isEqualTo(0)
+ assertThat(topState.anchors.toList()).containsExactly(0 to 0f, -1 to -45f)
+
+ val bottomState = underTest.bottomDragState
+ assertThat(bottomState.currentValue).isEqualTo(0)
+ assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f)
+ }
+
+ /**
+ * Verifies element in first row which is already at full size (2 span) can only be shrunk from
+ * the bottom.
+ */
+ @Test
+ fun testTwoSpanGrid_elementInFirstRow_sizeTwoSpan() =
+ testScope.runTest {
+ updateGridLayout(singleSpanGrid.copy(maxItemSpan = 2, currentSpan = 2))
+
+ val topState = underTest.topDragState
+ assertThat(topState.currentValue).isEqualTo(0)
+ assertThat(topState.anchors.toList()).containsExactly(0 to 0f)
+
+ val bottomState = underTest.bottomDragState
+ assertThat(bottomState.currentValue).isEqualTo(0)
+ assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f, -1 to -45f)
+ }
+
+ /**
+ * Verifies element in a middle row at minimum size can be expanded from either top or bottom.
+ */
+ @Test
+ fun testThreeSpanGrid_elementInMiddleRow_sizeOneSpan() =
+ testScope.runTest {
+ updateGridLayout(singleSpanGrid.copy(maxItemSpan = 3, currentRow = 1))
+
+ val topState = underTest.topDragState
+ assertThat(topState.currentValue).isEqualTo(0)
+ assertThat(topState.anchors.toList()).containsExactly(0 to 0f, -1 to -30f)
+
+ val bottomState = underTest.bottomDragState
+ assertThat(bottomState.currentValue).isEqualTo(0)
+ assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f, 1 to 30f)
+ }
+
+ @Test
+ fun testThreeSpanGrid_elementInTopRow_sizeOneSpan() =
+ testScope.runTest {
+ updateGridLayout(singleSpanGrid.copy(maxItemSpan = 3))
+
+ val topState = underTest.topDragState
+ assertThat(topState.currentValue).isEqualTo(0)
+ assertThat(topState.anchors.toList()).containsExactly(0 to 0f)
+
+ val bottomState = underTest.bottomDragState
+ assertThat(bottomState.currentValue).isEqualTo(0)
+ assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f, 1 to 30f, 2 to 60f)
+ }
+
+ @Test
+ fun testSixSpanGrid_minSpanThree_itemInThirdRow_sizeThreeSpans() =
+ testScope.runTest {
+ updateGridLayout(
+ singleSpanGrid.copy(
+ maxItemSpan = 6,
+ currentRow = 3,
+ currentSpan = 3,
+ minItemSpan = 3,
+ )
+ )
+
+ val topState = underTest.topDragState
+ assertThat(topState.currentValue).isEqualTo(0)
+ assertThat(topState.anchors.toList()).containsExactly(0 to 0f, -3 to -45f)
+
+ val bottomState = underTest.bottomDragState
+ assertThat(bottomState.currentValue).isEqualTo(0)
+ assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f)
+ }
+
+ @Test
+ fun testTwoSpanGrid_elementMovesFromFirstRowToSecondRow() =
+ testScope.runTest {
+ updateGridLayout(singleSpanGrid.copy(maxItemSpan = 2))
+
+ val topState = underTest.topDragState
+ val bottomState = underTest.bottomDragState
+
+ assertThat(topState.anchors.toList()).containsExactly(0 to 0f)
+ assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f, 1 to 45f)
+
+ updateGridLayout(singleSpanGrid.copy(maxItemSpan = 2, currentRow = 1))
+
+ assertThat(topState.anchors.toList()).containsExactly(0 to 0f, -1 to -45f)
+ assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f)
+ }
+
+ @Test
+ fun testTwoSpanGrid_expandElementFromBottom() = runTestWithSnapshots {
+ val resizeInfo by collectLastValue(underTest.resizeInfo)
+ updateGridLayout(singleSpanGrid.copy(maxItemSpan = 2))
+
+ assertThat(resizeInfo).isNull()
+ underTest.bottomDragState.anchoredDrag { dragTo(45f) }
+ assertThat(resizeInfo).isEqualTo(ResizeInfo(1, DragHandle.BOTTOM))
+ }
+
+ @Test
+ fun testThreeSpanGrid_expandMiddleElementUpwards() = runTestWithSnapshots {
+ val resizeInfo by collectLastValue(underTest.resizeInfo)
+ updateGridLayout(singleSpanGrid.copy(maxItemSpan = 3, currentRow = 1))
+
+ assertThat(resizeInfo).isNull()
+ underTest.topDragState.anchoredDrag { dragTo(-30f) }
+ assertThat(resizeInfo).isEqualTo(ResizeInfo(1, DragHandle.TOP))
+ }
+
+ @Test
+ fun testThreeSpanGrid_expandTopElementDownBy2Spans() = runTestWithSnapshots {
+ val resizeInfo by collectLastValue(underTest.resizeInfo)
+ updateGridLayout(singleSpanGrid.copy(maxItemSpan = 3))
+
+ assertThat(resizeInfo).isNull()
+ underTest.bottomDragState.anchoredDrag { dragTo(60f) }
+ assertThat(resizeInfo).isEqualTo(ResizeInfo(2, DragHandle.BOTTOM))
+ }
+
+ @Test
+ fun testTwoSpanGrid_shrinkElementFromBottom() = runTestWithSnapshots {
+ val resizeInfo by collectLastValue(underTest.resizeInfo)
+ updateGridLayout(singleSpanGrid.copy(maxItemSpan = 2, currentSpan = 2))
+
+ assertThat(resizeInfo).isNull()
+ underTest.bottomDragState.anchoredDrag { dragTo(-45f) }
+ assertThat(resizeInfo).isEqualTo(ResizeInfo(-1, DragHandle.BOTTOM))
+ }
+
+ @Test
+ fun testRowInfoBecomesNull_revertsBackToDefault() =
+ testScope.runTest {
+ val gridLayout = singleSpanGrid.copy(maxItemSpan = 3, currentRow = 1)
+ updateGridLayout(gridLayout)
+
+ val topState = underTest.topDragState
+ assertThat(topState.anchors.toList()).containsExactly(0 to 0f, -1 to -30f)
+
+ val bottomState = underTest.bottomDragState
+ assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f, 1 to 30f)
+
+ updateGridLayout(gridLayout.copy(currentRow = null))
+ assertThat(topState.anchors.toList()).containsExactly(0 to 0f)
+ assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun testIllegalState_maxSpanSmallerThanMinSpan() =
+ testScope.runTest {
+ updateGridLayout(singleSpanGrid.copy(maxItemSpan = 2, minItemSpan = 3))
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun testIllegalState_minSpanOfZero() =
+ testScope.runTest {
+ updateGridLayout(singleSpanGrid.copy(maxItemSpan = 2, minItemSpan = 0))
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun testIllegalState_maxSpanOfZero() =
+ testScope.runTest {
+ updateGridLayout(singleSpanGrid.copy(maxItemSpan = 0, minItemSpan = 0))
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun testIllegalState_currentRowNotMultipleOfMinSpan() =
+ testScope.runTest {
+ updateGridLayout(singleSpanGrid.copy(maxItemSpan = 6, minItemSpan = 3, currentSpan = 2))
+ }
+
+ private fun TestScope.updateGridLayout(gridLayout: GridLayout) {
+ underTest.setGridLayoutInfo(
+ gridLayout.verticalItemSpacingPx,
+ gridLayout.verticalContentPaddingPx,
+ gridLayout.viewportHeightPx,
+ gridLayout.maxItemSpan,
+ gridLayout.minItemSpan,
+ gridLayout.currentRow,
+ gridLayout.currentSpan,
+ )
+ runCurrent()
+ }
+
+ private fun DraggableAnchors<Int>.toList() = buildList {
+ for (index in 0 until this@toList.size) {
+ add(anchorAt(index) to positionAt(index))
+ }
+ }
+
+ private fun runTestWithSnapshots(testBody: suspend TestScope.() -> Unit) {
+ val globalWriteObserverHandle =
+ Snapshot.registerGlobalWriteObserver {
+ // This is normally done by the compose runtime.
+ Snapshot.sendApplyNotifications()
+ }
+
+ try {
+ testScope.runTest(testBody = testBody)
+ } finally {
+ globalWriteObserverHandle.dispose()
+ }
+ }
+
+ private data class GridLayout(
+ val verticalItemSpacingPx: Float,
+ val verticalContentPaddingPx: Float,
+ val viewportHeightPx: Int,
+ val maxItemSpan: Int,
+ val minItemSpan: Int,
+ val currentRow: Int?,
+ val currentSpan: Int?,
+ )
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
index 40b2a08..a0c56b4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
@@ -139,7 +139,7 @@
TransitionStep(
KeyguardState.OFF,
KeyguardState.LOCKSCREEN,
- transitionState = TransitionState.STARTED
+ transitionState = TransitionState.STARTED,
)
)
@@ -181,7 +181,7 @@
TransitionStep(
KeyguardState.AOD,
KeyguardState.LOCKSCREEN,
- transitionState = TransitionState.STARTED
+ transitionState = TransitionState.STARTED,
)
)
@@ -206,7 +206,7 @@
TransitionStep(
KeyguardState.DOZING,
KeyguardState.LOCKSCREEN,
- transitionState = TransitionState.STARTED
+ transitionState = TransitionState.STARTED,
)
)
@@ -229,7 +229,7 @@
TransitionStep(
KeyguardState.DOZING,
KeyguardState.LOCKSCREEN,
- transitionState = TransitionState.STARTED
+ transitionState = TransitionState.STARTED,
)
)
@@ -244,12 +244,14 @@
fun faceAuthLockedOutStateIsUpdatedAfterUserSwitch() =
testScope.runTest {
underTest.start()
+ runCurrent()
+ fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
// User switching has started
fakeUserRepository.setSelectedUserInfo(primaryUser, SelectionStatus.SELECTION_COMPLETE)
fakeUserRepository.setSelectedUserInfo(
primaryUser,
- SelectionStatus.SELECTION_IN_PROGRESS
+ SelectionStatus.SELECTION_IN_PROGRESS,
)
runCurrent()
@@ -258,7 +260,7 @@
facePropertyRepository.setLockoutMode(secondaryUser.id, LockoutMode.NONE)
fakeUserRepository.setSelectedUserInfo(
secondaryUser,
- SelectionStatus.SELECTION_COMPLETE
+ SelectionStatus.SELECTION_COMPLETE,
)
runCurrent()
@@ -316,7 +318,7 @@
.isEqualTo(
Pair(
FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN,
- false
+ false,
)
)
}
@@ -600,7 +602,7 @@
faceAuthRepository.requestAuthenticate(
FaceAuthUiEvent.FACE_AUTH_UPDATED_KEYGUARD_VISIBILITY_CHANGED,
- true
+ true,
)
facePropertyRepository.setCameraIno(CameraInfo("0", "1", null))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt
index d7fe263..dd83702 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt
@@ -71,7 +71,7 @@
@Mock private lateinit var accessibilityManagerWrapper: AccessibilityManagerWrapper
@get:Rule val mockitoRule = MockitoJUnit.rule()
private var toastContent = ""
- private val timeoutMillis = 3500L
+ private val timeoutMillis = 5000L
@Before
fun setUp() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt
index 17e3006..047d8c2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt
@@ -42,7 +42,8 @@
import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepositorySpy
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
import com.android.systemui.kosmos.testScope
@@ -57,7 +58,6 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mockito
import org.mockito.Mockito.reset
@ExperimentalCoroutinesApi
@@ -66,7 +66,7 @@
class FromAlternateBouncerTransitionInteractorTest : SysuiTestCase() {
private val kosmos =
testKosmos().apply {
- this.fakeKeyguardTransitionRepository = Mockito.spy(FakeKeyguardTransitionRepository())
+ this.keyguardTransitionRepository = fakeKeyguardTransitionRepositorySpy
}
private val testScope = kosmos.testScope
private lateinit var underTest: FromAlternateBouncerTransitionInteractor
@@ -74,7 +74,7 @@
@Before
fun setup() {
- transitionRepository = kosmos.fakeKeyguardTransitionRepository
+ transitionRepository = kosmos.fakeKeyguardTransitionRepositorySpy
underTest = kosmos.fromAlternateBouncerTransitionInteractor
underTest.start()
}
@@ -86,7 +86,7 @@
transitionRepository.sendTransitionSteps(
from = KeyguardState.OCCLUDED,
to = KeyguardState.ALTERNATE_BOUNCER,
- testScope
+ testScope,
)
reset(transitionRepository)
@@ -111,7 +111,7 @@
transitionRepository.sendTransitionSteps(
from = KeyguardState.OCCLUDED,
to = KeyguardState.ALTERNATE_BOUNCER,
- testScope
+ testScope,
)
reset(transitionRepository)
@@ -129,7 +129,7 @@
transitionRepository.sendTransitionSteps(
from = KeyguardState.OCCLUDED,
to = KeyguardState.ALTERNATE_BOUNCER,
- testScope
+ testScope,
)
reset(transitionRepository)
@@ -158,7 +158,7 @@
transitionRepository.sendTransitionSteps(
from = KeyguardState.OCCLUDED,
to = KeyguardState.ALTERNATE_BOUNCER,
- testScope
+ testScope,
)
reset(transitionRepository)
@@ -168,7 +168,7 @@
assertThat(transitionRepository)
.startedTransition(
from = KeyguardState.ALTERNATE_BOUNCER,
- to = KeyguardState.OCCLUDED
+ to = KeyguardState.OCCLUDED,
)
}
@@ -183,7 +183,7 @@
transitionRepository.sendTransitionSteps(
from = KeyguardState.GLANCEABLE_HUB,
to = KeyguardState.ALTERNATE_BOUNCER,
- testScope
+ testScope,
)
reset(transitionRepository)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
index 33f3cd4..9300964 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
@@ -42,8 +42,9 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepositorySpy
import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
@@ -69,7 +70,6 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.reset
-import org.mockito.Mockito.spy
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -77,7 +77,7 @@
class FromAodTransitionInteractorTest : SysuiTestCase() {
private val kosmos =
testKosmos().apply {
- this.fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository())
+ this.keyguardTransitionRepository = fakeKeyguardTransitionRepositorySpy
}
private val testScope = kosmos.testScope
@@ -89,7 +89,7 @@
@Before
fun setup() {
powerInteractor = kosmos.powerInteractor
- transitionRepository = kosmos.fakeKeyguardTransitionRepository
+ transitionRepository = kosmos.fakeKeyguardTransitionRepositorySpy
underTest = kosmos.fromAodTransitionInteractor
underTest.start()
@@ -101,7 +101,7 @@
transitionRepository.sendTransitionSteps(
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.AOD,
- testScope
+ testScope,
)
kosmos.fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockMode.NONE)
reset(transitionRepository)
@@ -117,10 +117,7 @@
// Under default conditions, we should transition to LOCKSCREEN when waking up.
assertThat(transitionRepository)
- .startedTransition(
- from = KeyguardState.AOD,
- to = KeyguardState.LOCKSCREEN,
- )
+ .startedTransition(from = KeyguardState.AOD, to = KeyguardState.LOCKSCREEN)
}
@Test
@@ -133,10 +130,7 @@
// Waking with a SHOW_WHEN_LOCKED activity on top should transition to OCCLUDED.
assertThat(transitionRepository)
- .startedTransition(
- from = KeyguardState.AOD,
- to = KeyguardState.OCCLUDED,
- )
+ .startedTransition(from = KeyguardState.AOD, to = KeyguardState.OCCLUDED)
}
@Test
@@ -363,13 +357,13 @@
from = KeyguardState.GONE,
to = KeyguardState.AOD,
transitionState = TransitionState.STARTED,
- value = 0f
+ value = 0f,
),
TransitionStep(
from = KeyguardState.GONE,
to = KeyguardState.AOD,
transitionState = TransitionState.RUNNING,
- value = 0.1f
+ value = 0.1f,
),
),
testScope = testScope,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
index ff0a4a1..3b6e5d0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
@@ -36,8 +36,9 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepositorySpy
import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
@@ -79,7 +80,7 @@
class FromDozingTransitionInteractorTest(flags: FlagsParameterization?) : SysuiTestCase() {
private val kosmos =
testKosmos().apply {
- this.fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository())
+ this.keyguardTransitionRepository = fakeKeyguardTransitionRepositorySpy
this.fakeCommunalSceneRepository =
spy(FakeCommunalSceneRepository(applicationScope = applicationCoroutineScope))
}
@@ -105,7 +106,7 @@
@Before
fun setup() {
powerInteractor = kosmos.powerInteractor
- transitionRepository = kosmos.fakeKeyguardTransitionRepository
+ transitionRepository = kosmos.fakeKeyguardTransitionRepositorySpy
underTest = kosmos.fromDozingTransitionInteractor
underTest.start()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
index fa304c9..9ca3ce6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
@@ -32,7 +32,9 @@
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepositorySpy
import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
@@ -53,7 +55,6 @@
import org.junit.runner.RunWith
import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.reset
-import org.mockito.Mockito.spy
import org.mockito.kotlin.whenever
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters
@@ -77,14 +78,21 @@
private val kosmos =
testKosmos().apply {
- this.fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository())
+ this.fakeKeyguardTransitionRepository =
+ FakeKeyguardTransitionRepository(
+ // This test sends transition steps manually in the test cases.
+ sendTransitionStepsOnStartTransition = false,
+ testScope = testScope,
+ )
+
+ this.keyguardTransitionRepository = fakeKeyguardTransitionRepositorySpy
}
private val testScope = kosmos.testScope
private val underTest by lazy { kosmos.fromDreamingTransitionInteractor }
private val powerInteractor = kosmos.powerInteractor
- private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
+ private val transitionRepository = kosmos.fakeKeyguardTransitionRepositorySpy
@Before
fun setup() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorTest.kt
index af76b08..57b1299 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorTest.kt
@@ -23,10 +23,10 @@
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepositorySpy
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.AuthenticationFlags
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
@@ -41,18 +41,17 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.reset
-import org.mockito.Mockito.spy
@SmallTest
@RunWith(AndroidJUnit4::class)
class FromGoneTransitionInteractorTest : SysuiTestCase() {
private val kosmos =
testKosmos().apply {
- fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository())
+ this.keyguardTransitionRepository = fakeKeyguardTransitionRepositorySpy
}
private val testScope = kosmos.testScope
private val underTest = kosmos.fromGoneTransitionInteractor
- private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepositorySpy
@Before
fun setUp() {
@@ -101,9 +100,7 @@
// We're in the middle of a GONE -> LOCKSCREEN transition.
assertThat(keyguardTransitionRepository)
- .startedTransition(
- to = KeyguardState.LOCKSCREEN,
- )
+ .startedTransition(to = KeyguardState.LOCKSCREEN)
}
@Test
@@ -121,15 +118,13 @@
kosmos.fakeBiometricSettingsRepository.setAuthenticationFlags(
AuthenticationFlags(
0,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN,
)
)
runCurrent()
// We're in the middle of a GONE -> LOCKSCREEN transition.
assertThat(keyguardTransitionRepository)
- .startedTransition(
- to = KeyguardState.LOCKSCREEN,
- )
+ .startedTransition(to = KeyguardState.LOCKSCREEN)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
index 4d81317..9c2e631 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
@@ -27,9 +27,11 @@
import com.android.systemui.coroutines.collectValues
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepositorySpy
import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.StatusBarState.KEYGUARD
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -42,10 +44,10 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.reset
-import org.mockito.Mockito.spy
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -53,15 +55,20 @@
class FromLockscreenTransitionInteractorTest : SysuiTestCase() {
private val kosmos =
testKosmos().apply {
- fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository())
+ this.keyguardTransitionRepository = fakeKeyguardTransitionRepositorySpy
}
private val testScope = kosmos.testScope
private val underTest = kosmos.fromLockscreenTransitionInteractor
- private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
+ private lateinit var transitionRepository: FakeKeyguardTransitionRepository
private val shadeRepository = kosmos.fakeShadeRepository
private val keyguardRepository = kosmos.fakeKeyguardRepository
+ @Before
+ fun setup() {
+ transitionRepository = kosmos.fakeKeyguardTransitionRepositorySpy
+ }
+
@Test
fun testSurfaceBehindVisibility() =
testScope.runTest {
@@ -256,4 +263,43 @@
assertThatRepository(transitionRepository)
.startedTransition(from = KeyguardState.LOCKSCREEN, to = KeyguardState.DREAMING)
}
+
+ @Test
+ fun testTransitionsBackToOccluded_ifOccluded_andCanceledSwipe() =
+ testScope.runTest {
+ underTest.start()
+ keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+ keyguardRepository.setKeyguardDismissible(false)
+ keyguardRepository.setKeyguardOccluded(false)
+ runCurrent()
+
+ reset(transitionRepository)
+
+ shadeRepository.setLegacyShadeTracking(true)
+ runCurrent()
+ shadeRepository.setLegacyShadeExpansion(0.5f)
+ runCurrent()
+
+ assertThatRepository(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ )
+ reset(transitionRepository)
+
+ runCurrent()
+
+ shadeRepository.setLegacyShadeExpansion(0.6f)
+ shadeRepository.setLegacyShadeExpansion(0.7f)
+ runCurrent()
+
+ shadeRepository.setLegacyShadeExpansion(1f)
+ runCurrent()
+
+ assertThatRepository(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt
index 7424320..4a90722 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt
@@ -42,9 +42,9 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
import com.android.systemui.communal.shared.model.CommunalScenes
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepositorySpy
import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
import com.android.systemui.kosmos.testScope
@@ -60,7 +60,6 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.reset
-import org.mockito.Mockito.spy
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -68,14 +67,14 @@
class FromOccludedTransitionInteractorTest : SysuiTestCase() {
private val kosmos =
testKosmos().apply {
- this.fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository())
+ this.keyguardTransitionRepository = fakeKeyguardTransitionRepositorySpy
}
private val testScope = kosmos.testScope
private val underTest = kosmos.fromOccludedTransitionInteractor
private val powerInteractor = kosmos.powerInteractor
- private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
+ private val transitionRepository = kosmos.fakeKeyguardTransitionRepositorySpy
@Before
fun setup() {
@@ -88,7 +87,7 @@
transitionRepository.sendTransitionSteps(
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.OCCLUDED,
- testScope
+ testScope,
)
reset(transitionRepository)
}
@@ -102,10 +101,7 @@
runCurrent()
assertThat(transitionRepository)
- .startedTransition(
- from = KeyguardState.OCCLUDED,
- to = KeyguardState.LOCKSCREEN,
- )
+ .startedTransition(from = KeyguardState.OCCLUDED, to = KeyguardState.LOCKSCREEN)
}
@Test
@@ -122,9 +118,6 @@
runCurrent()
assertThat(transitionRepository)
- .startedTransition(
- from = KeyguardState.OCCLUDED,
- to = KeyguardState.GLANCEABLE_HUB,
- )
+ .startedTransition(from = KeyguardState.OCCLUDED, to = KeyguardState.GLANCEABLE_HUB)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
index 14f2d65..a7da230 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
@@ -26,9 +26,9 @@
import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.coroutines.collectValues
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepositorySpy
import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -44,20 +44,19 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.reset
-import org.mockito.Mockito.spy
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class FromPrimaryBouncerTransitionInteractorTest : SysuiTestCase() {
- val kosmos =
+ private val kosmos =
testKosmos().apply {
- this.fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository())
+ this.keyguardTransitionRepository = fakeKeyguardTransitionRepositorySpy
}
val underTest = kosmos.fromPrimaryBouncerTransitionInteractor
val testScope = kosmos.testScope
val selectedUserInteractor = kosmos.selectedUserInteractor
- val transitionRepository = kosmos.fakeKeyguardTransitionRepository
+ val transitionRepository = kosmos.fakeKeyguardTransitionRepositorySpy
val bouncerRepository = kosmos.fakeKeyguardBouncerRepository
@Test
@@ -67,12 +66,7 @@
runCurrent()
// Transition-specific surface visibility should be null ("don't care") initially.
- assertEquals(
- listOf(
- null,
- ),
- values
- )
+ assertEquals(listOf(null), values)
transitionRepository.sendTransitionStep(
TransitionStep(
@@ -86,9 +80,9 @@
assertEquals(
listOf(
- null, // PRIMARY_BOUNCER -> LOCKSCREEN does not have any specific visibility.
+ null // PRIMARY_BOUNCER -> LOCKSCREEN does not have any specific visibility.
),
- values
+ values,
)
transitionRepository.sendTransitionStep(
@@ -117,7 +111,7 @@
null,
false, // Surface is only made visible once the bouncer UI animates out.
),
- values
+ values,
)
transitionRepository.sendTransitionStep(
@@ -137,7 +131,7 @@
false,
true, // Surface should eventually be visible.
),
- values
+ values,
)
}
@@ -150,7 +144,7 @@
transitionRepository.sendTransitionSteps(
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.PRIMARY_BOUNCER,
- testScope
+ testScope,
)
reset(transitionRepository)
@@ -161,7 +155,7 @@
assertThat(transitionRepository)
.startedTransition(
from = KeyguardState.PRIMARY_BOUNCER,
- to = KeyguardState.LOCKSCREEN
+ to = KeyguardState.LOCKSCREEN,
)
}
@@ -177,7 +171,7 @@
transitionRepository.sendTransitionSteps(
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.PRIMARY_BOUNCER,
- testScope
+ testScope,
)
reset(transitionRepository)
@@ -188,7 +182,7 @@
assertThat(transitionRepository)
.startedTransition(
from = KeyguardState.PRIMARY_BOUNCER,
- to = KeyguardState.GLANCEABLE_HUB
+ to = KeyguardState.GLANCEABLE_HUB,
)
}
@@ -201,7 +195,7 @@
transitionRepository.sendTransitionSteps(
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.PRIMARY_BOUNCER,
- testScope
+ testScope,
)
reset(transitionRepository)
@@ -218,7 +212,7 @@
assertThat(transitionRepository)
.startedTransition(
from = KeyguardState.PRIMARY_BOUNCER,
- to = KeyguardState.OCCLUDED
+ to = KeyguardState.OCCLUDED,
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index a617484..8f3d549 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -41,9 +41,9 @@
import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
import com.android.systemui.flags.andSceneContainer
import com.android.systemui.flags.fakeFeatureFlagsClassic
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepositorySpy
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
@@ -76,7 +76,6 @@
import org.mockito.Mock
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.reset
-import org.mockito.Mockito.spy
import org.mockito.MockitoAnnotations
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters
@@ -91,7 +90,7 @@
class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTestCase() {
private val kosmos =
testKosmos().apply {
- fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository())
+ this.keyguardTransitionRepository = fakeKeyguardTransitionRepositorySpy
}
private val testScope = kosmos.testScope
@@ -99,7 +98,7 @@
private val keyguardInteractor by lazy { kosmos.keyguardInteractor }
private val bouncerRepository by lazy { kosmos.fakeKeyguardBouncerRepository }
private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
- private val transitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
+ private val transitionRepository by lazy { kosmos.fakeKeyguardTransitionRepositorySpy }
private lateinit var featureFlags: FakeFeatureFlags
// Used to verify transition requests for test output
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModelTest.kt
new file mode 100644
index 0000000..22677b2
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModelTest.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
+import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DeviceEntryBackgroundViewModelTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val underTest: DeviceEntryBackgroundViewModel by lazy {
+ kosmos.deviceEntryBackgroundViewModel
+ }
+
+ @Test
+ fun lockscreenToDozingTransitionChangesBackgroundViewAlphaToZero() =
+ testScope.runTest {
+ kosmos.fingerprintPropertyRepository.supportsUdfps()
+ val alpha by collectLastValue(underTest.alpha)
+
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+ listOf(dozingToLockscreen(0f, STARTED), dozingToLockscreen(0.1f)),
+ testScope,
+ )
+ runCurrent()
+ assertThat(alpha).isEqualTo(1.0f)
+
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+ listOf(lockscreenToDozing(0f, STARTED)),
+ testScope,
+ )
+ runCurrent()
+
+ assertThat(alpha).isEqualTo(0.0f)
+ }
+
+ private fun lockscreenToDozing(value: Float, state: TransitionState = RUNNING): TransitionStep {
+ return TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.DOZING,
+ value = value,
+ transitionState = state,
+ ownerName = "DeviceEntryBackgroundViewModelTest",
+ )
+ }
+
+ private fun dozingToLockscreen(value: Float, state: TransitionState = RUNNING): TransitionStep {
+ return TransitionStep(
+ from = KeyguardState.DOZING,
+ to = KeyguardState.LOCKSCREEN,
+ value = value,
+ transitionState = state,
+ ownerName = "DeviceEntryBackgroundViewModelTest",
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/IssueRecordingServiceSessionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/IssueRecordingServiceSessionTest.kt
index 3e5dee6..a1edfc1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/IssueRecordingServiceSessionTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/IssueRecordingServiceSessionTest.kt
@@ -53,7 +53,7 @@
private val bgExecutor = kosmos.fakeExecutor
private val userContextProvider: UserContextProvider = kosmos.userTracker
private val dialogTransitionAnimator: DialogTransitionAnimator = kosmos.dialogTransitionAnimator
- private lateinit var traceurMessageSender: TraceurMessageSender
+ private lateinit var traceurConnection: TraceurConnection
private val issueRecordingState =
IssueRecordingState(kosmos.userTracker, kosmos.userFileManager)
@@ -65,13 +65,13 @@
@Before
fun setup() {
- traceurMessageSender = mock<TraceurMessageSender>()
+ traceurConnection = mock<TraceurConnection>()
underTest =
IssueRecordingServiceSession(
bgExecutor,
dialogTransitionAnimator,
panelInteractor,
- traceurMessageSender,
+ traceurConnection,
issueRecordingState,
iActivityManager,
notificationManager,
@@ -85,7 +85,7 @@
bgExecutor.runAllReady()
Truth.assertThat(issueRecordingState.isRecording).isTrue()
- verify(traceurMessageSender).startTracing(any<TraceConfig>())
+ verify(traceurConnection).startTracing(any<TraceConfig>())
}
@Test
@@ -94,12 +94,12 @@
bgExecutor.runAllReady()
Truth.assertThat(issueRecordingState.isRecording).isFalse()
- verify(traceurMessageSender).stopTracing()
+ verify(traceurConnection).stopTracing()
}
@Test
fun cancelsNotification_afterReceivingShareCommand() {
- underTest.share(0, null, mContext)
+ underTest.share(0, null)
bgExecutor.runAllReady()
verify(notificationManager).cancelAsUser(isNull(), anyInt(), any<UserHandle>())
@@ -110,7 +110,7 @@
issueRecordingState.takeBugreport = true
val uri = mock<Uri>()
- underTest.share(0, uri, mContext)
+ underTest.share(0, uri)
bgExecutor.runAllReady()
verify(iActivityManager).requestBugReportWithExtraAttachment(uri)
@@ -121,17 +121,17 @@
issueRecordingState.takeBugreport = false
val uri = mock<Uri>()
- underTest.share(0, uri, mContext)
+ underTest.share(0, uri)
bgExecutor.runAllReady()
- verify(traceurMessageSender).shareTraces(mContext, uri)
+ verify(traceurConnection).shareTraces(uri)
}
@Test
fun closesShade_afterReceivingShareCommand() {
val uri = mock<Uri>()
- underTest.share(0, uri, mContext)
+ underTest.share(0, uri)
bgExecutor.runAllReady()
verify(panelInteractor).collapsePanels()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
index 8d84c3e..9639735 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
@@ -78,7 +78,6 @@
@Mock private lateinit var sysuiState: SysUiState
@Mock private lateinit var systemUIDialogManager: SystemUIDialogManager
@Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
- @Mock private lateinit var traceurMessageSender: TraceurMessageSender
private val systemClock = FakeSystemClock()
private val bgExecutor = FakeExecutor(systemClock)
private val mainExecutor = FakeExecutor(systemClock)
@@ -104,7 +103,7 @@
systemUIDialogManager,
sysuiState,
broadcastDispatcher,
- mDialogTransitionAnimator
+ mDialogTransitionAnimator,
)
)
@@ -120,7 +119,6 @@
mediaProjectionMetricsLogger,
screenCaptureDisabledDialogDelegate,
state,
- traceurMessageSender
) {
latch.countDown()
}
@@ -166,7 +164,7 @@
verify(mediaProjectionMetricsLogger, never())
.notifyProjectionInitiated(
anyInt(),
- eq(SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER)
+ eq(SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER),
)
assertThat(screenRecordSwitch.isChecked).isFalse()
}
@@ -188,7 +186,7 @@
verify(mediaProjectionMetricsLogger)
.notifyProjectionInitiated(
anyInt(),
- eq(SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER)
+ eq(SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER),
)
verify(factory, times(2)).create(any(SystemUIDialog.Delegate::class.java))
}
@@ -208,7 +206,7 @@
verify(mediaProjectionMetricsLogger)
.notifyProjectionInitiated(
anyInt(),
- eq(SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER)
+ eq(SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER),
)
verify(factory, never()).create()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/TraceurConnectionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/TraceurConnectionTest.kt
new file mode 100644
index 0000000..d90cca9
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/TraceurConnectionTest.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recordissue
+
+import android.os.IBinder
+import android.os.Looper
+import android.os.Messenger
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.settings.UserContextProvider
+import com.android.traceur.PresetTraceConfigs
+import java.util.concurrent.CountDownLatch
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class TraceurConnectionTest : SysuiTestCase() {
+
+ @Mock private lateinit var userContextProvider: UserContextProvider
+
+ private lateinit var underTest: TraceurConnection
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ whenever(userContextProvider.userContext).thenReturn(mContext)
+ underTest = TraceurConnection.Provider(userContextProvider, Looper.getMainLooper()).create()
+ }
+
+ @Test
+ fun onBoundRunnables_areRun_whenServiceIsBound() {
+ val latch = CountDownLatch(1)
+ underTest.onBound.add { latch.countDown() }
+
+ underTest.onServiceConnected(
+ InstrumentationRegistry.getInstrumentation().componentName,
+ mock(IBinder::class.java),
+ )
+
+ latch.await()
+ }
+
+ @Test
+ fun startTracing_sendsMsg_toStartTracing() {
+ underTest.binder = mock(Messenger::class.java)
+
+ underTest.startTracing(PresetTraceConfigs.getThermalConfig())
+
+ verify(underTest.binder)!!.send(any())
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/UserAwareConnectionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/UserAwareConnectionTest.kt
new file mode 100644
index 0000000..f671bf4
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/UserAwareConnectionTest.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recordissue
+
+import android.content.Context
+import android.content.Intent
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.settings.UserContextProvider
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class UserAwareConnectionTest : SysuiTestCase() {
+
+ @Mock private lateinit var userContextProvider: UserContextProvider
+ @Mock private lateinit var mockContext: Context
+
+ private lateinit var underTest: UserAwareConnection
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ whenever(userContextProvider.userContext).thenReturn(mockContext)
+ whenever(mockContext.bindService(any(), any(), anyInt())).thenReturn(true)
+ underTest = UserAwareConnection(userContextProvider, Intent())
+ }
+
+ @Test
+ fun doBindService_requestToBindToTheService_viaTheCorrectUserContext() {
+ underTest.doBind()
+
+ verify(userContextProvider).userContext
+ }
+
+ @Test
+ fun doBindService_DoesntRequestToBindToTheService_IfAlreadyRequested() {
+ underTest.doBind()
+ underTest.doBind()
+ underTest.doBind()
+
+ verify(userContextProvider, times(1)).userContext
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
index 4ec0802..b632a8a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
@@ -176,8 +176,8 @@
sceneContainerConfig.sceneKeys
.filter { it != currentScene }
.filter {
- // Moving to the Communal scene is not currently falsing protected.
- it != Scenes.Communal
+ // Moving to the Communal and Dream scene is not currently falsing protected.
+ it != Scenes.Communal && it != Scenes.Dream
}
.forEach { toScene ->
assertWithMessage("Protected scene $toScene not properly protected")
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
index beba162..ea5c29e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
@@ -20,8 +20,6 @@
import static junit.framework.Assert.assertFalse;
-import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow;
-
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
@@ -31,12 +29,16 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow;
+
+import android.platform.test.annotations.EnableFlags;
import android.testing.TestableLooper;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.compose.animation.scene.ObservableTransitionState;
+import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.communal.shared.model.CommunalScenes;
import com.android.systemui.dump.DumpManager;
@@ -67,9 +69,6 @@
import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.time.FakeSystemClock;
-import kotlinx.coroutines.flow.MutableStateFlow;
-import kotlinx.coroutines.test.TestScope;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -79,6 +78,9 @@
import org.mockito.MockitoAnnotations;
import org.mockito.verification.VerificationMode;
+import kotlinx.coroutines.flow.MutableStateFlow;
+import kotlinx.coroutines.test.TestScope;
+
@SmallTest
@RunWith(AndroidJUnit4.class)
@TestableLooper.RunWithLooper
@@ -517,6 +519,32 @@
}
@Test
+ @EnableFlags(Flags.FLAG_CHECK_LOCKSCREEN_GONE_TRANSITION)
+ public void testNotLockscreenInGoneTransition_invalidationCalled() {
+ // GIVEN visual stability is being maintained b/c animation is playing
+ mKosmos.getKeyguardTransitionRepository().sendTransitionStepJava(
+ mTestScope, new TransitionStep(
+ KeyguardState.LOCKSCREEN,
+ KeyguardState.GONE,
+ 1f,
+ TransitionState.RUNNING), /* validateStep = */ false);
+ mTestScope.getTestScheduler().runCurrent();
+ assertFalse(mNotifStabilityManager.isPipelineRunAllowed());
+
+ // WHEN the animation has stopped playing
+ mKosmos.getKeyguardTransitionRepository().sendTransitionStepJava(
+ mTestScope, new TransitionStep(
+ KeyguardState.LOCKSCREEN,
+ KeyguardState.GONE,
+ 1f,
+ TransitionState.FINISHED), /* validateStep = */ false);
+ mTestScope.getTestScheduler().runCurrent();
+
+ // invalidate is called, b/c we were previously suppressing the pipeline from running
+ verifyStabilityManagerWasInvalidated(times(1));
+ }
+
+ @Test
public void testNeverSuppressPipelineRunFromPanelCollapse_noInvalidationCalled() {
// GIVEN animation is playing
setPanelCollapsing(true);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModelTest.kt
deleted file mode 100644
index a310ef4..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModelTest.kt
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-@file:OptIn(ExperimentalCoroutinesApi::class)
-
-package com.android.systemui.statusbar.notification.row.ui.viewmodel
-
-import android.platform.test.annotations.EnableFlags
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.statusbar.notification.row.data.repository.fakeNotificationRowRepository
-import com.android.systemui.statusbar.notification.row.shared.EnRouteContentModel
-import com.android.systemui.statusbar.notification.row.shared.IconModel
-import com.android.systemui.statusbar.notification.row.shared.RichOngoingNotificationFlag
-import com.android.systemui.testKosmos
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.kotlin.mock
-
-@RunWith(AndroidJUnit4::class)
-@SmallTest
-@EnableFlags(RichOngoingNotificationFlag.FLAG_NAME)
-class EnRouteViewModelTest : SysuiTestCase() {
- private val kosmos = testKosmos()
- private val testScope = kosmos.testScope
- private val repository = kosmos.fakeNotificationRowRepository
-
- private var contentModel: EnRouteContentModel?
- get() = repository.richOngoingContentModel.value as? EnRouteContentModel
- set(value) {
- repository.richOngoingContentModel.value = value
- }
-
- private lateinit var underTest: EnRouteViewModel
-
- @Before
- fun setup() {
- underTest = kosmos.getEnRouteViewModel(repository)
- }
-
- @Test
- fun viewModelShowsContent() =
- testScope.runTest {
- val title by collectLastValue(underTest.title)
- val text by collectLastValue(underTest.text)
- contentModel =
- exampleEnRouteContent(
- title = "Example EnRoute Title",
- text = "Example EnRoute Text",
- )
- assertThat(title).isEqualTo("Example EnRoute Title")
- assertThat(text).isEqualTo("Example EnRoute Text")
- }
-
- private fun exampleEnRouteContent(
- icon: IconModel = mock(),
- title: CharSequence = "example text",
- text: CharSequence = "example title",
- ) =
- EnRouteContentModel(
- smallIcon = icon,
- title = title,
- text = text,
- )
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModelTest.kt
deleted file mode 100644
index 61873ad..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModelTest.kt
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-@file:OptIn(ExperimentalCoroutinesApi::class)
-
-package com.android.systemui.statusbar.notification.row.ui.viewmodel
-
-import android.app.Notification
-import android.app.PendingIntent
-import android.platform.test.annotations.EnableFlags
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.statusbar.notification.row.data.repository.fakeNotificationRowRepository
-import com.android.systemui.statusbar.notification.row.shared.IconModel
-import com.android.systemui.statusbar.notification.row.shared.RichOngoingNotificationFlag
-import com.android.systemui.statusbar.notification.row.shared.TimerContentModel
-import com.android.systemui.statusbar.notification.row.shared.TimerContentModel.TimerState.Paused
-import com.android.systemui.testKosmos
-import com.google.common.truth.Truth.assertThat
-import java.time.Duration
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.kotlin.mock
-
-@RunWith(AndroidJUnit4::class)
-@SmallTest
-@EnableFlags(RichOngoingNotificationFlag.FLAG_NAME)
-class TimerViewModelTest : SysuiTestCase() {
- private val kosmos = testKosmos()
- private val testScope = kosmos.testScope
- private val repository = kosmos.fakeNotificationRowRepository
-
- private var contentModel: TimerContentModel?
- get() = repository.richOngoingContentModel.value as? TimerContentModel
- set(value) {
- repository.richOngoingContentModel.value = value
- }
-
- private lateinit var underTest: TimerViewModel
-
- @Before
- fun setup() {
- underTest = kosmos.getTimerViewModel(repository)
- }
-
- @Test
- fun labelShowsTheTimerName() =
- testScope.runTest {
- val label by collectLastValue(underTest.label)
- contentModel = pausedTimer(name = "Example Timer Name")
- assertThat(label).isEqualTo("Example Timer Name")
- }
-
- @Test
- fun pausedTimeRemainingFormatsWell() =
- testScope.runTest {
- val label by collectLastValue(underTest.pausedTime)
- contentModel = pausedTimer(timeRemaining = Duration.ofMinutes(3))
- assertThat(label).isEqualTo("3:00")
- contentModel = pausedTimer(timeRemaining = Duration.ofSeconds(119))
- assertThat(label).isEqualTo("1:59")
- contentModel = pausedTimer(timeRemaining = Duration.ofSeconds(121))
- assertThat(label).isEqualTo("2:01")
- contentModel = pausedTimer(timeRemaining = Duration.ofHours(1))
- assertThat(label).isEqualTo("1:00:00")
- contentModel = pausedTimer(timeRemaining = Duration.ofHours(24))
- assertThat(label).isEqualTo("24:00:00")
- }
-
- private fun pausedTimer(
- icon: IconModel = mock(),
- name: String = "example",
- timeRemaining: Duration = Duration.ofMinutes(3),
- resumeIntent: PendingIntent? = null,
- addMinuteAction: Notification.Action? = null,
- resetAction: Notification.Action? = null
- ) =
- TimerContentModel(
- icon = icon,
- name = name,
- state =
- Paused(
- timeRemaining = timeRemaining,
- resumeIntent = resumeIntent,
- addMinuteAction = addMinuteAction,
- resetAction = resetAction,
- )
- )
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
index f40bfbd..8d678ef 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
@@ -506,7 +506,7 @@
@EnableSceneContainer
fun pinnedHeadsUpRows_filtersForPinnedItems() =
testScope.runTest {
- val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRows)
+ val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRowKeys)
// WHEN there are no pinned rows
val rows =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorTest.kt
new file mode 100644
index 0000000..7ce421a
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorTest.kt
@@ -0,0 +1,150 @@
+/*
+ * 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.volume.dialog.domain.interactor
+
+import android.app.ActivityManager
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.fakeVolumeDialogController
+import com.android.systemui.testKosmos
+import com.android.systemui.volume.Events
+import com.android.systemui.volume.dialog.domain.model.VolumeDialogVisibilityModel
+import com.google.common.truth.Truth.assertThat
+import kotlin.time.Duration.Companion.days
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private val dialogTimeoutDuration = 3.seconds
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper()
+class VolumeDialogVisibilityInteractorTest : SysuiTestCase() {
+
+ private val kosmos: Kosmos = testKosmos()
+
+ private lateinit var underTest: VolumeDialogVisibilityInteractor
+
+ @Before
+ fun setUp() {
+ underTest = kosmos.volumeDialogVisibilityInteractor
+ }
+
+ @Test
+ fun testShowRequest_visible() =
+ with(kosmos) {
+ testScope.runTest {
+ runCurrent()
+ val visibilityModel by collectLastValue(underTest.dialogVisibility)
+ fakeVolumeDialogController.onShowRequested(
+ Events.SHOW_REASON_VOLUME_CHANGED,
+ false,
+ ActivityManager.LOCK_TASK_MODE_LOCKED,
+ )
+ runCurrent()
+
+ assertThat(visibilityModel!!)
+ .isEqualTo(
+ VolumeDialogVisibilityModel.Visible(
+ Events.SHOW_REASON_VOLUME_CHANGED,
+ false,
+ ActivityManager.LOCK_TASK_MODE_LOCKED,
+ )
+ )
+ }
+ }
+
+ @Test
+ fun testDismissRequest_dismissed() =
+ with(kosmos) {
+ testScope.runTest {
+ runCurrent()
+ val visibilityModel by collectLastValue(underTest.dialogVisibility)
+ fakeVolumeDialogController.onShowRequested(
+ Events.SHOW_REASON_VOLUME_CHANGED,
+ false,
+ ActivityManager.LOCK_TASK_MODE_LOCKED,
+ )
+ runCurrent()
+
+ fakeVolumeDialogController.onDismissRequested(Events.DISMISS_REASON_SCREEN_OFF)
+
+ assertThat(visibilityModel!!)
+ .isEqualTo(
+ VolumeDialogVisibilityModel.Dismissed(Events.DISMISS_REASON_SCREEN_OFF)
+ )
+ }
+ }
+
+ @Test
+ fun testTimeout_dismissed() =
+ with(kosmos) {
+ testScope.runTest {
+ runCurrent()
+ underTest.resetDismissTimeout()
+ val visibilityModel by collectLastValue(underTest.dialogVisibility)
+ fakeVolumeDialogController.onShowRequested(
+ Events.SHOW_REASON_VOLUME_CHANGED,
+ false,
+ ActivityManager.LOCK_TASK_MODE_LOCKED,
+ )
+ runCurrent()
+
+ advanceTimeBy(1.days)
+
+ assertThat(visibilityModel!!)
+ .isEqualTo(VolumeDialogVisibilityModel.Dismissed(Events.DISMISS_REASON_TIMEOUT))
+ }
+ }
+
+ @Test
+ fun testResetTimeoutInterruptsEvents() =
+ with(kosmos) {
+ testScope.runTest {
+ runCurrent()
+ underTest.resetDismissTimeout()
+ val visibilityModel by collectLastValue(underTest.dialogVisibility)
+ fakeVolumeDialogController.onShowRequested(
+ Events.SHOW_REASON_VOLUME_CHANGED,
+ false,
+ ActivityManager.LOCK_TASK_MODE_LOCKED,
+ )
+ runCurrent()
+
+ advanceTimeBy(dialogTimeoutDuration / 2)
+ underTest.resetDismissTimeout()
+ advanceTimeBy(dialogTimeoutDuration / 2)
+ underTest.resetDismissTimeout()
+ advanceTimeBy(dialogTimeoutDuration / 2)
+
+ assertThat(visibilityModel)
+ .isInstanceOf(VolumeDialogVisibilityModel.Visible::class.java)
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/WMShellTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/WMShellTest.kt
index e1be6b0..d2688a8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/WMShellTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/WMShellTest.kt
@@ -53,7 +53,7 @@
import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.util.kotlin.JavaAdapter
import com.android.wm.shell.desktopmode.DesktopMode
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener
+import com.android.wm.shell.desktopmode.DesktopRepository.VisibleTasksListener
import com.android.wm.shell.onehanded.OneHanded
import com.android.wm.shell.onehanded.OneHandedEventCallback
import com.android.wm.shell.onehanded.OneHandedTransitionCallback
diff --git a/packages/SystemUI/res-keyguard/layout-land/keyguard_pattern_view.xml b/packages/SystemUI/res-keyguard/layout-land/keyguard_pattern_view.xml
new file mode 100644
index 0000000..8a77d88
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout-land/keyguard_pattern_view.xml
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2024, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License")
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<!-- This is the screen that shows the 9 circle unlock widget and instructs
+ the user how to unlock their device, or make an emergency call. This
+ is the landscape layout. -->
+<com.android.keyguard.KeyguardPatternView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/keyguard_pattern_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center_horizontal|bottom"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:orientation="horizontal">
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="2"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:layoutDirection="ltr"
+ android:orientation="vertical">
+
+ <include layout="@layout/keyguard_bouncer_message_area"/>
+
+ <com.android.systemui.bouncer.ui.BouncerMessageView
+ android:id="@+id/bouncer_message_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ androidprv:layout_constraintBottom_toTopOf="@+id/lockPatternView"
+ androidprv:layout_constraintTop_toTopOf="parent"
+ androidprv:layout_constraintVertical_chainStyle="packed" />
+
+ <include
+ android:id="@+id/keyguard_selector_fade_container"
+ layout="@layout/keyguard_eca"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom|center_horizontal"
+ android:layout_marginBottom="@dimen/keyguard_eca_bottom_margin"
+ android:layout_marginTop="@dimen/keyguard_eca_top_margin"
+ android:gravity="center_horizontal"
+ android:orientation="vertical"
+ androidprv:layout_constraintBottom_toBottomOf="parent" />
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/pattern_container"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="3"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:layoutDirection="ltr"
+ android:orientation="vertical">
+
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/pattern_top_guideline"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ androidprv:layout_constraintGuide_percent="0" />
+
+ <com.android.internal.widget.LockPatternView
+ android:id="@+id/lockPatternView"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:orientation="horizontal"
+ androidprv:layout_constraintDimensionRatio="1.0"
+ androidprv:layout_constraintVertical_bias="1.0"
+ androidprv:layout_constraintLeft_toLeftOf="parent"
+ androidprv:layout_constraintRight_toRightOf="parent"
+ androidprv:layout_constraintBottom_toBottomOf="parent"
+ androidprv:layout_constraintTop_toBottomOf="@id/pattern_top_guideline"/>
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
+</com.android.keyguard.KeyguardPatternView>
diff --git a/packages/SystemUI/res-keyguard/layout-land/keyguard_sim_pin_view.xml b/packages/SystemUI/res-keyguard/layout-land/keyguard_sim_pin_view.xml
new file mode 100644
index 0000000..4b8b63f
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout-land/keyguard_sim_pin_view.xml
@@ -0,0 +1,221 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2024, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License")
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<!-- This is the SIM PIN view that allows the user to enter a SIM PIN to unlock the device. -->
+<com.android.keyguard.KeyguardSimPinView xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/res-auto"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/keyguard_sim_pin_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center_horizontal|bottom"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:orientation="horizontal">
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="2"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:layoutDirection="ltr"
+ android:orientation="vertical">
+
+ <include layout="@layout/keyguard_bouncer_message_area"/>
+
+ <ImageView
+ android:id="@+id/keyguard_sim"
+ android:layout_width="40dp"
+ android:layout_height="40dp"
+ android:layout_marginBottom="3dp"
+ android:src="@drawable/ic_lockscreen_sim"
+ androidprv:layout_constraintBottom_toTopOf="@+id/pin_entry_area"
+ androidprv:layout_constraintLeft_toLeftOf="parent"
+ androidprv:layout_constraintRight_toRightOf="parent"
+ app:tint="@color/background_protected"/>
+
+ <include
+ android:id="@+id/keyguard_esim_area"
+ layout="@layout/keyguard_esim_area"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ androidprv:layout_constraintLeft_toLeftOf="parent"
+ androidprv:layout_constraintRight_toRightOf="parent"
+ androidprv:layout_constraintBottom_toTopOf="@+id/pin_entry_area"/>
+
+ <com.android.keyguard.AlphaOptimizedRelativeLayout
+ android:id="@+id/pin_entry_area"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingBottom="@dimen/num_pad_entry_row_margin_bottom"
+ androidprv:layout_constraintBottom_toTopOf="@+id/keyguard_selector_fade_container">
+
+ <com.android.keyguard.PasswordTextView
+ android:id="@+id/simPinEntry"
+ style="@style/Widget.TextView.Password"
+ android:layout_width="@dimen/keyguard_security_width"
+ android:layout_height="@dimen/keyguard_password_height"
+ android:layout_centerHorizontal="true"
+ android:layout_marginRight="72dp"
+ android:contentDescription="@string/keyguard_accessibility_sim_pin_area"
+ androidprv:scaledTextSize="@integer/scaled_password_text_size" />
+ </com.android.keyguard.AlphaOptimizedRelativeLayout>
+
+ <include
+ android:id="@+id/keyguard_selector_fade_container"
+ layout="@layout/keyguard_eca"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom|center_horizontal"
+ android:layout_marginBottom="@dimen/keyguard_eca_bottom_margin"
+ android:layout_marginTop="@dimen/keyguard_eca_top_margin"
+ android:gravity="center_horizontal"
+ android:orientation="vertical"
+ androidprv:layout_constraintBottom_toBottomOf="parent"/>
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/sim_pin_container"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="3"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:layoutDirection="ltr"
+ android:orientation="vertical">
+
+ <androidx.constraintlayout.helper.widget.Flow
+ android:id="@+id/flow1"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:orientation="horizontal"
+ androidprv:constraint_referenced_ids="key1,key2,key3,key4,key5,key6,key7,key8,key9,delete_button,key0,key_enter"
+ androidprv:flow_horizontalGap="@dimen/num_pad_key_margin_end"
+ androidprv:flow_horizontalStyle="packed"
+ androidprv:flow_maxElementsWrap="3"
+ androidprv:flow_verticalBias="0.5"
+ androidprv:flow_verticalGap="@dimen/num_pad_entry_row_margin_bottom"
+ androidprv:flow_verticalStyle="packed"
+ androidprv:flow_wrapMode="aligned"
+ androidprv:layout_constraintLeft_toLeftOf="parent"
+ androidprv:layout_constraintRight_toRightOf="parent"
+ androidprv:layout_constraintTop_toTopOf="parent"
+ androidprv:layout_constraintBottom_toBottomOf="parent"/>
+
+ <com.android.keyguard.NumPadButton
+ android:id="@+id/delete_button"
+ style="@style/NumPadKey.Delete"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key0"
+ android:contentDescription="@string/keyboardview_keycode_delete" />
+
+ <com.android.keyguard.NumPadButton
+ android:id="@+id/key_enter"
+ style="@style/NumPadKey.Enter"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:contentDescription="@string/keyboardview_keycode_enter" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key1"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key2"
+ androidprv:digit="1"
+ androidprv:textView="@+id/simPinEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key2"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key3"
+ androidprv:digit="2"
+ androidprv:textView="@+id/simPinEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key3"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key4"
+ androidprv:digit="3"
+ androidprv:textView="@+id/simPinEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key4"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key5"
+ androidprv:digit="4"
+ androidprv:textView="@+id/simPinEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key5"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key6"
+ androidprv:digit="5"
+ androidprv:textView="@+id/simPinEntry" />
+
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key6"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key7"
+ androidprv:digit="6"
+ androidprv:textView="@+id/simPinEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key7"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key8"
+ androidprv:digit="7"
+ androidprv:textView="@+id/simPinEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key8"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key9"
+ androidprv:digit="8"
+ androidprv:textView="@+id/simPinEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key9"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/delete_button"
+ androidprv:digit="9"
+ androidprv:textView="@+id/simPinEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key0"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key_enter"
+ androidprv:digit="0"
+ androidprv:textView="@+id/simPinEntry" />
+ </androidx.constraintlayout.widget.ConstraintLayout>
+</com.android.keyguard.KeyguardSimPinView>
diff --git a/packages/SystemUI/res-keyguard/layout-land/keyguard_sim_puk_view.xml b/packages/SystemUI/res-keyguard/layout-land/keyguard_sim_puk_view.xml
new file mode 100644
index 0000000..9012856
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout-land/keyguard_sim_puk_view.xml
@@ -0,0 +1,223 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2024, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License")
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<!-- This is the SIM PUK view that allows the user to recover their device by entering the
+ carrier-provided PUK code and entering a new SIM PIN for it. -->
+<com.android.keyguard.KeyguardSimPukView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/res-auto"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/keyguard_sim_puk_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center_horizontal|bottom"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:orientation="horizontal">
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="2"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:layoutDirection="ltr"
+ android:orientation="vertical">
+
+ <include layout="@layout/keyguard_bouncer_message_area"/>
+
+ <ImageView
+ android:id="@+id/keyguard_sim"
+ android:layout_width="40dp"
+ android:layout_height="40dp"
+ android:layout_marginBottom="3dp"
+ android:src="@drawable/ic_lockscreen_sim"
+ androidprv:layout_constraintBottom_toTopOf="@+id/pin_entry_area"
+ androidprv:layout_constraintLeft_toLeftOf="parent"
+ androidprv:layout_constraintRight_toRightOf="parent"
+ app:tint="@color/background_protected"/>
+
+ <include
+ android:id="@+id/keyguard_esim_area"
+ layout="@layout/keyguard_esim_area"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ androidprv:layout_constraintLeft_toLeftOf="parent"
+ androidprv:layout_constraintRight_toRightOf="parent"
+ androidprv:layout_constraintBottom_toTopOf="@+id/pin_entry_area"/>
+
+ <com.android.keyguard.AlphaOptimizedRelativeLayout
+ android:id="@+id/pin_entry_area"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingBottom="@dimen/num_pad_entry_row_margin_bottom"
+ androidprv:layout_constraintBottom_toTopOf="@+id/keyguard_selector_fade_container">
+
+ <com.android.keyguard.PasswordTextView
+ android:id="@+id/pukEntry"
+ style="@style/Widget.TextView.Password"
+ android:layout_width="@dimen/keyguard_security_width"
+ android:layout_height="@dimen/keyguard_password_height"
+ android:layout_centerHorizontal="true"
+ android:layout_marginRight="72dp"
+ android:contentDescription="@string/keyguard_accessibility_sim_pin_area"
+ androidprv:scaledTextSize="@integer/scaled_password_text_size" />
+ </com.android.keyguard.AlphaOptimizedRelativeLayout>
+
+ <include
+ android:id="@+id/keyguard_selector_fade_container"
+ layout="@layout/keyguard_eca"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom|center_horizontal"
+ android:layout_marginBottom="@dimen/keyguard_eca_bottom_margin"
+ android:layout_marginTop="@dimen/keyguard_eca_top_margin"
+ android:gravity="center_horizontal"
+ android:orientation="vertical"
+ androidprv:layout_constraintBottom_toBottomOf="parent"/>
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/sim_puk_container"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="3"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:layoutDirection="ltr"
+ android:orientation="vertical">
+
+ <androidx.constraintlayout.helper.widget.Flow
+ android:id="@+id/flow1"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:orientation="horizontal"
+ androidprv:constraint_referenced_ids="key1,key2,key3,key4,key5,key6,key7,key8,key9,delete_button,key0,key_enter"
+ androidprv:flow_horizontalGap="@dimen/num_pad_key_margin_end"
+ androidprv:flow_horizontalStyle="packed"
+ androidprv:flow_maxElementsWrap="3"
+ androidprv:flow_verticalBias="0.5"
+ androidprv:flow_verticalGap="@dimen/num_pad_entry_row_margin_bottom"
+ androidprv:flow_verticalStyle="packed"
+ androidprv:flow_wrapMode="aligned"
+ androidprv:layout_constraintLeft_toLeftOf="parent"
+ androidprv:layout_constraintRight_toRightOf="parent"
+ androidprv:layout_constraintTop_toTopOf="parent"
+ androidprv:layout_constraintBottom_toBottomOf="parent"/>
+
+ <com.android.keyguard.NumPadButton
+ android:id="@+id/delete_button"
+ style="@style/NumPadKey.Delete"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key0"
+ android:contentDescription="@string/keyboardview_keycode_delete" />
+
+ <com.android.keyguard.NumPadButton
+ android:id="@+id/key_enter"
+ style="@style/NumPadKey.Enter"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:contentDescription="@string/keyboardview_keycode_enter" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key1"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key2"
+ androidprv:digit="1"
+ androidprv:textView="@+id/pukEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key2"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key3"
+ androidprv:digit="2"
+ androidprv:textView="@+id/pukEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key3"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key4"
+ androidprv:digit="3"
+ androidprv:textView="@+id/pukEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key4"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key5"
+ androidprv:digit="4"
+ androidprv:textView="@+id/pukEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key5"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key6"
+ androidprv:digit="5"
+ androidprv:textView="@+id/pukEntry" />
+
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key6"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key7"
+ androidprv:digit="6"
+ androidprv:textView="@+id/pukEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key7"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key8"
+ androidprv:digit="7"
+ androidprv:textView="@+id/pukEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key8"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key9"
+ androidprv:digit="8"
+ androidprv:textView="@+id/pukEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key9"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/delete_button"
+ androidprv:digit="9"
+ androidprv:textView="@+id/pukEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key0"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key_enter"
+ androidprv:digit="0"
+ androidprv:textView="@+id/pukEntry" />
+ </androidx.constraintlayout.widget.ConstraintLayout>
+</com.android.keyguard.KeyguardSimPukView>
diff --git a/packages/SystemUI/res/drawable/touchpad_tutorial_home_icon.xml b/packages/SystemUI/res/drawable/touchpad_tutorial_home_icon.xml
new file mode 100644
index 0000000..9f3d075
--- /dev/null
+++ b/packages/SystemUI/res/drawable/touchpad_tutorial_home_icon.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:tint="?attr/colorControlNormal"
+ android:viewportHeight="960"
+ android:viewportWidth="960">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M240,760L360,760L360,520L600,520L600,760L720,760L720,400L480,220L240,400L240,760ZM160,840L160,360L480,120L800,360L800,840L520,840L520,600L440,600L440,840L160,840ZM480,490L480,490L480,490L480,490L480,490L480,490L480,490L480,490L480,490Z" />
+</vector>
diff --git a/packages/SystemUI/res/drawable/touchpad_tutorial_recents_icon.xml b/packages/SystemUI/res/drawable/touchpad_tutorial_recents_icon.xml
new file mode 100644
index 0000000..113908a
--- /dev/null
+++ b/packages/SystemUI/res/drawable/touchpad_tutorial_recents_icon.xml
@@ -0,0 +1,27 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:autoMirrored="true"
+ android:tint="?attr/colorControlNormal"
+ android:viewportHeight="960"
+ android:viewportWidth="960">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M120,800Q87,800 63.5,776.5Q40,753 40,720L40,240Q40,207 63.5,183.5Q87,160 120,160L200,160Q233,160 256.5,183.5Q280,207 280,240L280,720Q280,753 256.5,776.5Q233,800 200,800L120,800ZM120,721L200,721Q200,721 200,721Q200,721 200,721L200,239Q200,239 200,239Q200,239 200,239L120,239Q120,239 120,239Q120,239 120,239L120,721Q120,721 120,721Q120,721 120,721ZM440,800Q407,800 383.5,776.5Q360,753 360,720L360,240Q360,207 383.5,183.5Q407,160 440,160L840,160Q873,160 896.5,183.5Q920,207 920,240L920,720Q920,753 896.5,776.5Q873,800 840,800L440,800ZM440,721L840,721Q840,721 840,721Q840,721 840,721L840,239Q840,239 840,239Q840,239 840,239L440,239Q440,239 440,239Q440,239 440,239L440,721Q440,721 440,721Q440,721 440,721ZM200,721Q200,721 200,721Q200,721 200,721L200,239Q200,239 200,239Q200,239 200,239L200,239Q200,239 200,239Q200,239 200,239L200,721Q200,721 200,721Q200,721 200,721ZM440,721Q440,721 440,721Q440,721 440,721L440,239Q440,239 440,239Q440,239 440,239L440,239Q440,239 440,239Q440,239 440,239L440,721Q440,721 440,721Q440,721 440,721Z" />
+</vector>
diff --git a/packages/SystemUI/res/layout/notification_template_en_route_contracted.xml b/packages/SystemUI/res/layout/notification_template_en_route_contracted.xml
deleted file mode 100644
index e7a40d1..0000000
--- a/packages/SystemUI/res/layout/notification_template_en_route_contracted.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!--
- ~ Copyright (C) 2024 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-
-<com.android.systemui.statusbar.notification.row.ui.view.EnRouteView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@*android:id/status_bar_latest_event_content"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:minHeight="@*android:dimen/notification_headerless_min_height"
- android:tag="enroute"
- >
-
- <include layout="@*android:layout/notification_template_material_base" />
-
-</com.android.systemui.statusbar.notification.row.ui.view.EnRouteView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/notification_template_en_route_expanded.xml b/packages/SystemUI/res/layout/notification_template_en_route_expanded.xml
deleted file mode 100644
index ca6d66a..0000000
--- a/packages/SystemUI/res/layout/notification_template_en_route_expanded.xml
+++ /dev/null
@@ -1,86 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!--
- ~ Copyright (C) 2014 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-<com.android.systemui.statusbar.notification.row.ui.view.EnRouteView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@*android:id/status_bar_latest_event_content"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:clipChildren="false"
- android:tag="big"
- >
-
- <LinearLayout
- android:id="@*android:id/notification_action_list_margin_target"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginBottom="@*android:dimen/notification_content_margin"
- android:orientation="vertical"
- >
-
- <FrameLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:layout_gravity="top"
- >
-
- <include layout="@*android:layout/notification_template_header" />
-
- <LinearLayout
- android:id="@*android:id/notification_main_column"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginStart="@*android:dimen/notification_content_margin_start"
- android:layout_marginEnd="@*android:dimen/notification_content_margin_end"
- android:layout_marginTop="@*android:dimen/notification_content_margin_top"
- android:orientation="vertical"
- >
-
- <include layout="@*android:layout/notification_template_part_line1" />
-
- <include layout="@*android:layout/notification_template_text_multiline" />
-
- <include
- android:layout_width="match_parent"
- android:layout_height="@*android:dimen/notification_progress_bar_height"
- android:layout_marginTop="@*android:dimen/notification_progress_margin_top"
- layout="@*android:layout/notification_template_progress"
- />
- </LinearLayout>
-
- <include layout="@*android:layout/notification_template_right_icon" />
- </FrameLayout>
-
- <ViewStub
- android:layout="@*android:layout/notification_material_reply_text"
- android:id="@*android:id/notification_material_reply_container"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- />
-
- <include
- layout="@*android:layout/notification_template_smart_reply_container"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginStart="@*android:dimen/notification_content_margin_start"
- android:layout_marginEnd="@*android:dimen/notification_content_margin_end"
- android:layout_marginTop="@*android:dimen/notification_content_margin"
- />
-
- <include layout="@*android:layout/notification_material_action_list" />
- </LinearLayout>
-</com.android.systemui.statusbar.notification.row.ui.view.EnRouteView>
diff --git a/packages/SystemUI/res/layout/rich_ongoing_timer_notification.xml b/packages/SystemUI/res/layout/rich_ongoing_timer_notification.xml
deleted file mode 100644
index 3a679e3..0000000
--- a/packages/SystemUI/res/layout/rich_ongoing_timer_notification.xml
+++ /dev/null
@@ -1,122 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2024 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-<com.android.systemui.statusbar.notification.row.ui.view.TimerView
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- xmlns:app="http://schemas.android.com/apk/res-auto">
-
- <androidx.constraintlayout.widget.Guideline
- android:id="@+id/topBaseline"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- app:layout_constraintGuide_begin="22sp"
- />
-
- <ImageView
- android:id="@+id/icon"
- android:layout_width="24dp"
- android:layout_height="24dp"
- app:tint="@android:color/white"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toStartOf="@id/label"
- android:baseline="18dp"
- app:layout_constraintBaseline_toTopOf="@id/topBaseline"
- />
- <TextView
- android:id="@+id/label"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- app:layout_constraintStart_toEndOf="@id/icon"
- app:layout_constraintEnd_toStartOf="@id/chronoRemaining"
- android:singleLine="true"
- tools:text="15s Timer"
- app:layout_constraintBaseline_toTopOf="@id/topBaseline"
- android:paddingEnd="4dp"
- />
- <Chronometer
- android:id="@+id/chronoRemaining"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:singleLine="true"
- android:textSize="20sp"
- android:gravity="end"
- tools:text="0:12"
- app:layout_constraintBaseline_toTopOf="@id/topBaseline"
- app:layout_constraintEnd_toStartOf="@id/pausedTimeRemaining"
- app:layout_constraintStart_toEndOf="@id/label"
- android:countDown="true"
- android:paddingEnd="4dp"
- />
- <TextView
- android:id="@+id/pausedTimeRemaining"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:singleLine="true"
- android:textSize="20sp"
- android:gravity="end"
- tools:text="0:12"
- app:layout_constraintBaseline_toTopOf="@id/topBaseline"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintStart_toEndOf="@id/chronoRemaining"
- android:paddingEnd="4dp"
- />
-
- <androidx.constraintlayout.widget.Barrier
- android:id="@+id/bottomOfTop"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- app:barrierDirection="bottom"
- app:constraint_referenced_ids="icon,label,chronoRemaining,pausedTimeRemaining"
- />
-
- <com.android.systemui.statusbar.notification.row.ui.view.TimerButtonView
- style="@*android:style/NotificationEmphasizedAction"
- android:id="@+id/mainButton"
- android:layout_width="124dp"
- android:layout_height="wrap_content"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toStartOf="@id/altButton"
- app:layout_constraintTop_toBottomOf="@id/bottomOfTop"
- app:layout_constraintHorizontal_chainStyle="spread"
- android:paddingEnd="4dp"
- />
-
- <com.android.systemui.statusbar.notification.row.ui.view.TimerButtonView
- style="@*android:style/NotificationEmphasizedAction"
- android:id="@+id/altButton"
- android:layout_width="124dp"
- android:layout_height="wrap_content"
- app:layout_constraintTop_toBottomOf="@id/bottomOfTop"
- app:layout_constraintStart_toEndOf="@id/mainButton"
- app:layout_constraintEnd_toEndOf="@id/resetButton"
- android:paddingEnd="4dp"
- />
-
- <com.android.systemui.statusbar.notification.row.ui.view.TimerButtonView
- style="@*android:style/NotificationEmphasizedAction"
- android:id="@+id/resetButton"
- android:layout_width="124dp"
- android:layout_height="wrap_content"
- app:layout_constraintTop_toBottomOf="@id/bottomOfTop"
- app:layout_constraintStart_toEndOf="@id/altButton"
- app:layout_constraintEnd_toEndOf="parent"
- android:paddingEnd="4dp"
- />
-</com.android.systemui.statusbar.notification.row.ui.view.TimerView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 2ddaa56..96a85d7 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3752,9 +3752,9 @@
<!-- TOUCHPAD TUTORIAL-->
<!-- Label for button opening tutorial for back gesture on touchpad [CHAR LIMIT=NONE] -->
- <string name="touchpad_tutorial_back_gesture_button">Back gesture</string>
+ <string name="touchpad_tutorial_back_gesture_button">Go back</string>
<!-- Label for button opening tutorial for back gesture on touchpad [CHAR LIMIT=NONE] -->
- <string name="touchpad_tutorial_home_gesture_button">Home gesture</string>
+ <string name="touchpad_tutorial_home_gesture_button">Go home</string>
<!-- Label for button opening tutorial for "view recent apps" gesture on touchpad [CHAR LIMIT=NONE] -->
<string name="touchpad_tutorial_recent_apps_gesture_button">View recent apps</string>
<!-- Label for button finishing touchpad tutorial [CHAR LIMIT=NONE] -->
@@ -3763,26 +3763,25 @@
<!-- Touchpad back gesture action name in tutorial [CHAR LIMIT=NONE] -->
<string name="touchpad_back_gesture_action_title">Go back</string>
<!-- Touchpad back gesture guidance in gestures tutorial [CHAR LIMIT=NONE] -->
- <string name="touchpad_back_gesture_guidance">To go back, swipe left or right using three fingers anywhere on the touchpad.\n\nYou can also use the keyboard shortcut
-Action + ESC for this.</string>
+ <string name="touchpad_back_gesture_guidance">Swipe left or right using three fingers on your touchpad</string>
<!-- Screen title after back gesture was done successfully [CHAR LIMIT=NONE] -->
- <string name="touchpad_back_gesture_success_title">Great job!</string>
+ <string name="touchpad_back_gesture_success_title">Nice!</string>
<!-- Text shown to the user after they complete back gesture tutorial [CHAR LIMIT=NONE] -->
<string name="touchpad_back_gesture_success_body">You completed the go back gesture.</string>
<!-- HOME GESTURE -->
<!-- Touchpad home gesture action name in tutorial [CHAR LIMIT=NONE] -->
<string name="touchpad_home_gesture_action_title">Go home</string>
<!-- Touchpad home gesture guidance in gestures tutorial [CHAR LIMIT=NONE] -->
- <string name="touchpad_home_gesture_guidance">To go to your home screen at any time, swipe up with three fingers from the bottom of your screen.</string>
+ <string name="touchpad_home_gesture_guidance">Swipe up with three fingers on your touchpad</string>
<!-- Screen title after home gesture was done successfully [CHAR LIMIT=NONE] -->
- <string name="touchpad_home_gesture_success_title">Nice!</string>
+ <string name="touchpad_home_gesture_success_title">Great job!</string>
<!-- Text shown to the user after they complete home gesture tutorial [CHAR LIMIT=NONE] -->
- <string name="touchpad_home_gesture_success_body">You completed the go home gesture.</string>
+ <string name="touchpad_home_gesture_success_body">You completed the go home gesture</string>
<!-- RECENT APPS GESTURE -->
<!-- Touchpad recent apps gesture action name in tutorial [CHAR LIMIT=NONE] -->
<string name="touchpad_recent_apps_gesture_action_title">View recent apps</string>
<!-- Touchpad recent apps gesture guidance in gestures tutorial [CHAR LIMIT=NONE] -->
- <string name="touchpad_recent_apps_gesture_guidance">Swipe up and hold using three fingers on your touchpad.</string>
+ <string name="touchpad_recent_apps_gesture_guidance">Swipe up and hold using three fingers on your touchpad</string>
<!-- Screen title after recent apps gesture was done successfully [CHAR LIMIT=NONE] -->
<string name="touchpad_recent_apps_gesture_success_title">Great job!</string>
<!-- Text shown to the user after they complete recent apps gesture tutorial [CHAR LIMIT=NONE] -->
@@ -3790,13 +3789,13 @@
<!-- KEYBOARD TUTORIAL-->
<!-- Action key tutorial title [CHAR LIMIT=NONE] -->
- <string name="tutorial_action_key_title">Action key</string>
+ <string name="tutorial_action_key_title">View all apps</string>
<!-- Action key tutorial guidance[CHAR LIMIT=NONE] -->
- <string name="tutorial_action_key_guidance">To access your apps, press the action key on your keyboard.</string>
+ <string name="tutorial_action_key_guidance">Press the action key on your keyboard</string>
<!-- Screen title after action key pressed successfully [CHAR LIMIT=NONE] -->
- <string name="tutorial_action_key_success_title">Congratulations!</string>
+ <string name="tutorial_action_key_success_title">Well done!</string>
<!-- Text shown to the user after they complete action key tutorial [CHAR LIMIT=NONE] -->
- <string name="tutorial_action_key_success_body">You completed the action key gesture.\n\nAction + / shows all the shortcuts you have available.</string>
+ <string name="tutorial_action_key_success_body">You completed the view all apps gesture</string>
<!-- Content description for keyboard backlight brightness dialog [CHAR LIMIT=NONE] -->
<string name="keyboard_backlight_dialog_title">Keyboard backlight</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 1c09f84..94b0b5f 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -540,6 +540,7 @@
<!-- Overridden by values-television/styles.xml with tv-specific settings -->
<style name="volume_dialog_theme" parent="Theme.SystemUI">
<item name="android:windowIsFloating">true</item>
+ <item name="android:showWhenLocked">true</item>
</style>
<style name="Theme.SystemUI.DayNightDialog" parent="@android:style/Theme.DeviceDefault.Light.Dialog"/>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index f05cbf4..2d27f1c0 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -1067,6 +1067,8 @@
@Override
public void onDestroy() {
+ mUserSwitcherController.removeUserSwitchCallback(mUserSwitchCallback);
+
ConstraintSet constraintSet = new ConstraintSet();
constraintSet.clone(mView);
constraintSet.clear(mUserSwitcherViewGroup.getId());
@@ -1075,6 +1077,8 @@
mView.removeView(mUserSwitcherViewGroup);
mView.removeView(mUserSwitcher);
+ mUserSwitcher = null;
+ mUserSwitcherViewGroup = null;
}
private void findLargeUserIcon(int userId, Consumer<Drawable> consumer) {
@@ -1102,6 +1106,10 @@
return;
}
+ if (mUserSwitcherViewGroup == null) {
+ return;
+ }
+
mUserSwitcherViewGroup.setAlpha(0f);
ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
int yTrans = mView.getResources().getDimensionPixelSize(R.dimen.pin_view_trans_y_entry);
@@ -1110,14 +1118,18 @@
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- mUserSwitcherViewGroup.setAlpha(1f);
- mUserSwitcherViewGroup.setTranslationY(0f);
+ if (mUserSwitcherViewGroup != null) {
+ mUserSwitcherViewGroup.setAlpha(1f);
+ mUserSwitcherViewGroup.setTranslationY(0f);
+ }
}
});
animator.addUpdateListener(animation -> {
- float value = (float) animation.getAnimatedValue();
- mUserSwitcherViewGroup.setAlpha(value);
- mUserSwitcherViewGroup.setTranslationY(yTrans - yTrans * value);
+ if (mUserSwitcherViewGroup != null) {
+ float value = (float) animation.getAnimatedValue();
+ mUserSwitcherViewGroup.setAlpha(value);
+ mUserSwitcherViewGroup.setTranslationY(yTrans - yTrans * value);
+ }
});
animator.start();
}
@@ -1148,6 +1160,10 @@
Log.e(TAG, "Current user in user switcher is null.");
return;
}
+ if (mUserSwitcher == null) {
+ Log.w(TAG, "User switcher is not inflated, cannot setupUserSwitcher");
+ return;
+ }
final String currentUserName = mUserSwitcherController.getCurrentUserName();
findLargeUserIcon(currentUser.info.id,
(Drawable userIcon) -> {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
index 60edaae..158623f 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
@@ -280,6 +280,14 @@
if (mLocalBluetoothManager == null) {
return;
}
+
+ // Remove the default padding of the system ui dialog
+ View container = dialog.findViewById(android.R.id.custom);
+ if (container != null && container.getParent() != null) {
+ View containerParent = (View) container.getParent();
+ containerParent.setPadding(0, 0, 0, 0);
+ }
+
mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_DIALOG_SHOW, mLaunchSourceId);
mPairButton = dialog.requireViewById(R.id.pair_new_device_button);
mDeviceList = dialog.requireViewById(R.id.device_list);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java
index 664f3f8..9367cb5 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java
@@ -108,6 +108,7 @@
private final ImageView mIconView;
private final ImageView mGearIcon;
private final View mGearView;
+ private final View mDividerView;
DeviceItemViewHolder(@NonNull View itemView, Context context) {
super(itemView);
@@ -118,6 +119,7 @@
mIconView = itemView.requireViewById(R.id.bluetooth_device_icon);
mGearIcon = itemView.requireViewById(R.id.gear_icon_image);
mGearView = itemView.requireViewById(R.id.gear_icon);
+ mDividerView = itemView.requireViewById(R.id.divider);
}
public void bindView(DeviceItem item, HearingDeviceItemCallback callback) {
@@ -153,6 +155,7 @@
mGearIcon.getDrawable().mutate().setTint(tintColor);
mGearView.setOnClickListener(view -> callback.onDeviceItemGearClicked(item, view));
+ mDividerView.setBackgroundColor(tintColor);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
index 5dd4c1c..02bf9db 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
@@ -23,6 +23,7 @@
import androidx.room.Query
import androidx.room.RoomDatabase
import androidx.room.Transaction
+import androidx.room.Update
import androidx.sqlite.db.SupportSQLiteDatabase
import com.android.systemui.communal.nano.CommunalHubState
import com.android.systemui.communal.shared.model.CommunalContentSize
@@ -171,8 +172,7 @@
@Query("UPDATE communal_item_rank_table SET rank = :order WHERE uid = :itemUid")
fun updateItemRank(itemUid: Long, order: Int)
- @Query("UPDATE communal_widget_table SET span_y = :spanY WHERE widget_id = :widgetId")
- fun updateWidgetSpanY(widgetId: Int, spanY: Int)
+ @Update fun updateWidget(widget: CommunalWidgetItem)
@Query("DELETE FROM communal_widget_table") fun clearCommunalWidgetsTable()
@@ -189,6 +189,15 @@
}
@Transaction
+ fun resizeWidget(appWidgetId: Int, spanY: Int, widgetIdToRankMap: Map<Int, Int>) {
+ val widget = getWidgetByIdNow(appWidgetId)
+ if (widget != null) {
+ updateWidget(widget.copy(spanY = spanY))
+ }
+ updateWidgetOrder(widgetIdToRankMap)
+ }
+
+ @Transaction
fun addWidget(
widgetId: Int,
provider: ComponentName,
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
index 3312f3c..0e94289 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
@@ -21,6 +21,7 @@
import android.content.ComponentName
import android.os.UserHandle
import android.os.UserManager
+import com.android.systemui.Flags.communalWidgetResizing
import com.android.systemui.common.data.repository.PackageChangeRepository
import com.android.systemui.common.shared.model.PackageInstallSession
import com.android.systemui.communal.data.backup.CommunalBackupUtils
@@ -82,7 +83,7 @@
*
* @param widgetIdToRankMap mapping of the widget ids to the rank of the widget.
*/
- fun updateWidgetOrder(widgetIdToRankMap: Map<Int, Int>) {}
+ fun updateWidgetOrder(widgetIdToRankMap: Map<Int, Int>)
/**
* Restores the database by reading a state file from disk and updating the widget ids according
@@ -96,10 +97,13 @@
/**
* Update the spanY of a widget in the database.
*
- * @param widgetId id of the widget to update.
+ * @param appWidgetId id of the widget to update.
* @param spanY new spanY value for the widget.
+ * @param widgetIdToRankMap mapping of the widget ids to its rank. Allows re-ordering widgets
+ * alongside the resize, in case resizing also requires re-ordering. This ensures the
+ * re-ordering is done in the same database transaction as the resize.
*/
- fun updateWidgetSpanY(widgetId: Int, spanY: Int)
+ fun resizeWidget(appWidgetId: Int, spanY: Int, widgetIdToRankMap: Map<Int, Int>)
}
@SysUISingleton
@@ -135,15 +139,17 @@
componentName = widget.componentName,
rank = rank.rank,
providerInfo = providers[widget.widgetId],
+ spanY = widget.spanY,
)
}
}
- override fun updateWidgetSpanY(widgetId: Int, spanY: Int) {
+ override fun resizeWidget(appWidgetId: Int, spanY: Int, widgetIdToRankMap: Map<Int, Int>) {
+ if (!communalWidgetResizing()) return
bgScope.launch {
- communalWidgetDao.updateWidgetSpanY(widgetId, spanY)
+ communalWidgetDao.resizeWidget(appWidgetId, spanY, widgetIdToRankMap)
logger.i({ "Updated spanY of widget $int1 to $int2." }) {
- int1 = widgetId
+ int1 = appWidgetId
int2 = spanY
}
backupManager.dataChanged()
@@ -445,7 +451,7 @@
val appWidgetId: Int,
val componentName: String,
val rank: Int,
+ val spanY: Int,
var providerInfo: AppWidgetProviderInfo? = null,
- var spanY: Int = 3,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index a687734..3a04d02 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -399,6 +399,10 @@
fun updateWidgetOrder(widgetIdToRankMap: Map<Int, Int>) =
widgetRepository.updateWidgetOrder(widgetIdToRankMap)
+ fun resizeWidget(appWidgetId: Int, spanY: Int, widgetIdToRankMap: Map<Int, Int>) {
+ widgetRepository.resizeWidget(appWidgetId, spanY, widgetIdToRankMap)
+ }
+
/** Request to unpause work profile that is currently in quiet mode. */
fun unpauseWorkProfile() {
managedProfileController.setWorkModeEnabled(true)
@@ -449,6 +453,7 @@
providerInfo = widget.providerInfo,
appWidgetHost = appWidgetHost,
inQuietMode = isQuietModeEnabled(widget.providerInfo.profile),
+ size = CommunalContentSize.toSize(widget.spanY),
)
}
is CommunalWidgetContentModel.Pending -> {
@@ -457,6 +462,7 @@
rank = widget.rank,
componentName = widget.componentName,
icon = widget.icon,
+ size = CommunalContentSize.toSize(widget.spanY),
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
index c2f6e85..4c2c094 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
@@ -62,11 +62,10 @@
val providerInfo: AppWidgetProviderInfo,
val appWidgetHost: CommunalAppWidgetHost,
val inQuietMode: Boolean,
+ override val size: CommunalContentSize,
) : WidgetContent {
override val key = KEY.widget(appWidgetId)
override val componentName: ComponentName = providerInfo.provider
- // Widget size is always half.
- override val size = CommunalContentSize.HALF
/** Whether this widget can be reconfigured after it has already been added. */
val reconfigurable: Boolean
@@ -79,11 +78,10 @@
override val appWidgetId: Int,
override val rank: Int,
val providerInfo: AppWidgetProviderInfo,
+ override val size: CommunalContentSize,
) : WidgetContent {
override val key = KEY.disabledWidget(appWidgetId)
override val componentName: ComponentName = providerInfo.provider
- // Widget size is always half.
- override val size = CommunalContentSize.HALF
val appInfo: ApplicationInfo?
get() = providerInfo.providerInfo?.applicationInfo
@@ -93,11 +91,10 @@
override val appWidgetId: Int,
override val rank: Int,
override val componentName: ComponentName,
+ override val size: CommunalContentSize,
val icon: Bitmap? = null,
) : WidgetContent {
override val key = KEY.pendingWidget(appWidgetId)
- // Widget size is always half.
- override val size = CommunalContentSize.HALF
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
index bcbc8f6..0c9ea78 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
@@ -25,13 +25,14 @@
sealed interface CommunalWidgetContentModel {
val appWidgetId: Int
val rank: Int
+ val spanY: Int
/** Widget is ready to display */
data class Available(
override val appWidgetId: Int,
val providerInfo: AppWidgetProviderInfo,
override val rank: Int,
- val spanY: Int = 3,
+ override val spanY: Int,
) : CommunalWidgetContentModel
/** Widget is pending installation */
@@ -41,6 +42,6 @@
val componentName: ComponentName,
val icon: Bitmap?,
val user: UserHandle,
- val spanY: Int = 3,
+ override val spanY: Int,
) : CommunalWidgetContentModel
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index 0929d3e..e25ea4c 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -105,7 +105,7 @@
scene: SceneKey,
loggingReason: String,
transitionKey: TransitionKey? = null,
- keyguardState: KeyguardState? = null
+ keyguardState: KeyguardState? = null,
) {
communalSceneInteractor.changeScene(scene, loggingReason, transitionKey, keyguardState)
}
@@ -155,17 +155,10 @@
) {}
/** Called as the UI requests deleting a widget. */
- open fun onDeleteWidget(
- id: Int,
- componentName: ComponentName,
- rank: Int,
- ) {}
+ open fun onDeleteWidget(id: Int, componentName: ComponentName, rank: Int) {}
/** Called as the UI detects a tap event on the widget. */
- open fun onTapWidget(
- componentName: ComponentName,
- rank: Int,
- ) {}
+ open fun onTapWidget(componentName: ComponentName, rank: Int) {}
/**
* Called as the UI requests reordering widgets.
@@ -176,10 +169,19 @@
*/
open fun onReorderWidgets(widgetIdToRankMap: Map<Int, Int>) {}
+ /**
+ * Called as the UI requests resizing a widget.
+ *
+ * @param appWidgetId The id of the widget being resized.
+ * @param spanY The new size of the widget, in grid spans.
+ * @param widgetIdToRankMap Mapping of the widget ids to its rank. Allows re-ordering widgets
+ * alongside the resize, in case resizing also requires re-ordering. This ensures the
+ * re-ordering is done in the same database transaction as the resize.
+ */
+ open fun onResizeWidget(appWidgetId: Int, spanY: Int, widgetIdToRankMap: Map<Int, Int>) {}
+
/** Called as the UI requests opening the widget editor with an optional preselected widget. */
- open fun onOpenWidgetEditor(
- shouldOpenWidgetPickerOnStart: Boolean = false,
- ) {}
+ open fun onOpenWidgetEditor(shouldOpenWidgetPickerOnStart: Boolean = false) {}
/** Called as the UI requests to dismiss the CTA tile. */
open fun onDismissCtaTile() {}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index 65f0679..3ae9250 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -107,9 +107,9 @@
allOf(
keyguardTransitionInteractor.isFinishedIn(
scene = Scenes.Gone,
- stateWithoutSceneContainer = KeyguardState.GONE
+ stateWithoutSceneContainer = KeyguardState.GONE,
),
- communalInteractor.editModeOpen
+ communalInteractor.editModeOpen,
)
.filter { it }
@@ -128,17 +128,13 @@
componentName: ComponentName,
user: UserHandle,
rank: Int?,
- configurator: WidgetConfigurator?
+ configurator: WidgetConfigurator?,
) {
communalInteractor.addWidget(componentName, user, rank, configurator)
metricsLogger.logAddWidget(componentName.flattenToString(), rank)
}
- override fun onDeleteWidget(
- id: Int,
- componentName: ComponentName,
- rank: Int,
- ) {
+ override fun onDeleteWidget(id: Int, componentName: ComponentName, rank: Int) {
communalInteractor.deleteWidget(id)
metricsLogger.logRemoveWidget(componentName.flattenToString(), rank)
}
@@ -146,6 +142,10 @@
override fun onReorderWidgets(widgetIdToRankMap: Map<Int, Int>) =
communalInteractor.updateWidgetOrder(widgetIdToRankMap)
+ override fun onResizeWidget(appWidgetId: Int, spanY: Int, widgetIdToRankMap: Map<Int, Int>) {
+ communalInteractor.resizeWidget(appWidgetId, spanY, widgetIdToRankMap)
+ }
+
override fun onReorderWidgetStart() {
// Clear selection status
setSelectedKey(null)
@@ -173,7 +173,7 @@
val announcementText =
context.getString(
R.string.accessibility_announcement_communal_widget_added,
- widgetLabel
+ widgetLabel,
)
accessibilityManager.sendAccessibilityEvent(
AccessibilityEvent(AccessibilityEvent.TYPE_ANNOUNCEMENT).apply {
@@ -187,7 +187,7 @@
/** Launch the widget picker activity using the given {@link ActivityResultLauncher}. */
suspend fun onOpenWidgetPicker(
resources: Resources,
- activityLauncher: ActivityResultLauncher<Intent>
+ activityLauncher: ActivityResultLauncher<Intent>,
): Boolean =
withContext(backgroundDispatcher) {
val widgets = communalInteractor.widgetContent.first()
@@ -210,21 +210,21 @@
private fun getWidgetPickerActivityIntent(
resources: Resources,
- excludeList: ArrayList<AppWidgetProviderInfo>
+ excludeList: ArrayList<AppWidgetProviderInfo>,
): Intent? {
return Intent(Intent.ACTION_PICK).apply {
setPackage(launcherPackage)
putExtra(
EXTRA_DESIRED_WIDGET_WIDTH,
- resources.getDimensionPixelSize(R.dimen.communal_widget_picker_desired_width)
+ resources.getDimensionPixelSize(R.dimen.communal_widget_picker_desired_width),
)
putExtra(
EXTRA_DESIRED_WIDGET_HEIGHT,
- resources.getDimensionPixelSize(R.dimen.communal_widget_picker_desired_height)
+ resources.getDimensionPixelSize(R.dimen.communal_widget_picker_desired_height),
)
putExtra(
AppWidgetManager.EXTRA_CATEGORY_FILTER,
- CommunalWidgetCategories.defaultCategories
+ CommunalWidgetCategories.defaultCategories,
)
communalSettingsInteractor.workProfileUserDisallowedByDevicePolicy.value?.let {
@@ -234,7 +234,7 @@
putExtra(EXTRA_PICKER_TITLE, resources.getString(R.string.communal_widget_picker_title))
putExtra(
EXTRA_PICKER_DESCRIPTION,
- resources.getString(R.string.communal_widget_picker_description)
+ resources.getString(R.string.communal_widget_picker_description),
)
putParcelableArrayListExtra(EXTRA_ADDED_APP_WIDGETS_KEY, excludeList)
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModel.kt
new file mode 100644
index 0000000..87fcdd7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModel.kt
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.ui.viewmodel
+
+import androidx.compose.foundation.gestures.AnchoredDraggableState
+import androidx.compose.foundation.gestures.DraggableAnchors
+import androidx.compose.runtime.snapshotFlow
+import com.android.app.tracing.coroutines.coroutineScope
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onEach
+
+enum class DragHandle {
+ TOP,
+ BOTTOM,
+}
+
+data class ResizeInfo(
+ /**
+ * The number of spans to resize by. A positive number indicates expansion, whereas a negative
+ * number indicates shrinking.
+ */
+ val spans: Int,
+ /** The drag handle which was used to resize the element. */
+ val fromHandle: DragHandle,
+) {
+ /** Whether we are expanding. If false, then we are shrinking. */
+ val isExpanding = spans > 0
+}
+
+class ResizeableItemFrameViewModel : ExclusiveActivatable() {
+ private data class GridLayoutInfo(
+ val minSpan: Int,
+ val maxSpan: Int,
+ val heightPerSpanPx: Float,
+ val verticalItemSpacingPx: Float,
+ val currentRow: Int,
+ val currentSpan: Int,
+ )
+
+ /**
+ * The layout information necessary in order to calculate the pixel offsets of the drag anchor
+ * points.
+ */
+ private val gridLayoutInfo = MutableStateFlow<GridLayoutInfo?>(null)
+
+ val topDragState = AnchoredDraggableState(0, DraggableAnchors { 0 at 0f })
+ val bottomDragState = AnchoredDraggableState(0, DraggableAnchors { 0 at 0f })
+
+ /** Emits a [ResizeInfo] when the element is resized using a drag gesture. */
+ val resizeInfo: Flow<ResizeInfo> =
+ merge(
+ snapshotFlow { topDragState.settledValue }.map { ResizeInfo(-it, DragHandle.TOP) },
+ snapshotFlow { bottomDragState.settledValue }
+ .map { ResizeInfo(it, DragHandle.BOTTOM) },
+ )
+ .filter { it.spans != 0 }
+ .distinctUntilChanged()
+
+ /**
+ * Sets the necessary grid layout information needed for calculating the pixel offsets of the
+ * drag anchors.
+ */
+ fun setGridLayoutInfo(
+ verticalItemSpacingPx: Float,
+ verticalContentPaddingPx: Float,
+ viewportHeightPx: Int,
+ maxItemSpan: Int,
+ minItemSpan: Int,
+ currentRow: Int?,
+ currentSpan: Int?,
+ ) {
+ if (currentSpan == null || currentRow == null) {
+ gridLayoutInfo.value = null
+ return
+ }
+ require(maxItemSpan >= minItemSpan) {
+ "Maximum item span of $maxItemSpan cannot be less than the minimum span of $minItemSpan"
+ }
+ require(minItemSpan in 1..maxItemSpan) {
+ "Minimum span must be between 1 and $maxItemSpan, but was $minItemSpan"
+ }
+ require(currentSpan % minItemSpan == 0) {
+ "Current span of $currentSpan is not a multiple of the minimum span of $minItemSpan"
+ }
+ val availableHeight = viewportHeightPx - verticalContentPaddingPx
+ val totalSpacing = verticalItemSpacingPx * ((maxItemSpan / minItemSpan) - 1)
+ val heightPerSpanPx = (availableHeight - totalSpacing) / maxItemSpan
+ gridLayoutInfo.value =
+ GridLayoutInfo(
+ minSpan = minItemSpan,
+ maxSpan = maxItemSpan,
+ heightPerSpanPx = heightPerSpanPx,
+ verticalItemSpacingPx = verticalItemSpacingPx,
+ currentRow = currentRow,
+ currentSpan = currentSpan,
+ )
+ }
+
+ private fun calculateAnchorsForHandle(
+ handle: DragHandle,
+ layoutInfo: GridLayoutInfo?,
+ ): DraggableAnchors<Int> {
+
+ if (layoutInfo == null || !isDragAllowed(handle, layoutInfo)) {
+ return DraggableAnchors { 0 at 0f }
+ }
+
+ val (
+ minItemSpan,
+ maxItemSpan,
+ heightPerSpanPx,
+ verticalSpacingPx,
+ currentRow,
+ currentSpan,
+ ) = layoutInfo
+
+ // The maximum row this handle can be dragged to.
+ val maxRow =
+ if (handle == DragHandle.TOP) {
+ (currentRow + currentSpan - minItemSpan).coerceAtLeast(0)
+ } else {
+ maxItemSpan
+ }
+
+ // The minimum row this handle can be dragged to.
+ val minRow =
+ if (handle == DragHandle.TOP) {
+ 0
+ } else {
+ (currentRow + minItemSpan).coerceAtMost(maxItemSpan)
+ }
+
+ // The current row position of this handle
+ val currentPosition = if (handle == DragHandle.TOP) currentRow else currentRow + currentSpan
+
+ return DraggableAnchors {
+ for (targetRow in minRow..maxRow step minItemSpan) {
+ val diff = targetRow - currentPosition
+ val spacing = diff / minItemSpan * verticalSpacingPx
+ diff at diff * heightPerSpanPx + spacing
+ }
+ }
+ }
+
+ private fun isDragAllowed(handle: DragHandle, layoutInfo: GridLayoutInfo): Boolean {
+ val minItemSpan = layoutInfo.minSpan
+ val maxItemSpan = layoutInfo.maxSpan
+ val currentRow = layoutInfo.currentRow
+ val currentSpan = layoutInfo.currentSpan
+ val atMinSize = currentSpan == minItemSpan
+
+ // If already at the minimum size and in the first row, item cannot be expanded from the top
+ if (handle == DragHandle.TOP && currentRow == 0 && atMinSize) {
+ return false
+ }
+
+ // If already at the minimum size and occupying the last row, item cannot be expanded from
+ // the
+ // bottom
+ if (handle == DragHandle.BOTTOM && (currentRow + currentSpan) == maxItemSpan && atMinSize) {
+ return false
+ }
+
+ // If at maximum size, item can only be shrunk from the bottom and not the top.
+ if (handle == DragHandle.TOP && currentSpan == maxItemSpan) {
+ return false
+ }
+
+ return true
+ }
+
+ override suspend fun onActivated(): Nothing {
+ coroutineScope("ResizeableItemFrameViewModel.onActivated") {
+ gridLayoutInfo
+ .onEach { layoutInfo ->
+ topDragState.updateAnchors(
+ calculateAnchorsForHandle(DragHandle.TOP, layoutInfo)
+ )
+ bottomDragState.updateAnchors(
+ calculateAnchorsForHandle(DragHandle.BOTTOM, layoutInfo)
+ )
+ }
+ .launchIn(this)
+ awaitCancellation()
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
index 3b5d5a8..b19b2d9 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
@@ -114,7 +114,7 @@
faceAuthenticationLogger.bouncerVisibilityChanged()
runFaceAuth(
FaceAuthUiEvent.FACE_AUTH_UPDATED_PRIMARY_BOUNCER_SHOWN,
- fallbackToDetect = false
+ fallbackToDetect = false,
)
}
.launchIn(applicationScope)
@@ -125,7 +125,7 @@
faceAuthenticationLogger.alternateBouncerVisibilityChanged()
runFaceAuth(
FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN,
- fallbackToDetect = false
+ fallbackToDetect = false,
)
}
.launchIn(applicationScope)
@@ -153,7 +153,7 @@
it.lastWakeReason.powerManagerWakeReason
runFaceAuth(
FaceAuthUiEvent.FACE_AUTH_UPDATED_KEYGUARD_VISIBILITY_CHANGED,
- fallbackToDetect = true
+ fallbackToDetect = true,
)
}
.launchIn(applicationScope)
@@ -193,13 +193,16 @@
.map { (_, curr) -> curr.userInfo.id }
.sample(isBouncerVisible, ::Pair)
.onEach { (userId, isBouncerCurrentlyVisible) ->
+ if (!isFaceAuthEnabledAndEnrolled()) {
+ return@onEach
+ }
resetLockedOutState(userId)
yield()
runFaceAuth(
FaceAuthUiEvent.FACE_AUTH_UPDATED_USER_SWITCHING,
// Fallback to detection if bouncer is not showing so that we can detect a
// face and then show the bouncer to the user if face auth can't run
- fallbackToDetect = !isBouncerCurrentlyVisible
+ fallbackToDetect = !isBouncerCurrentlyVisible,
)
}
.launchIn(applicationScope)
@@ -210,7 +213,7 @@
repository.cancel()
runFaceAuth(
FaceAuthUiEvent.FACE_AUTH_CAMERA_AVAILABLE_CHANGED,
- fallbackToDetect = true
+ fallbackToDetect = true,
)
}
}
@@ -321,7 +324,7 @@
faceAuthenticationStatusOverride.value =
ErrorFaceAuthenticationStatus(
BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT,
- context.resources.getString(R.string.keyguard_face_unlock_unavailable)
+ context.resources.getString(R.string.keyguard_face_unlock_unavailable),
)
} else {
faceAuthenticationStatusOverride.value = null
diff --git a/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt b/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt
index 373279c..462e820 100644
--- a/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt
@@ -20,6 +20,8 @@
import com.android.systemui.display.data.repository.DeviceStateRepositoryImpl
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.display.data.repository.DisplayRepositoryImpl
+import com.android.systemui.display.data.repository.FocusedDisplayRepository
+import com.android.systemui.display.data.repository.FocusedDisplayRepositoryImpl
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractorImpl
import dagger.Binds
@@ -39,4 +41,9 @@
fun bindsDeviceStateRepository(
deviceStateRepository: DeviceStateRepositoryImpl
): DeviceStateRepository
+
+ @Binds
+ fun bindsFocusedDisplayRepository(
+ focusedDisplayRepository: FocusedDisplayRepositoryImpl
+ ): FocusedDisplayRepository
}
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/FocusedDisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/FocusedDisplayRepository.kt
index dc07cca..6fc08f6 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/FocusedDisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/FocusedDisplayRepository.kt
@@ -19,8 +19,7 @@
import android.annotation.MainThread
import android.view.Display.DEFAULT_DISPLAY
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel
import com.android.systemui.log.dagger.FocusedDisplayRepoLog
@@ -38,20 +37,30 @@
import kotlinx.coroutines.flow.stateIn
/** Repository tracking display focus. */
+interface FocusedDisplayRepository {
+ /** Provides the currently focused display. */
+ val focusedDisplayId: StateFlow<Int>
+}
+
@SysUISingleton
@MainThread
-class FocusedDisplayRepository
+class FocusedDisplayRepositoryImpl
@Inject
constructor(
- @Application val scope: CoroutineScope,
- @Main private val mainExecutor: Executor,
+ @Background val backgroundScope: CoroutineScope,
+ @Background private val backgroundExecutor: Executor,
transitions: ShellTransitions,
@FocusedDisplayRepoLog logBuffer: LogBuffer,
-) {
+) : FocusedDisplayRepository {
val focusedTask: Flow<Int> =
- conflatedCallbackFlow {
- val listener = FocusTransitionListener { displayId -> trySend(displayId) }
- transitions.setFocusTransitionListener(listener, mainExecutor)
+ conflatedCallbackFlow<Int> {
+ val listener =
+ object : FocusTransitionListener {
+ override fun onFocusedDisplayChanged(displayId: Int) {
+ trySend(displayId)
+ }
+ }
+ transitions.setFocusTransitionListener(listener, backgroundExecutor)
awaitClose { transitions.unsetFocusTransitionListener(listener) }
}
.onEach {
@@ -63,7 +72,6 @@
)
}
- /** Provides the currently focused display. */
- val focusedDisplayId: StateFlow<Int>
- get() = focusedTask.stateIn(scope, SharingStarted.Eagerly, DEFAULT_DISPLAY)
+ override val focusedDisplayId: StateFlow<Int>
+ get() = focusedTask.stateIn(backgroundScope, SharingStarted.Eagerly, DEFAULT_DISPLAY)
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModel.kt
new file mode 100644
index 0000000..8b6cc8c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModel.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams.ui.viewmodel
+
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
+import com.android.systemui.scene.ui.viewmodel.UserActionsViewModel
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+/** Handles user input for the dream scene. */
+class DreamUserActionsViewModel @AssistedInject constructor() : UserActionsViewModel() {
+
+ override suspend fun hydrateActions(setActions: (Map<UserAction, UserActionResult>) -> Unit) {
+ setActions(emptyMap())
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(): DreamUserActionsViewModel
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduStatsInteractor.kt b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduStatsInteractor.kt
index 0e2d9b6..43e39cf 100644
--- a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduStatsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduStatsInteractor.kt
@@ -30,7 +30,7 @@
import java.time.Clock
import javax.inject.Inject
import kotlin.time.Duration
-import kotlin.time.Duration.Companion.hours
+import kotlin.time.Duration.Companion.days
import kotlin.time.DurationUnit
import kotlin.time.toDuration
import kotlinx.coroutines.CoroutineScope
@@ -64,7 +64,7 @@
get() =
SystemProperties.getLong(
"persist.contextual_edu.initial_delay_sec",
- /* defaultValue= */ 72.hours.inWholeSeconds
+ /* defaultValue= */ 7.days.inWholeSeconds,
)
.toDuration(DurationUnit.SECONDS)
}
diff --git a/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduViewModel.kt b/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduViewModel.kt
index 32e7f41..5563969 100644
--- a/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduViewModel.kt
@@ -48,7 +48,7 @@
) {
companion object {
- const val DEFAULT_DIALOG_TIMEOUT_MILLIS = 3500
+ const val DEFAULT_DIALOG_TIMEOUT_MILLIS = 5000
}
private val timeoutMillis: Long
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index 1e9541e..6d1d9cb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -189,6 +189,7 @@
internalTransitionInteractor.currentTransitionInfoInternal,
keyguardInteractor.statusBarState,
keyguardInteractor.isKeyguardDismissible,
+ keyguardInteractor.isKeyguardOccluded,
)
.collect {
(
@@ -196,7 +197,8 @@
startedStep,
currentTransitionInfo,
statusBarState,
- isKeyguardUnlocked) ->
+ isKeyguardUnlocked,
+ isKeyguardOccluded) ->
val id = transitionId
if (id != null) {
if (startedStep.to == KeyguardState.PRIMARY_BOUNCER) {
@@ -236,9 +238,13 @@
if (nextState == TransitionState.CANCELED) {
transitionRepository.startTransition(
TransitionInfo(
- ownerName = name,
+ ownerName =
+ "$name " +
+ "(on behalf of FromPrimaryBouncerInteractor)",
from = KeyguardState.PRIMARY_BOUNCER,
- to = KeyguardState.LOCKSCREEN,
+ to =
+ if (isKeyguardOccluded) KeyguardState.OCCLUDED
+ else KeyguardState.LOCKSCREEN,
modeOnCanceled = TransitionModeOnCanceled.REVERSE,
animator =
getDefaultAnimatorForTransitionsToState(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
index ca1a800..68244d8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
@@ -60,6 +60,7 @@
primaryBouncerToAodTransitionViewModel: PrimaryBouncerToAodTransitionViewModel,
primaryBouncerToDozingTransitionViewModel: PrimaryBouncerToDozingTransitionViewModel,
primaryBouncerToLockscreenTransitionViewModel: PrimaryBouncerToLockscreenTransitionViewModel,
+ lockscreenToDozingTransitionViewModel: LockscreenToDozingTransitionViewModel,
) {
val color: Flow<Int> =
deviceEntryIconViewModel.useBackgroundProtection.flatMapLatest { useBackground ->
@@ -103,7 +104,9 @@
offToLockscreenTransitionViewModel.deviceEntryBackgroundViewAlpha,
primaryBouncerToAodTransitionViewModel.deviceEntryBackgroundViewAlpha,
primaryBouncerToDozingTransitionViewModel.deviceEntryBackgroundViewAlpha,
- primaryBouncerToLockscreenTransitionViewModel.deviceEntryBackgroundViewAlpha,
+ primaryBouncerToLockscreenTransitionViewModel
+ .deviceEntryBackgroundViewAlpha,
+ lockscreenToDozingTransitionViewModel.deviceEntryBackgroundViewAlpha,
)
.merge()
.onStart {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt
index d3eefca..7abf35d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt
@@ -55,16 +55,16 @@
onCancel = { 1f },
)
+ val deviceEntryBackgroundViewAlpha: Flow<Float> =
+ transitionAnimation.immediatelyTransitionTo(0f)
+
override val deviceEntryParentViewAlpha: Flow<Float> =
deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled.flatMapLatest {
isUdfpsEnrolledAndEnabled ->
if (isUdfpsEnrolledAndEnabled) {
transitionAnimation.immediatelyTransitionTo(1f)
} else {
- transitionAnimation.sharedFlow(
- duration = 250.milliseconds,
- onStep = { 1f - it },
- )
+ transitionAnimation.sharedFlow(duration = 250.milliseconds, onStep = { 1f - it })
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
index 51d2329..65c29b8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -52,8 +52,8 @@
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.approachLayout
import androidx.compose.ui.layout.onPlaced
-import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.layout.positionInRoot
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.res.dimensionResource
@@ -66,6 +66,9 @@
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
+import com.android.compose.animation.scene.ContentKey
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.ElementMatcher
import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneScope
@@ -288,7 +291,7 @@
transitions =
transitions {
from(QuickQuickSettings, QuickSettings) {
- quickQuickSettingsToQuickSettings()
+ quickQuickSettingsToQuickSettings(viewModel::inFirstPage::get)
}
},
)
@@ -533,6 +536,10 @@
onDispose { qqsVisible.value = false }
}
+ val squishiness by
+ viewModel.containerViewModel.quickQuickSettingsViewModel.squishinessViewModel
+ .squishiness
+ .collectAsStateWithLifecycle()
Column(modifier = Modifier.sysuiResTag("quick_qs_panel")) {
Box(
modifier =
@@ -546,7 +553,16 @@
topFromRoot + coordinates.size.height,
)
}
- .onSizeChanged { size -> qqsHeight.value = size.height }
+ // Use an approach layout to determien the height without squishiness, as
+ // that's the value that NPVC and QuickSettingsController care about
+ // (measured height).
+ .approachLayout(isMeasurementApproachInProgress = { squishiness < 1f }) {
+ measurable,
+ constraints ->
+ qqsHeight.value = lookaheadSize.height
+ val placeable = measurable.measure(constraints)
+ layout(placeable.width, placeable.height) { placeable.place(0, 0) }
+ }
.padding(top = { qqsPadding }, bottom = { bottomPadding.roundToPx() })
) {
val qsEnabled by viewModel.qsEnabled.collectAsStateWithLifecycle()
@@ -704,6 +720,14 @@
else -> QuickSettings
}
}
+
+ val QqsTileElementMatcher =
+ object : ElementMatcher {
+ override fun matches(key: ElementKey, content: ContentKey): Boolean {
+ return content == SceneKeys.QuickQuickSettings &&
+ ElementKeys.TileElementMatcher.matches(key, content)
+ }
+ }
}
suspend fun synchronizeQsState(state: MutableSceneTransitionLayoutState, expansion: Flow<Float>) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/FromQuickQuickSettingsToQuickSettings.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/FromQuickQuickSettingsToQuickSettings.kt
index 1514986..9e3945e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/FromQuickQuickSettingsToQuickSettings.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/FromQuickQuickSettingsToQuickSettings.kt
@@ -17,13 +17,23 @@
package com.android.systemui.qs.composefragment.ui
import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.qs.composefragment.SceneKeys
import com.android.systemui.qs.shared.ui.ElementKeys
-fun TransitionBuilder.quickQuickSettingsToQuickSettings() {
+fun TransitionBuilder.quickQuickSettingsToQuickSettings(inFirstPage: () -> Boolean = { true }) {
fractionRange(start = 0.5f) { fade(ElementKeys.QuickSettingsContent) }
fractionRange(start = 0.9f) { fade(ElementKeys.FooterActions) }
anchoredTranslate(ElementKeys.QuickSettingsContent, ElementKeys.GridAnchor)
+
+ sharedElement(ElementKeys.TileElementMatcher, enabled = inFirstPage())
+
+ // This will animate between 0f (QQS) and 0.6, fading in the QQS tiles when coming back
+ // from non first page QS. The QS content ends fading out at 0.5f, so there's a brief
+ // overlap, but because they are really faint, it looks better than complete black without
+ // overlap.
+ fractionRange(end = 0.6f) { fade(SceneKeys.QqsTileElementMatcher) }
+ anchoredTranslate(SceneKeys.QqsTileElementMatcher, ElementKeys.GridAnchor)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
index 2d4e358..7a8b2c2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
@@ -31,6 +31,7 @@
import com.android.systemui.qs.composefragment.viewmodel.QSFragmentComposeViewModel.QSExpansionState
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
import com.android.systemui.qs.panels.domain.interactor.TileSquishinessInteractor
+import com.android.systemui.qs.panels.ui.viewmodel.PaginatedGridViewModel
import com.android.systemui.qs.ui.viewmodel.QuickSettingsContainerViewModel
import com.android.systemui.shade.LargeScreenHeaderHelper
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
@@ -71,6 +72,7 @@
private val configurationInteractor: ConfigurationInteractor,
private val largeScreenHeaderHelper: LargeScreenHeaderHelper,
private val squishinessInteractor: TileSquishinessInteractor,
+ private val paginatedGridViewModel: PaginatedGridViewModel,
@Assisted private val lifecycleScope: LifecycleCoroutineScope,
) : Dumpable, ExclusiveActivatable() {
val footerActionsViewModel =
@@ -292,6 +294,9 @@
*/
var collapseExpandAccessibilityAction: Runnable? = null
+ val inFirstPage: Boolean
+ get() = paginatedGridViewModel.inFirstPage
+
override suspend fun onActivated(): Nothing {
hydrateSquishinessInteractor()
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PagerDots.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PagerDots.kt
index 331aabb..0dedfe1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PagerDots.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PagerDots.kt
@@ -46,6 +46,8 @@
import kotlin.math.absoluteValue
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
+import platform.test.motion.compose.values.MotionTestValueKey
+import platform.test.motion.compose.values.motionTestValues
@Composable
fun PagerDots(
@@ -93,13 +95,22 @@
}
Row(
- modifier = modifier.wrapContentWidth().pagerDotsSemantics(pagerState, coroutineScope),
+ modifier =
+ modifier
+ .motionTestValues { activeMarkerWidth exportAs PagerDotsMotionKeys.indicatorWidth }
+ .wrapContentWidth()
+ .pagerDotsSemantics(pagerState, coroutineScope),
horizontalArrangement = spacedBy(spaceSize),
verticalAlignment = Alignment.CenterVertically,
) {
// This means that the active rounded rect has to be drawn between the current page
// and the previous one (as we are animating back), or the current one if not transitioning
- val withPrevious = pagerState.currentPageOffsetFraction <= 0 || pagerState.isOverscrolling()
+ val withPrevious by
+ remember(pagerState) {
+ derivedStateOf {
+ pagerState.currentPageOffsetFraction <= 0 || pagerState.isOverscrolling()
+ }
+ }
repeat(pagerState.pageCount) { page ->
Canvas(Modifier.size(dotSize)) {
val rtl = layoutDirection == LayoutDirection.Rtl
@@ -127,6 +138,10 @@
}
}
+object PagerDotsMotionKeys {
+ val indicatorWidth = MotionTestValueKey<Dp>("indicatorWidth")
+}
+
private fun Modifier.pagerDotsSemantics(
pagerState: PagerState,
coroutineScope: CoroutineScope,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt
index 083f529..e749475 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt
@@ -31,8 +31,10 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
+import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@@ -76,6 +78,11 @@
val pagerState = rememberPagerState(0) { pages.size }
+ // Used to track if this is currently in the first page or not, for animations
+ LaunchedEffect(key1 = pagerState) {
+ snapshotFlow { pagerState.currentPage == 0 }.collect { viewModel.inFirstPage = it }
+ }
+
Column {
HorizontalPager(
state = pagerState,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/SquishTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/SquishTile.kt
index ada1ef4..91f7641 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/SquishTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/SquishTile.kt
@@ -17,7 +17,7 @@
package com.android.systemui.qs.panels.ui.compose.infinitegrid
import androidx.compose.ui.Modifier
-import androidx.compose.ui.layout.layout
+import androidx.compose.ui.layout.approachLayout
import kotlin.math.roundToInt
/**
@@ -27,17 +27,22 @@
* [squishiness] on the measure/layout pass.
*
* The squished composable will be center aligned.
+ *
+ * Use an [approachLayout] to indicate that this should be measured in the lookahead step without
+ * using squishiness. If a parent of this node needs to determine unsquished height, they should
+ * also use an approachLayout tracking the squishiness.
*/
fun Modifier.verticalSquish(squishiness: () -> Float): Modifier {
- return layout { measurable, constraints ->
- val placeable = measurable.measure(constraints)
- val actualHeight = placeable.height
- val squishedHeight = actualHeight * squishiness()
- // Center the content by moving it UP (squishedHeight < actualHeight)
- val scroll = (squishedHeight - actualHeight) / 2
+ return approachLayout(isMeasurementApproachInProgress = { squishiness() < 1 }) { measurable, _
+ ->
+ val squishinessValue = squishiness()
+ val expectedHeight = lookaheadSize.height
- layout(placeable.width, squishedHeight.roundToInt()) {
- placeable.place(0, scroll.roundToInt())
- }
+ val placeable = measurable.measure(lookaheadConstraints)
+ val squishedHeight = (expectedHeight * squishinessValue).roundToInt()
+ // Center the content by moving it UP (squishedHeight < actualHeight)
+ val scroll = (squishedHeight - expectedHeight) / 2
+
+ layout(placeable.width, squishedHeight) { placeable.place(0, scroll) }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt
index 28bf474..d4f8298 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt
@@ -43,4 +43,10 @@
SharingStarted.WhileSubscribed(),
paginatedGridInteractor.defaultRows,
)
+
+ /*
+ * Tracks whether the current HorizontalPager (using this viewmodel) is in the first page.
+ * This requires it to be a `@SysUISingleton` to be shared between viewmodels.
+ */
+ var inFirstPage = true
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/shared/ui/ElementKeys.kt b/packages/SystemUI/src/com/android/systemui/qs/shared/ui/ElementKeys.kt
index 625459d..2425f13 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/shared/ui/ElementKeys.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/shared/ui/ElementKeys.kt
@@ -25,7 +25,10 @@
val GridAnchor = ElementKey("QuickSettingsGridAnchor")
val FooterActions = ElementKey("FooterActions")
- class TileElementKey(spec: TileSpec, val position: Int) : ElementKey(spec.spec, spec.spec)
+ fun TileSpec.toElementKey(positionInGrid: Int) =
+ ElementKey(this.spec, TileIdentity(this, positionInGrid))
- fun TileSpec.toElementKey(positionInGrid: Int) = TileElementKey(this, positionInGrid)
+ val TileElementMatcher = ElementKey.withIdentity { it is TileIdentity }
}
+
+private data class TileIdentity(val spec: TileSpec, val position: Int)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
index d89e73d..fb406d4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
@@ -45,10 +45,11 @@
import com.android.systemui.qs.tileimpl.QSTileImpl
import com.android.systemui.recordissue.IssueRecordingService.Companion.getStartIntent
import com.android.systemui.recordissue.IssueRecordingService.Companion.getStopIntent
+import com.android.systemui.recordissue.IssueRecordingServiceConnection
import com.android.systemui.recordissue.IssueRecordingState
import com.android.systemui.recordissue.RecordIssueDialogDelegate
import com.android.systemui.recordissue.RecordIssueModule.Companion.TILE_SPEC
-import com.android.systemui.recordissue.TraceurMessageSender
+import com.android.systemui.recordissue.TraceurConnection
import com.android.systemui.res.R
import com.android.systemui.screenrecord.RecordingController
import com.android.systemui.screenrecord.RecordingService
@@ -66,7 +67,7 @@
constructor(
host: QSHost,
uiEventLogger: QsEventLogger,
- @Background backgroundLooper: Looper,
+ @Background private val backgroundLooper: Looper,
@Main mainHandler: Handler,
falsingManager: FalsingManager,
metricsLogger: MetricsLogger,
@@ -78,7 +79,8 @@
private val dialogTransitionAnimator: DialogTransitionAnimator,
private val panelInteractor: PanelInteractor,
private val userContextProvider: UserContextProvider,
- private val traceurMessageSender: TraceurMessageSender,
+ irsConnProvider: IssueRecordingServiceConnection.Provider,
+ traceurConnProvider: TraceurConnection.Provider,
@Background private val bgExecutor: Executor,
private val issueRecordingState: IssueRecordingState,
private val delegateFactory: RecordIssueDialogDelegate.Factory,
@@ -93,11 +95,20 @@
metricsLogger,
statusBarStateController,
activityStarter,
- qsLogger
+ qsLogger,
) {
private val onRecordingChangeListener = Runnable { refreshState() }
+ private val irsConnection: IssueRecordingServiceConnection = irsConnProvider.create()
+ private val traceurConnection =
+ traceurConnProvider.create().apply {
+ onBound.add {
+ getTags(issueRecordingState)
+ doUnBind()
+ }
+ }
+
override fun handleSetListening(listening: Boolean) {
super.handleSetListening(listening)
if (listening) {
@@ -109,7 +120,7 @@
override fun handleDestroy() {
super.handleDestroy()
- bgExecutor.execute { traceurMessageSender.unbindFromTraceur(mContext) }
+ bgExecutor.execute { irsConnection.doUnBind() }
}
override fun getTileLabel(): CharSequence = mContext.getString(R.string.qs_record_issue_label)
@@ -142,7 +153,7 @@
DELAY_MS,
INTERVAL_MS,
pendingServiceIntent(getStartIntent(userContextProvider.userContext)),
- pendingServiceIntent(getStopIntent(userContextProvider.userContext))
+ pendingServiceIntent(getStopIntent(userContextProvider.userContext)),
)
private fun stopIssueRecordingService() =
@@ -154,10 +165,19 @@
userContextProvider.userContext,
RecordingService.REQUEST_CODE,
action,
- PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
)
private fun showPrompt(expandable: Expandable?) {
+ bgExecutor.execute {
+ // We only want to get the tags once per session, as this is not likely to change, if at
+ // all on a month to month basis. Using onBound's size is a way to verify if the tag
+ // retrieval has already happened or not.
+ if (traceurConnection.onBound.isNotEmpty()) {
+ traceurConnection.doBind()
+ }
+ irsConnection.doBind()
+ }
val dialog: AlertDialog =
delegateFactory
.create {
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
index 4d2bc91..3f875bc 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
@@ -23,9 +23,12 @@
import android.content.res.Resources
import android.net.Uri
import android.os.Handler
+import android.os.IBinder
+import android.os.Looper
import android.util.Log
import com.android.internal.logging.UiEventLogger
import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.LongRunning
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor
@@ -42,6 +45,7 @@
@Inject
constructor(
controller: RecordingController,
+ @Background private val bgLooper: Looper,
@LongRunning private val bgExecutor: Executor,
@Main handler: Handler,
uiEventLogger: UiEventLogger,
@@ -50,8 +54,8 @@
keyguardDismissUtil: KeyguardDismissUtil,
dialogTransitionAnimator: DialogTransitionAnimator,
panelInteractor: PanelInteractor,
- traceurMessageSender: TraceurMessageSender,
private val issueRecordingState: IssueRecordingState,
+ traceurConnectionProvider: TraceurConnection.Provider,
iActivityManager: IActivityManager,
) :
RecordingService(
@@ -64,18 +68,37 @@
keyguardDismissUtil,
) {
+ private val traceurConnection: TraceurConnection = traceurConnectionProvider.create()
+
private val session =
IssueRecordingServiceSession(
bgExecutor,
dialogTransitionAnimator,
panelInteractor,
- traceurMessageSender,
+ traceurConnection,
issueRecordingState,
iActivityManager,
notificationManager,
userContextProvider,
)
+ /**
+ * It is necessary to bind to IssueRecordingService from the Record Issue Tile because there are
+ * instances where this service is not created in the same user profile as the record issue tile
+ * aka, headless system user mode. In those instances, the TraceurConnection will be considered
+ * a leak in between notification actions unless the tile is bound to this service to keep it
+ * alive.
+ */
+ override fun onBind(intent: Intent): IBinder? {
+ traceurConnection.doBind()
+ return super.onBind(intent)
+ }
+
+ override fun onUnbind(intent: Intent?): Boolean {
+ traceurConnection.doUnBind()
+ return super.onUnbind(intent)
+ }
+
override fun getTag(): String = TAG
override fun getChannelId(): String = CHANNEL_ID
@@ -99,7 +122,6 @@
session.share(
intent.getIntExtra(EXTRA_NOTIFICATION_ID, mNotificationId),
intent.getParcelableExtra(EXTRA_PATH, Uri::class.java),
- this,
)
// Unlike all other actions, action_share has different behavior for the screen
// recording qs tile than it does for the record issue qs tile. Return sticky to
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingServiceConnection.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingServiceConnection.kt
new file mode 100644
index 0000000..85a5805
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingServiceConnection.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recordissue
+
+import android.content.Intent
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.settings.UserContextProvider
+import com.android.traceur.MessageConstants.SYSTEM_UI_PACKAGE_NAME
+import javax.inject.Inject
+
+/**
+ * It is necessary to bind to IssueRecordingService from the Record Issue Tile because there are
+ * instances where this service is not created in the same user profile as the record issue tile
+ * aka, headless system user mode. In those instances, the TraceurConnection will be considered a
+ * leak in between notification actions unless the tile is bound to this service to keep it alive.
+ */
+class IssueRecordingServiceConnection(userContextProvider: UserContextProvider) :
+ UserAwareConnection(
+ userContextProvider,
+ Intent().setClassName(SYSTEM_UI_PACKAGE_NAME, IssueRecordingService::class.java.name),
+ ) {
+ @SysUISingleton
+ class Provider @Inject constructor(private val userContextProvider: UserContextProvider) {
+ fun create() = IssueRecordingServiceConnection(userContextProvider)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingServiceSession.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingServiceSession.kt
index e4d3e6c..ad9b4fe 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingServiceSession.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingServiceSession.kt
@@ -19,7 +19,6 @@
import android.app.IActivityManager
import android.app.NotificationManager
import android.content.ContentResolver
-import android.content.Context
import android.net.Uri
import android.os.UserHandle
import android.provider.Settings
@@ -42,7 +41,7 @@
private val bgExecutor: Executor,
private val dialogTransitionAnimator: DialogTransitionAnimator,
private val panelInteractor: PanelInteractor,
- private val traceurMessageSender: TraceurMessageSender,
+ private val traceurConnection: TraceurConnection,
private val issueRecordingState: IssueRecordingState,
private val iActivityManager: IActivityManager,
private val notificationManager: NotificationManager,
@@ -50,7 +49,7 @@
) {
fun start() {
- bgExecutor.execute { traceurMessageSender.startTracing(issueRecordingState.traceConfig) }
+ bgExecutor.execute { traceurConnection.startTracing(issueRecordingState.traceConfig) }
issueRecordingState.isRecording = true
}
@@ -59,12 +58,12 @@
if (issueRecordingState.traceConfig.longTrace) {
Settings.Global.putInt(contentResolver, NOTIFY_SESSION_ENDED_SETTING, DISABLED)
}
- traceurMessageSender.stopTracing()
+ traceurConnection.stopTracing()
}
issueRecordingState.isRecording = false
}
- fun share(notificationId: Int, screenRecording: Uri?, context: Context) {
+ fun share(notificationId: Int, screenRecording: Uri?) {
bgExecutor.execute {
notificationManager.cancelAsUser(
null,
@@ -75,7 +74,7 @@
if (issueRecordingState.takeBugreport) {
iActivityManager.requestBugReportWithExtraAttachment(screenRecording)
} else {
- traceurMessageSender.shareTraces(context, screenRecording)
+ traceurConnection.shareTraces(screenRecording)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
index ed67e64..6758c3b 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
@@ -64,7 +64,6 @@
private val mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
private val screenCaptureDisabledDialogDelegate: ScreenCaptureDisabledDialogDelegate,
private val state: IssueRecordingState,
- private val traceurMessageSender: TraceurMessageSender,
@Assisted private val onStarted: Runnable,
) : SystemUIDialog.Delegate {
@@ -87,10 +86,6 @@
setNegativeButton(R.string.cancel) { _, _ -> }
setPositiveButton(R.string.qs_record_issue_start) { _, _ -> onStarted.run() }
}
- bgExecutor.execute {
- traceurMessageSender.onBoundToTraceur.add { traceurMessageSender.getTags(state) }
- traceurMessageSender.bindToTraceur(dialog.context)
- }
}
override fun createDialog(): SystemUIDialog = factory.create(this)
@@ -151,7 +146,7 @@
mediaProjectionMetricsLogger.notifyProjectionInitiated(
userTracker.userId,
- SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER
+ SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER,
)
if (!state.hasUserApprovedScreenRecording) {
@@ -189,7 +184,7 @@
CustomTraceSettingsDialogDelegate(
factory,
state.customTraceState,
- state.tagTitles
+ state.tagTitles,
) {
onMenuItemClickListener.onMenuItemClick(it)
}
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/TraceurConnection.kt b/packages/SystemUI/src/com/android/systemui/recordissue/TraceurConnection.kt
new file mode 100644
index 0000000..81529b3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/TraceurConnection.kt
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recordissue
+
+import android.content.ComponentName
+import android.content.Intent
+import android.net.Uri
+import android.os.Bundle
+import android.os.Handler
+import android.os.IBinder
+import android.os.Looper
+import android.os.Message
+import android.os.Messenger
+import android.util.Log
+import androidx.annotation.WorkerThread
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.recordissue.IssueRecordingState.Companion.TAG_TITLE_DELIMITER
+import com.android.systemui.settings.UserContextProvider
+import com.android.traceur.FileSender
+import com.android.traceur.MessageConstants
+import com.android.traceur.MessageConstants.TRACING_APP_ACTIVITY
+import com.android.traceur.MessageConstants.TRACING_APP_PACKAGE_NAME
+import com.android.traceur.TraceConfig
+import java.util.concurrent.CopyOnWriteArrayList
+import javax.inject.Inject
+
+private const val TAG = "TraceurConnection"
+
+class TraceurConnection
+private constructor(userContextProvider: UserContextProvider, private val bgLooper: Looper) :
+ UserAwareConnection(
+ userContextProvider,
+ Intent().setClassName(TRACING_APP_PACKAGE_NAME, TRACING_APP_ACTIVITY),
+ ) {
+
+ @SysUISingleton
+ class Provider
+ @Inject
+ constructor(
+ private val userContextProvider: UserContextProvider,
+ @Background private val bgLooper: Looper,
+ ) {
+ fun create() = TraceurConnection(userContextProvider, bgLooper)
+ }
+
+ val onBound: MutableList<Runnable> = CopyOnWriteArrayList(mutableListOf())
+
+ override fun onServiceConnected(className: ComponentName, service: IBinder) {
+ super.onServiceConnected(className, service)
+ onBound.forEach(Runnable::run)
+ onBound.clear()
+ }
+
+ @WorkerThread
+ fun startTracing(traceType: TraceConfig) {
+ val data =
+ Bundle().apply { putParcelable(MessageConstants.INTENT_EXTRA_TRACE_TYPE, traceType) }
+ sendMessage(MessageConstants.START_WHAT, data)
+ }
+
+ @WorkerThread fun stopTracing() = sendMessage(MessageConstants.STOP_WHAT)
+
+ @WorkerThread
+ fun shareTraces(screenRecord: Uri?) {
+ val replyHandler = Messenger(ShareFilesHandler(screenRecord, userContextProvider, bgLooper))
+ sendMessage(MessageConstants.SHARE_WHAT, replyTo = replyHandler)
+ }
+
+ @WorkerThread
+ fun getTags(state: IssueRecordingState) =
+ sendMessage(MessageConstants.TAGS_WHAT, replyTo = Messenger(TagsHandler(bgLooper, state)))
+
+ @WorkerThread
+ private fun sendMessage(what: Int, data: Bundle = Bundle(), replyTo: Messenger? = null) =
+ try {
+ val msg =
+ Message.obtain().apply {
+ this.what = what
+ this.data = data
+ this.replyTo = replyTo
+ }
+ binder?.send(msg) ?: onBound.add { binder!!.send(msg) }
+ } catch (e: Exception) {
+ Log.e(TAG, "failed to notify Traceur", e)
+ }
+}
+
+private class ShareFilesHandler(
+ private val screenRecord: Uri?,
+ private val userContextProvider: UserContextProvider,
+ looper: Looper,
+) : Handler(looper) {
+
+ override fun handleMessage(msg: Message) {
+ if (MessageConstants.SHARE_WHAT == msg.what) {
+ shareTraces(
+ msg.data.getParcelable(MessageConstants.EXTRA_PERFETTO, Uri::class.java),
+ msg.data.getParcelable(MessageConstants.EXTRA_WINSCOPE, Uri::class.java),
+ )
+ } else {
+ throw IllegalArgumentException("received unknown msg.what: " + msg.what)
+ }
+ }
+
+ private fun shareTraces(perfetto: Uri?, winscope: Uri?) {
+ val uris: ArrayList<Uri> =
+ ArrayList<Uri>().apply {
+ perfetto?.let { add(it) }
+ winscope?.let { add(it) }
+ screenRecord?.let { add(it) }
+ }
+ val fileSharingIntent =
+ FileSender.buildSendIntent(userContextProvider.userContext, uris)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT)
+ userContextProvider.userContext.startActivity(fileSharingIntent)
+ }
+}
+
+private class TagsHandler(looper: Looper, private val state: IssueRecordingState) :
+ Handler(looper) {
+
+ override fun handleMessage(msg: Message) {
+ if (MessageConstants.TAGS_WHAT == msg.what) {
+ val keys = msg.data.getStringArrayList(MessageConstants.BUNDLE_KEY_TAGS)
+ val values = msg.data.getStringArrayList(MessageConstants.BUNDLE_KEY_TAG_DESCRIPTIONS)
+ if (keys == null || values == null) {
+ throw IllegalArgumentException(
+ "Neither keys: $keys, nor values: $values can be null"
+ )
+ }
+ state.tagTitles =
+ keys.zip(values).map { it.first + TAG_TITLE_DELIMITER + it.second }.toSet()
+ } else {
+ throw IllegalArgumentException("received unknown msg.what: " + msg.what)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/TraceurMessageSender.kt b/packages/SystemUI/src/com/android/systemui/recordissue/TraceurMessageSender.kt
deleted file mode 100644
index 8bfd14a..0000000
--- a/packages/SystemUI/src/com/android/systemui/recordissue/TraceurMessageSender.kt
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recordissue
-
-import android.annotation.SuppressLint
-import android.content.ComponentName
-import android.content.Context
-import android.content.Intent
-import android.content.ServiceConnection
-import android.content.pm.PackageManager
-import android.net.Uri
-import android.os.Bundle
-import android.os.Handler
-import android.os.IBinder
-import android.os.Looper
-import android.os.Message
-import android.os.Messenger
-import android.util.Log
-import androidx.annotation.WorkerThread
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.recordissue.IssueRecordingState.Companion.TAG_TITLE_DELIMITER
-import com.android.traceur.FileSender
-import com.android.traceur.MessageConstants
-import com.android.traceur.TraceConfig
-import javax.inject.Inject
-
-private const val TAG = "TraceurMessageSender"
-
-@SysUISingleton
-class TraceurMessageSender @Inject constructor(@Background private val backgroundLooper: Looper) {
- private var binder: Messenger? = null
- private var isBound: Boolean = false
-
- val onBoundToTraceur = mutableListOf<Runnable>()
-
- private val traceurConnection =
- object : ServiceConnection {
- override fun onServiceConnected(className: ComponentName, service: IBinder) {
- binder = Messenger(service)
- isBound = true
- onBoundToTraceur.forEach(Runnable::run)
- onBoundToTraceur.clear()
- }
-
- override fun onServiceDisconnected(className: ComponentName) {
- binder = null
- isBound = false
- }
- }
-
- @SuppressLint("WrongConstant")
- @WorkerThread
- fun bindToTraceur(context: Context) {
- if (isBound) {
- // Binding needs to happen after the phone has been unlocked. The RecordIssueTile is
- // initialized before this happens though, so binding is placed at a later time, during
- // normal operations that can be repeated. This check avoids calling "bindService" 2x+
- return
- }
- try {
- val info =
- context.packageManager.getPackageInfo(
- MessageConstants.TRACING_APP_PACKAGE_NAME,
- PackageManager.MATCH_SYSTEM_ONLY
- )
- val intent =
- Intent().setClassName(info.packageName, MessageConstants.TRACING_APP_ACTIVITY)
- val flags =
- Context.BIND_AUTO_CREATE or
- Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE or
- Context.BIND_WAIVE_PRIORITY
- context.bindService(intent, traceurConnection, flags)
- } catch (e: Exception) {
- Log.e(TAG, "failed to bind to Traceur's service", e)
- }
- }
-
- @WorkerThread
- fun unbindFromTraceur(context: Context) {
- if (isBound) {
- context.unbindService(traceurConnection)
- }
- }
-
- @WorkerThread
- fun startTracing(traceType: TraceConfig) {
- val data =
- Bundle().apply { putParcelable(MessageConstants.INTENT_EXTRA_TRACE_TYPE, traceType) }
- notifyTraceur(MessageConstants.START_WHAT, data)
- }
-
- @WorkerThread fun stopTracing() = notifyTraceur(MessageConstants.STOP_WHAT)
-
- @WorkerThread
- fun shareTraces(context: Context, screenRecord: Uri?) {
- val replyHandler = Messenger(ShareFilesHandler(context, screenRecord, backgroundLooper))
- notifyTraceur(MessageConstants.SHARE_WHAT, replyTo = replyHandler)
- }
-
- @WorkerThread
- fun getTags(state: IssueRecordingState) {
- val replyHandler = Messenger(TagsHandler(backgroundLooper, state))
- notifyTraceur(MessageConstants.TAGS_WHAT, replyTo = replyHandler)
- }
-
- @WorkerThread
- private fun notifyTraceur(what: Int, data: Bundle = Bundle(), replyTo: Messenger? = null) {
- try {
- binder!!.send(
- Message.obtain().apply {
- this.what = what
- this.data = data
- this.replyTo = replyTo
- }
- )
- } catch (e: Exception) {
- Log.e(TAG, "failed to notify Traceur", e)
- }
- }
-
- private class ShareFilesHandler(
- private val context: Context,
- private val screenRecord: Uri?,
- looper: Looper,
- ) : Handler(looper) {
-
- override fun handleMessage(msg: Message) {
- if (MessageConstants.SHARE_WHAT == msg.what) {
- shareTraces(
- msg.data.getParcelable(MessageConstants.EXTRA_PERFETTO, Uri::class.java),
- msg.data.getParcelable(MessageConstants.EXTRA_WINSCOPE, Uri::class.java)
- )
- } else {
- throw IllegalArgumentException("received unknown msg.what: " + msg.what)
- }
- }
-
- private fun shareTraces(perfetto: Uri?, winscope: Uri?) {
- val uris: List<Uri> =
- mutableListOf<Uri>().apply {
- perfetto?.let { add(it) }
- winscope?.let { add(it) }
- screenRecord?.let { add(it) }
- }
- val fileSharingIntent =
- FileSender.buildSendIntent(context, uris)
- .addFlags(
- Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT
- )
- context.startActivity(fileSharingIntent)
- }
- }
-
- private class TagsHandler(looper: Looper, private val state: IssueRecordingState) :
- Handler(looper) {
-
- override fun handleMessage(msg: Message) {
- if (MessageConstants.TAGS_WHAT == msg.what) {
- val keys = msg.data.getStringArrayList(MessageConstants.BUNDLE_KEY_TAGS)
- val values =
- msg.data.getStringArrayList(MessageConstants.BUNDLE_KEY_TAG_DESCRIPTIONS)
- if (keys == null || values == null) {
- throw IllegalArgumentException(
- "Neither keys: $keys, nor values: $values can be null"
- )
- }
- state.tagTitles =
- keys.zip(values).map { it.first + TAG_TITLE_DELIMITER + it.second }.toSet()
- } else {
- throw IllegalArgumentException("received unknown msg.what: " + msg.what)
- }
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/UserAwareConnection.kt b/packages/SystemUI/src/com/android/systemui/recordissue/UserAwareConnection.kt
new file mode 100644
index 0000000..6aaa27d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/UserAwareConnection.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recordissue
+
+import android.annotation.SuppressLint
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.ServiceConnection
+import android.os.IBinder
+import android.os.Messenger
+import android.util.Log
+import androidx.annotation.VisibleForTesting
+import androidx.annotation.WorkerThread
+import com.android.systemui.settings.UserContextProvider
+
+private const val TAG = "UserAwareConnection"
+private const val BIND_FLAGS =
+ Context.BIND_AUTO_CREATE or
+ Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE or
+ Context.BIND_WAIVE_PRIORITY
+
+/** ServiceConnection class that can be used to keep an IntentService alive. */
+open class UserAwareConnection(
+ protected val userContextProvider: UserContextProvider,
+ private val intent: Intent,
+) : ServiceConnection {
+ @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) var binder: Messenger? = null
+ private var shouldUnBind = false
+
+ override fun onServiceConnected(className: ComponentName, service: IBinder) {
+ binder = Messenger(service)
+ }
+
+ override fun onServiceDisconnected(className: ComponentName) {
+ binder = null
+ }
+
+ @SuppressLint("WrongConstant")
+ @WorkerThread
+ fun doBind() {
+ if (shouldUnBind) {
+ // Binding needs to happen after the phone has been unlocked. The RecordIssueTile is
+ // initialized before this happens though, so binding is placed at a later time, during
+ // normal operations that can be repeated. This check avoids calling "bindService" 2x+
+ return
+ }
+ try {
+ shouldUnBind = userContextProvider.userContext.bindService(intent, this, BIND_FLAGS)
+ } catch (e: Exception) {
+ Log.e(TAG, "failed to bind to the service", e)
+ }
+ }
+
+ @WorkerThread
+ fun doUnBind() {
+ if (shouldUnBind) {
+ userContextProvider.userContext.unbindService(this)
+ shouldUnBind = false
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
index a89f752..4beec10 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
@@ -44,6 +44,7 @@
[
BouncerSceneModule::class,
CommunalSceneModule::class,
+ DreamSceneModule::class,
EmptySceneModule::class,
GoneSceneModule::class,
LockscreenSceneModule::class,
@@ -98,6 +99,7 @@
listOfNotNull(
Scenes.Gone,
Scenes.Communal,
+ Scenes.Dream,
Scenes.Lockscreen,
Scenes.Bouncer,
Scenes.QuickSettings.takeUnless { DualShade.isEnabled },
@@ -114,9 +116,10 @@
Scenes.Gone to 0,
Scenes.Lockscreen to 0,
Scenes.Communal to 1,
- Scenes.Shade to 2.takeUnless { DualShade.isEnabled },
- Scenes.QuickSettings to 3.takeUnless { DualShade.isEnabled },
- Scenes.Bouncer to 4,
+ Scenes.Dream to 2,
+ Scenes.Shade to 3.takeUnless { DualShade.isEnabled },
+ Scenes.QuickSettings to 4.takeUnless { DualShade.isEnabled },
+ Scenes.Bouncer to 5,
)
.filterValues { it != null }
.mapValues { checkNotNull(it.value) },
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scenes.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scenes.kt
index 82b4b1c..16492ef 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scenes.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scenes.kt
@@ -33,6 +33,9 @@
/** The communal scene shows the glanceable hub when device is locked and docked. */
@JvmField val Communal = SceneKey("communal")
+ /** The dream scene shows up when a dream activity is showing. */
+ @JvmField val Dream = SceneKey("dream")
+
/**
* "Gone" is not a real scene but rather the absence of scenes when we want to skip showing any
* content from the scene framework.
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt
index ea19020..f0f476e 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt
@@ -25,6 +25,7 @@
import android.view.WindowInsets
import android.widget.FrameLayout
import androidx.core.view.updateMargins
+import com.android.systemui.Flags
import com.android.systemui.compose.ComposeInitializer
import com.android.systemui.res.R
@@ -103,6 +104,8 @@
private fun applyMargins() {
val count = childCount
+ val hasFlagsEnabled = Flags.checkLockscreenGoneTransition()
+ var hasChildMarginUpdated = false
for (i in 0 until count) {
val child = getChildAt(i)
if (child.layoutParams is LayoutParams) {
@@ -113,10 +116,17 @@
layoutParams.leftMargin != leftInset)
) {
layoutParams.updateMargins(left = leftInset, right = rightInset)
- child.requestLayout()
+ hasChildMarginUpdated = true
+ if (!hasFlagsEnabled) {
+ child.requestLayout()
+ }
}
}
}
+ if (hasFlagsEnabled && hasChildMarginUpdated) {
+ // Request layout at once after all children's margins has updated
+ requestLayout()
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/HeadlessScreenshotHandler.kt b/packages/SystemUI/src/com/android/systemui/screenshot/HeadlessScreenshotHandler.kt
index 6730d2d..7b56688 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/HeadlessScreenshotHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/HeadlessScreenshotHandler.kt
@@ -49,7 +49,7 @@
override fun handleScreenshot(
screenshot: ScreenshotData,
finisher: Consumer<Uri?>,
- requestCallback: TakeScreenshotService.RequestCallback
+ requestCallback: TakeScreenshotService.RequestCallback,
) {
if (screenshot.type == WindowManager.TAKE_SCREENSHOT_FULLSCREEN) {
screenshot.bitmap = imageCapture.captureDisplay(screenshot.displayId, crop = null)
@@ -69,8 +69,8 @@
Executors.newSingleThreadExecutor(),
UUID.randomUUID(),
screenshot.bitmap,
- screenshot.getUserOrDefault(),
- screenshot.displayId
+ screenshot.userHandle,
+ screenshot.displayId,
)
future.addListener(
{
@@ -86,7 +86,7 @@
requestCallback.reportError()
}
},
- mainExecutor
+ mainExecutor,
)
}
@@ -98,11 +98,11 @@
.notifyScreenshotError(R.string.screenshot_failed_to_save_text)
} else {
uiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED, 0, screenshot.packageNameString)
- if (userManager.isManagedProfile(screenshot.getUserOrDefault().identifier)) {
+ if (userManager.isManagedProfile(screenshot.userHandle.identifier)) {
uiEventLogger.log(
ScreenshotEvent.SCREENSHOT_SAVED_TO_WORK_PROFILE,
0,
- screenshot.packageNameString
+ screenshot.packageNameString,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java
index 7724abd..e589600 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java
@@ -301,7 +301,7 @@
saveScreenshotInBackground(screenshot, requestId, finisher, result -> {
if (result.uri != null) {
ScreenshotSavedResult savedScreenshot = new ScreenshotSavedResult(
- result.uri, screenshot.getUserOrDefault(), result.timestamp);
+ result.uri, screenshot.getUserHandle(), result.timestamp);
mActionsController.setCompletedScreenshot(requestId, savedScreenshot);
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.kt
index 29208f8..a762d84 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.kt
@@ -97,12 +97,13 @@
private val window: ScreenshotWindow
private val actionExecutor: ActionExecutor
private val copyBroadcastReceiver: BroadcastReceiver
+ private val currentRequestCallbacks: MutableList<TakeScreenshotService.RequestCallback> =
+ mutableListOf()
private var screenshotSoundController: ScreenshotSoundController? = null
private var screenBitmap: Bitmap? = null
private var screenshotTakenInPortrait = false
private var screenshotAnimation: Animator? = null
- private var currentRequestCallback: TakeScreenshotService.RequestCallback? = null
private var packageName = ""
/** Tracks config changes that require re-creating UI */
@@ -169,8 +170,8 @@
requestCallback: TakeScreenshotService.RequestCallback,
) {
Assert.isMainThread()
+ screenshotHandler.resetTimeout()
- currentRequestCallback = requestCallback
if (screenshot.type == TAKE_SCREENSHOT_FULLSCREEN && screenshot.bitmap == null) {
val bounds = fullScreenRect
screenshot.bitmap = imageCapture.captureDisplay(display.displayId, bounds)
@@ -181,7 +182,7 @@
if (currentBitmap == null) {
Log.e(TAG, "handleScreenshot: Screenshot bitmap was null")
notificationController.notifyScreenshotError(R.string.screenshot_failed_to_capture_text)
- currentRequestCallback?.reportError()
+ requestCallback.reportError()
return
}
@@ -194,8 +195,10 @@
// User setup isn't complete, so we don't want to show any UI beyond a toast, as editing
// and sharing shouldn't be exposed to the user.
saveScreenshotAndToast(screenshot, finisher)
+ requestCallback.onFinish()
return
}
+ currentRequestCallbacks.add(requestCallback)
broadcastSender.sendBroadcast(
Intent(ClipboardOverlayController.SCREENSHOT_ACTION),
@@ -214,11 +217,7 @@
saveScreenshotInBackground(screenshot, requestId, finisher) { result ->
if (result.uri != null) {
val savedScreenshot =
- ScreenshotSavedResult(
- result.uri,
- screenshot.getUserOrDefault(),
- result.timestamp,
- )
+ ScreenshotSavedResult(result.uri, screenshot.userHandle, result.timestamp)
actionsController.setCompletedScreenshot(requestId, savedScreenshot)
}
}
@@ -235,7 +234,7 @@
window.setFocusable(true)
viewProxy.requestFocus()
- enqueueScrollCaptureRequest(requestId, screenshot.userHandle!!)
+ enqueueScrollCaptureRequest(requestId, screenshot.userHandle)
window.attachWindow()
@@ -267,7 +266,7 @@
private fun prepareViewForNewScreenshot(screenshot: ScreenshotData, oldPackageName: String?) {
window.whenWindowAttached {
- announcementResolver.getScreenshotAnnouncement(screenshot.userHandle!!.identifier) {
+ announcementResolver.getScreenshotAnnouncement(screenshot.userHandle.identifier) {
viewProxy.announceForAccessibility(it)
}
}
@@ -499,8 +498,8 @@
Log.d(TAG, "finishDismiss")
actionsController.endScreenshotSession()
scrollCaptureExecutor.close()
- currentRequestCallback?.onFinish()
- currentRequestCallback = null
+ currentRequestCallbacks.forEach { it.onFinish() }
+ currentRequestCallbacks.clear()
viewProxy.reset()
removeWindow()
screenshotHandler.cancelTimeout()
@@ -517,7 +516,7 @@
bgExecutor,
requestId,
screenshot.bitmap,
- screenshot.getUserOrDefault(),
+ screenshot.userHandle,
display.displayId,
)
future.addListener(
@@ -525,7 +524,7 @@
try {
val result = future.get()
Log.d(TAG, "Saved screenshot: $result")
- logScreenshotResultStatus(result.uri, screenshot.userHandle!!)
+ logScreenshotResultStatus(result.uri, screenshot.userHandle)
onResult.accept(result)
if (LogConfig.DEBUG_CALLBACK) {
Log.d(TAG, "finished bg processing, calling back with uri: ${result.uri}")
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt
index fb7c34f..2df1e8a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt
@@ -18,7 +18,7 @@
@ScreenshotType val type: Int,
@ScreenshotSource val source: Int,
/** UserHandle for the owner of the app being screenshotted, if known. */
- val userHandle: UserHandle?,
+ val userHandle: UserHandle,
/** ComponentName of the top-most app in the screenshot. */
val topComponent: ComponentName?,
var screenBounds: Rect?,
@@ -40,7 +40,7 @@
ScreenshotData(
type = request.type,
source = request.source,
- userHandle = if (request.userId >= 0) UserHandle.of(request.userId) else null,
+ userHandle = UserHandle.of(request.userId),
topComponent = request.topComponent,
screenBounds = request.boundsInScreen,
taskId = request.taskId,
@@ -51,7 +51,7 @@
@VisibleForTesting
fun forTesting(
- userHandle: UserHandle? = null,
+ userHandle: UserHandle = UserHandle.CURRENT,
source: Int = ScreenshotSource.SCREENSHOT_KEY_CHORD,
topComponent: ComponentName? = null,
bitmap: Bitmap? = null,
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
index ab8a953..a755746 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
@@ -28,6 +28,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.display.data.repository.FocusedDisplayRepository
import com.android.systemui.res.R
import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_CAPTURE_FAILED
import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER
@@ -83,6 +84,7 @@
private val uiEventLogger: UiEventLogger,
private val screenshotNotificationControllerFactory: ScreenshotNotificationsController.Factory,
private val headlessScreenshotHandler: HeadlessScreenshotHandler,
+ private val focusedDisplayRepository: FocusedDisplayRepository,
) : TakeScreenshotExecutor {
private val displays = displayRepository.displays
private var screenshotController: InteractiveScreenshotHandler? = null
@@ -216,14 +218,13 @@
?: error("Can't find default display")
// All other invocations use the focused display
- else -> focusedDisplay()
+ else ->
+ displayRepository.getDisplay(focusedDisplayRepository.focusedDisplayId.value)
+ ?: displayRepository.getDisplay(Display.DEFAULT_DISPLAY)
+ ?: error("Can't find default display")
}
}
- // TODO(b/367394043): Determine the focused display here.
- private suspend fun focusedDisplay() =
- displayRepository.getDisplay(Display.DEFAULT_DISPLAY) ?: error("Can't find default display")
-
/** Propagates the close system dialog signal to the ScreenshotController. */
override fun onCloseSystemDialogsReceived() {
if (screenshotController?.isPendingSharedTransition() == false) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt
index b3d5c9e..b67ad8a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt
@@ -92,7 +92,7 @@
updates.component,
updates.owner,
type.taskId,
- type.taskBounds
+ type.taskBounds,
)
is FullScreen ->
replaceWithScreenshot(
@@ -122,7 +122,7 @@
componentName = topMainRootTask?.topActivity ?: defaultComponent,
taskId = topMainRootTask?.taskId,
owner = defaultOwner,
- displayId = original.displayId
+ displayId = original.displayId,
)
}
@@ -141,14 +141,14 @@
userHandle = owner,
taskId = taskId,
topComponent = componentName,
- screenBounds = taskBounds
+ screenBounds = taskBounds,
)
}
private suspend fun replaceWithScreenshot(
original: ScreenshotData,
componentName: ComponentName?,
- owner: UserHandle?,
+ owner: UserHandle,
displayId: Int,
taskId: Int? = null,
): ScreenshotData {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt
index a4906c1..91efa0a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt
@@ -41,7 +41,7 @@
class ScreenshotAnimationController(
private val view: ScreenshotShelfView,
- private val viewModel: ScreenshotViewModel
+ private val viewModel: ScreenshotViewModel,
) {
private var animator: Animator? = null
private val screenshotPreview = view.requireViewById<ImageView>(R.id.screenshot_preview)
@@ -56,7 +56,7 @@
listOf<View>(
view.requireViewById(R.id.screenshot_preview_border),
view.requireViewById(R.id.screenshot_badge),
- view.requireViewById(R.id.screenshot_dismiss_button)
+ view.requireViewById(R.id.screenshot_dismiss_button),
)
private val fadeUI =
listOf<View>(
@@ -70,9 +70,11 @@
fun getEntranceAnimation(
bounds: Rect,
showFlash: Boolean,
- onRevealMilestone: () -> Unit
+ onRevealMilestone: () -> Unit,
): Animator {
val entranceAnimation = AnimatorSet()
+ view.alpha = 1f
+ view.translationX = 0f
val previewAnimator = getPreviewAnimator(bounds)
@@ -142,7 +144,7 @@
fun runLongScreenshotTransition(
destRect: Rect,
longScreenshot: ScrollCaptureController.LongScreenshot,
- onTransitionEnd: Runnable
+ onTransitionEnd: Runnable,
): Animator {
val animSet = AnimatorSet()
@@ -165,7 +167,7 @@
matrix.setScale(currentScale, currentScale)
matrix.postTranslate(
longScreenshot.left * currentScale,
- longScreenshot.top * currentScale
+ longScreenshot.top * currentScale,
)
scrollTransitionPreview.setImageMatrix(matrix)
val destinationScale: Float = destRect.width() / scrollTransitionPreview.width.toFloat()
@@ -315,7 +317,7 @@
}
private fun getAdjustedVelocity(requestedVelocity: Float?): Float {
- return if (requestedVelocity == null) {
+ return if (requestedVelocity == null || abs(requestedVelocity) < .005f) {
val isLTR = view.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_LTR
// dismiss to the left in LTR locales, to the right in RTL
if (isLTR) -MINIMUM_VELOCITY else MINIMUM_VELOCITY
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/SwipeGestureListener.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/SwipeGestureListener.kt
index 61d4489..ecb45e1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/SwipeGestureListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/SwipeGestureListener.kt
@@ -25,7 +25,7 @@
class SwipeGestureListener(
private val view: View,
private val onDismiss: (Float?) -> Unit,
- private val onCancel: () -> Unit
+ private val onCancel: () -> Unit,
) {
private val velocityTracker = VelocityTracker.obtain()
private val displayMetrics = view.resources.displayMetrics
@@ -54,9 +54,9 @@
onDismiss.invoke(xVelocity)
return true
} else {
- velocityTracker.clear()
onCancel.invoke()
}
+ velocityTracker.clear()
}
MotionEvent.ACTION_MOVE -> {
velocityTracker.addMovement(ev)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 5896659..2bff7c86 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -30,6 +30,7 @@
import static com.android.systemui.classifier.Classifier.GENERIC;
import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
import static com.android.systemui.classifier.Classifier.UNLOCK;
+import static com.android.systemui.keyguard.shared.model.KeyguardState.AOD;
import static com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING;
import static com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING_LOCKSCREEN_HOSTED;
import static com.android.systemui.keyguard.shared.model.KeyguardState.GONE;
@@ -1213,6 +1214,16 @@
}, mMainDispatcher);
}
+ if (MigrateClocksToBlueprint.isEnabled()) {
+ collectFlow(mView, mKeyguardTransitionInteractor.transition(
+ Edge.Companion.create(AOD, LOCKSCREEN)),
+ (TransitionStep step) -> {
+ if (step.getTransitionState() == TransitionState.FINISHED) {
+ updateExpandedHeightToMaxHeight();
+ }
+ }, mMainDispatcher);
+ }
+
// Ensures that flags are updated when an activity launches
collectFlow(mView,
mShadeAnimationInteractor.isLaunchingActivity(),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index e47952f..a79b78f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -58,6 +58,7 @@
import com.android.internal.statusbar.NotificationVisibility;
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.Dumpable;
+import com.android.systemui.Flags;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
@@ -813,11 +814,17 @@
private void notifyNotificationStateChanged() {
if (!Looper.getMainLooper().isCurrentThread()) {
- mMainExecutor.execute(() -> {
+ if (Flags.checkLockscreenGoneTransition()) {
for (NotificationStateChangedListener listener : mNotifStateChangedListeners) {
- listener.onNotificationStateChanged();
+ mMainExecutor.execute(listener::onNotificationStateChanged);
}
- });
+ } else {
+ mMainExecutor.execute(() -> {
+ for (NotificationStateChangedListener listener : mNotifStateChangedListeners) {
+ listener.onNotificationStateChanged();
+ }
+ });
+ }
} else {
for (NotificationStateChangedListener listener : mNotifStateChangedListeners) {
listener.onNotificationStateChanged();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS b/packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS
index 5558ab1..0a7f08d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS
@@ -2,11 +2,13 @@
# Bug component: 78010
-aioana@google.com
-aroederer@google.com
-iyz@google.com
jeffdq@google.com
juliacr@google.com
+
+aioana@google.com
+aroederer@google.com
+asc@google.com
+iyz@google.com
juliatuttle@google.com
kurucz@google.com
liuyining@google.com
@@ -15,4 +17,4 @@
valiiftime@google.com
yurilin@google.com
-per-file MediaNotificationProcessor.java = ethibodeau@google.com, asc@google.com
+per-file MediaNotificationProcessor.java = ethibodeau@google.com
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index cb133ec..e74ed8d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -68,11 +68,9 @@
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController;
import com.android.systemui.statusbar.notification.row.NotificationGuts;
-import com.android.systemui.statusbar.notification.row.data.repository.NotificationRowRepository;
import com.android.systemui.statusbar.notification.row.shared.HeadsUpStatusBarModel;
import com.android.systemui.statusbar.notification.row.shared.NotificationContentModel;
import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor;
-import com.android.systemui.statusbar.notification.row.shared.RichOngoingContentModel;
import com.android.systemui.statusbar.notification.stack.PriorityBucket;
import com.android.systemui.util.ListenerSet;
@@ -99,7 +97,7 @@
* At the moment, there are many things here that shouldn't be and vice-versa. Hopefully we can
* clean this up in the future.
*/
-public final class NotificationEntry extends ListEntry implements NotificationRowRepository {
+public final class NotificationEntry extends ListEntry {
private final String mKey;
private StatusBarNotification mSbn;
@@ -161,8 +159,6 @@
StateFlowKt.MutableStateFlow(null);
private final MutableStateFlow<CharSequence> mHeadsUpStatusBarTextPublic =
StateFlowKt.MutableStateFlow(null);
- private final MutableStateFlow<RichOngoingContentModel> mRichOngoingContentModel =
- StateFlowKt.MutableStateFlow(null);
// indicates when this entry's view was first attached to a window
// this value will reset when the view is completely removed from the shade (ie: filtered out)
@@ -969,12 +965,6 @@
return mHeadsUpStatusBarTextPublic;
}
- /** Gets the current RON content model, which may be null */
- @NonNull
- public StateFlow<RichOngoingContentModel> getRichOngoingContentModel() {
- return mRichOngoingContentModel;
- }
-
/**
* Sets the text to be displayed on the StatusBar, when this notification is the top pinned
* heads up, and its content is sensitive right now.
@@ -1069,7 +1059,6 @@
HeadsUpStatusBarModel headsUpStatusBarModel = contentModel.getHeadsUpStatusBarModel();
this.mHeadsUpStatusBarText.setValue(headsUpStatusBarModel.getPrivateText());
this.mHeadsUpStatusBarTextPublic.setValue(headsUpStatusBarModel.getPublicText());
- this.mRichOngoingContentModel.setValue(contentModel.getRichOngoingContentModel());
}
/** Information about a suggestion that is being edited. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
index 41419f3..8660cd1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
@@ -22,15 +22,18 @@
import androidx.annotation.VisibleForTesting;
import com.android.systemui.Dumpable;
+import com.android.systemui.Flags;
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.shared.model.Edge;
import com.android.systemui.keyguard.shared.model.KeyguardState;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.scene.shared.flag.SceneContainerFlag;
+import com.android.systemui.scene.shared.model.Scenes;
import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor;
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
@@ -85,6 +88,7 @@
private boolean mNotifPanelLaunchingActivity;
private boolean mCommunalShowing = false;
private boolean mLockscreenShowing = false;
+ private boolean mLockscreenInGoneTransition = false;
private boolean mPipelineRunAllowed;
private boolean mReorderingAllowed;
@@ -158,6 +162,13 @@
KeyguardState.LOCKSCREEN),
this::onLockscreenKeyguardStateTransitionValueChanged);
}
+ if (Flags.checkLockscreenGoneTransition()) {
+ mJavaAdapter.alwaysCollectFlow(mKeyguardTransitionInteractor.isInTransition(
+ Edge.create(KeyguardState.LOCKSCREEN, Scenes.Gone),
+ Edge.create(KeyguardState.LOCKSCREEN, KeyguardState.GONE)),
+ this::onLockscreenInGoneTransitionChanged);
+ }
+
pipeline.setVisualStabilityManager(mNotifStabilityManager);
}
@@ -239,7 +250,9 @@
private void updateAllowedStates(String field, boolean value) {
boolean wasPipelineRunAllowed = mPipelineRunAllowed;
boolean wasReorderingAllowed = mReorderingAllowed;
- mPipelineRunAllowed = !isPanelCollapsingOrLaunchingActivity();
+ // No need to run notification pipeline when the lockscreen is in fading animation.
+ mPipelineRunAllowed = !(isPanelCollapsingOrLaunchingActivity()
+ || (Flags.checkLockscreenGoneTransition() && mLockscreenInGoneTransition));
mReorderingAllowed = isReorderingAllowed();
if (wasPipelineRunAllowed != mPipelineRunAllowed
|| wasReorderingAllowed != mReorderingAllowed) {
@@ -330,7 +343,6 @@
updateAllowedStates("fullyDozed", fullyDozed);
}
};
-
final WakefulnessLifecycle.Observer mWakefulnessObserver = new WakefulnessLifecycle.Observer() {
@Override
public void onFinishedGoingToSleep() {
@@ -353,6 +365,9 @@
pw.println("pipelineRunAllowed: " + mPipelineRunAllowed);
pw.println(" notifPanelCollapsing: " + mNotifPanelCollapsing);
pw.println(" launchingNotifActivity: " + mNotifPanelLaunchingActivity);
+ if (Flags.checkLockscreenGoneTransition()) {
+ pw.println(" lockscreenInGoneTransition: " + mLockscreenInGoneTransition);
+ }
pw.println("reorderingAllowed: " + mReorderingAllowed);
pw.println(" sleepy: " + mSleepy);
pw.println(" fullyDozed: " + mFullyDozed);
@@ -401,4 +416,15 @@
mLockscreenShowing = isShowing;
updateAllowedStates("lockscreenShowing", isShowing);
}
+
+ private void onLockscreenInGoneTransitionChanged(boolean inGoneTransition) {
+ if (!Flags.checkLockscreenGoneTransition()) {
+ return;
+ }
+ if (inGoneTransition == mLockscreenInGoneTransition) {
+ return;
+ }
+ mLockscreenInGoneTransition = inGoneTransition;
+ updateAllowedStates("lockscreenInGoneTransition", mLockscreenInGoneTransition);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
index aa203d7..e25127e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
@@ -39,10 +39,10 @@
@Inject
constructor(
private val headsUpRepository: HeadsUpRepository,
- private val faceAuthInteractor: DeviceEntryFaceAuthInteractor,
- private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
- private val notificationsKeyguardInteractor: NotificationsKeyguardInteractor,
- private val shadeInteractor: ShadeInteractor,
+ faceAuthInteractor: DeviceEntryFaceAuthInteractor,
+ keyguardTransitionInteractor: KeyguardTransitionInteractor,
+ notificationsKeyguardInteractor: NotificationsKeyguardInteractor,
+ shadeInteractor: ShadeInteractor,
) {
/** The top-ranked heads up row, regardless of pinned state */
@@ -56,8 +56,7 @@
}
.distinctUntilChanged()
- /** Set of currently pinned top-level heads up rows to be displayed. */
- val pinnedHeadsUpRows: Flow<Set<HeadsUpRowKey>> by lazy {
+ private val activeHeadsUpRows: Flow<Set<Pair<HeadsUpRowKey, Boolean>>> by lazy {
if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
flowOf(emptySet())
} else {
@@ -67,9 +66,7 @@
repositories.map { repo ->
repo.isPinned.map { isPinned -> repo to isPinned }
}
- combine(toCombine) { pairs ->
- pairs.filter { (_, isPinned) -> isPinned }.map { (repo, _) -> repo }.toSet()
- }
+ combine(toCombine) { pairs -> pairs.toSet() }
} else {
// if the set is empty, there are no flows to combine
flowOf(emptySet())
@@ -78,6 +75,26 @@
}
}
+ /** Set of currently active top-level heads up rows to be displayed. */
+ val activeHeadsUpRowKeys: Flow<Set<HeadsUpRowKey>> by lazy {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
+ flowOf(emptySet())
+ } else {
+ activeHeadsUpRows.map { it.map { (repo, _) -> repo }.toSet() }
+ }
+ }
+
+ /** Set of currently pinned top-level heads up rows to be displayed. */
+ val pinnedHeadsUpRowKeys: Flow<Set<HeadsUpRowKey>> by lazy {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
+ flowOf(emptySet())
+ } else {
+ activeHeadsUpRows.map {
+ it.filter { (_, isPinned) -> isPinned }.map { (repo, _) -> repo }.toSet()
+ }
+ }
+ }
+
/** Are there any pinned heads up rows to display? */
val hasPinnedRows: Flow<Boolean> by lazy {
if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModel.kt
index 8c8f200..695e088 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModel.kt
@@ -18,6 +18,8 @@
import android.content.Context
import android.icu.text.MessageFormat
+import com.android.app.tracing.coroutines.flow.flowOn
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dump.DumpManager
import com.android.systemui.modes.shared.ModesUi
import com.android.systemui.res.R
@@ -32,6 +34,7 @@
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import java.util.Locale
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -50,11 +53,16 @@
zenModeInteractor: ZenModeInteractor,
seenNotificationsInteractor: SeenNotificationsInteractor,
notificationSettingsInteractor: NotificationSettingsInteractor,
+ @Background bgDispatcher: CoroutineDispatcher,
dumpManager: DumpManager,
) : FlowDumperImpl(dumpManager) {
val areNotificationsHiddenInShade: Flow<Boolean> by lazy {
if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
flowOf(false)
+ } else if (ModesEmptyShadeFix.isEnabled) {
+ zenModeInteractor.areNotificationsHiddenInShade
+ .dumpWhileCollecting("areNotificationsHiddenInShade")
+ .flowOn(bgDispatcher)
} else {
zenModeInteractor.areNotificationsHiddenInShade.dumpWhileCollecting(
"areNotificationsHiddenInShade"
@@ -80,31 +88,33 @@
// recommended architecture, and making it so it reacts to changes for the new Modes.
// The former does not depend on the modes flags being on, but the latter does.
if (ModesUi.isEnabled) {
- zenModeInteractor.modesHidingNotifications.map { modes ->
- // Create a string that is either "No notifications" if no modes are filtering
- // them out, or something like "Notifications paused by SomeMode" otherwise.
- val msgFormat =
- MessageFormat(
- context.getString(R.string.modes_suppressing_shade_text),
- Locale.getDefault(),
- )
- val count = modes.count()
- val args: MutableMap<String, Any> = HashMap()
- args["count"] = count
- if (count >= 1) {
- args["mode"] = modes[0].name
+ zenModeInteractor.modesHidingNotifications.map { modes ->
+ // Create a string that is either "No notifications" if no modes are
+ // filtering
+ // them out, or something like "Notifications paused by SomeMode" otherwise.
+ val msgFormat =
+ MessageFormat(
+ context.getString(R.string.modes_suppressing_shade_text),
+ Locale.getDefault(),
+ )
+ val count = modes.count()
+ val args: MutableMap<String, Any> = HashMap()
+ args["count"] = count
+ if (count >= 1) {
+ args["mode"] = modes[0].name
+ }
+ msgFormat.format(args)
}
- msgFormat.format(args)
- }
- } else {
- areNotificationsHiddenInShade.map { areNotificationsHiddenInShade ->
- if (areNotificationsHiddenInShade) {
- context.getString(R.string.dnd_suppressing_shade_text)
- } else {
- context.getString(R.string.empty_shade_text)
+ } else {
+ areNotificationsHiddenInShade.map { areNotificationsHiddenInShade ->
+ if (areNotificationsHiddenInShade) {
+ context.getString(R.string.dnd_suppressing_shade_text)
+ } else {
+ context.getString(R.string.empty_shade_text)
+ }
}
}
- }
+ .flowOn(bgDispatcher)
}
}
@@ -120,23 +130,24 @@
val onClick: Flow<SettingsIntent> by lazy {
ModesEmptyShadeFix.assertInNewMode()
combine(
- zenModeInteractor.modesHidingNotifications,
- notificationSettingsInteractor.isNotificationHistoryEnabled,
- ) { modes, isNotificationHistoryEnabled ->
- if (modes.isNotEmpty()) {
- if (modes.size == 1) {
- SettingsIntent.forModeSettings(modes[0].id)
+ zenModeInteractor.modesHidingNotifications,
+ notificationSettingsInteractor.isNotificationHistoryEnabled,
+ ) { modes, isNotificationHistoryEnabled ->
+ if (modes.isNotEmpty()) {
+ if (modes.size == 1) {
+ SettingsIntent.forModeSettings(modes[0].id)
+ } else {
+ SettingsIntent.forModesSettings()
+ }
} else {
- SettingsIntent.forModesSettings()
- }
- } else {
- if (isNotificationHistoryEnabled) {
- SettingsIntent.forNotificationHistory()
- } else {
- SettingsIntent.forNotificationSettings()
+ if (isNotificationHistoryEnabled) {
+ SettingsIntent.forNotificationHistory()
+ } else {
+ SettingsIntent.forNotificationSettings()
+ }
}
}
- }
+ .flowOn(bgDispatcher)
}
@AssistedFactory
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 48c974a..9166e7e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -76,8 +76,6 @@
import com.android.systemui.util.Compile;
import com.android.systemui.util.DumpUtilsKt;
-import kotlinx.coroutines.DisposableHandle;
-
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
@@ -116,10 +114,6 @@
@VisibleForTesting
protected HybridNotificationView mSingleLineView;
- @Nullable public DisposableHandle mContractedBinderHandle;
- @Nullable public DisposableHandle mExpandedBinderHandle;
- @Nullable public DisposableHandle mHeadsUpBinderHandle;
-
private RemoteInputView mExpandedRemoteInput;
private RemoteInputView mHeadsUpRemoteInput;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
index c342bcd..b166def 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
@@ -46,9 +46,6 @@
import com.android.systemui.statusbar.notification.ConversationNotificationProcessor
import com.android.systemui.statusbar.notification.InflationException
import com.android.systemui.statusbar.notification.collection.NotificationEntry
-import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.InflatedContentViewHolder
-import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.KeepExistingView
-import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.NullContentView
import com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED
import com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_EXPANDED
import com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP
@@ -71,7 +68,6 @@
import com.android.systemui.statusbar.notification.row.shared.NewRemoteViews
import com.android.systemui.statusbar.notification.row.shared.NotificationContentModel
import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor
-import com.android.systemui.statusbar.notification.row.shared.RichOngoingContentModel
import com.android.systemui.statusbar.notification.row.ui.viewbinder.SingleLineViewBinder
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper
import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer
@@ -95,8 +91,6 @@
private val remoteViewCache: NotifRemoteViewCache,
private val remoteInputManager: NotificationRemoteInputManager,
private val conversationProcessor: ConversationNotificationProcessor,
- private val ronExtractor: RichOngoingNotificationContentExtractor,
- private val ronInflater: RichOngoingNotificationViewInflater,
@NotifInflation private val inflationExecutor: Executor,
private val smartReplyStateInflater: SmartReplyStateInflater,
private val notifLayoutInflaterFactoryProvider: NotifLayoutInflaterFactory.Provider,
@@ -144,8 +138,6 @@
remoteViewCache,
entry,
conversationProcessor,
- ronExtractor,
- ronInflater,
row,
bindParams.isMinimized,
bindParams.usesIncreasedHeight,
@@ -190,7 +182,6 @@
notifLayoutInflaterFactoryProvider = notifLayoutInflaterFactoryProvider,
headsUpStyleProvider = headsUpStyleProvider,
conversationProcessor = conversationProcessor,
- ronExtractor = ronExtractor,
logger = logger,
)
inflateSmartReplyViews(
@@ -282,22 +273,16 @@
when (inflateFlag) {
FLAG_CONTENT_VIEW_CONTRACTED ->
row.privateLayout.performWhenContentInactive(VISIBLE_TYPE_CONTRACTED) {
- row.privateLayout.mContractedBinderHandle?.dispose()
- row.privateLayout.mContractedBinderHandle = null
row.privateLayout.setContractedChild(null)
remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED)
}
FLAG_CONTENT_VIEW_EXPANDED ->
row.privateLayout.performWhenContentInactive(VISIBLE_TYPE_EXPANDED) {
- row.privateLayout.mExpandedBinderHandle?.dispose()
- row.privateLayout.mExpandedBinderHandle = null
row.privateLayout.setExpandedChild(null)
remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED)
}
FLAG_CONTENT_VIEW_HEADS_UP ->
row.privateLayout.performWhenContentInactive(VISIBLE_TYPE_HEADSUP) {
- row.privateLayout.mHeadsUpBinderHandle?.dispose()
- row.privateLayout.mHeadsUpBinderHandle = null
row.privateLayout.setHeadsUpChild(null)
remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP)
row.privateLayout.setHeadsUpInflatedSmartReplies(null)
@@ -378,8 +363,6 @@
private val remoteViewCache: NotifRemoteViewCache,
private val entry: NotificationEntry,
private val conversationProcessor: ConversationNotificationProcessor,
- private val ronExtractor: RichOngoingNotificationContentExtractor,
- private val ronInflater: RichOngoingNotificationViewInflater,
private val row: ExpandableNotificationRow,
private val isMinimized: Boolean,
private val usesIncreasedHeight: Boolean,
@@ -459,7 +442,6 @@
notifLayoutInflaterFactoryProvider = notifLayoutInflaterFactoryProvider,
headsUpStyleProvider = headsUpStyleProvider,
conversationProcessor = conversationProcessor,
- ronExtractor = ronExtractor,
logger = logger
)
logger.logAsyncTaskProgress(
@@ -506,90 +488,6 @@
}
}
- val richOngoingContentModel = inflationProgress.contentModel.richOngoingContentModel
-
- if (
- richOngoingContentModel != null &&
- reInflateFlags and CONTENT_VIEWS_TO_CREATE_RICH_ONGOING != 0
- ) {
- logger.logAsyncTaskProgress(entry, "inflating RON view")
- val inflateContractedView = reInflateFlags and FLAG_CONTENT_VIEW_CONTRACTED != 0
- val inflateExpandedView = reInflateFlags and FLAG_CONTENT_VIEW_EXPANDED != 0
- val inflateHeadsUpView = reInflateFlags and FLAG_CONTENT_VIEW_HEADS_UP != 0
-
- inflationProgress.contractedRichOngoingNotificationViewHolder =
- if (inflateContractedView) {
- ronInflater.inflateView(
- contentModel = richOngoingContentModel,
- existingView = row.privateLayout.contractedChild,
- entry = entry,
- systemUiContext = context,
- parentView = row.privateLayout,
- viewType = RichOngoingNotificationViewType.Contracted
- )
- } else {
- if (
- ronInflater.canKeepView(
- contentModel = richOngoingContentModel,
- existingView = row.privateLayout.contractedChild,
- viewType = RichOngoingNotificationViewType.Contracted
- )
- ) {
- KeepExistingView
- } else {
- NullContentView
- }
- }
-
- inflationProgress.expandedRichOngoingNotificationViewHolder =
- if (inflateExpandedView) {
- ronInflater.inflateView(
- contentModel = richOngoingContentModel,
- existingView = row.privateLayout.expandedChild,
- entry = entry,
- systemUiContext = context,
- parentView = row.privateLayout,
- viewType = RichOngoingNotificationViewType.Expanded
- )
- } else {
- if (
- ronInflater.canKeepView(
- contentModel = richOngoingContentModel,
- existingView = row.privateLayout.expandedChild,
- viewType = RichOngoingNotificationViewType.Expanded
- )
- ) {
- KeepExistingView
- } else {
- NullContentView
- }
- }
-
- inflationProgress.headsUpRichOngoingNotificationViewHolder =
- if (inflateHeadsUpView) {
- ronInflater.inflateView(
- contentModel = richOngoingContentModel,
- existingView = row.privateLayout.headsUpChild,
- entry = entry,
- systemUiContext = context,
- parentView = row.privateLayout,
- viewType = RichOngoingNotificationViewType.HeadsUp
- )
- } else {
- if (
- ronInflater.canKeepView(
- contentModel = richOngoingContentModel,
- existingView = row.privateLayout.headsUpChild,
- viewType = RichOngoingNotificationViewType.HeadsUp
- )
- ) {
- KeepExistingView
- } else {
- NullContentView
- }
- }
- }
-
logger.logAsyncTaskProgress(entry, "getting row image resolver (on wrong thread!)")
val imageResolver = row.imageResolver
// wait for image resolver to finish preloading
@@ -695,9 +593,6 @@
var inflatedSmartReplyState: InflatedSmartReplyState? = null
var expandedInflatedSmartReplies: InflatedSmartReplyViewHolder? = null
var headsUpInflatedSmartReplies: InflatedSmartReplyViewHolder? = null
- var contractedRichOngoingNotificationViewHolder: ContentViewInflationResult? = null
- var expandedRichOngoingNotificationViewHolder: ContentViewInflationResult? = null
- var headsUpRichOngoingNotificationViewHolder: ContentViewInflationResult? = null
// Inflated SingleLineView that lacks the UI State
var inflatedSingleLineView: HybridNotificationView? = null
@@ -734,7 +629,6 @@
val inflateHeadsUp =
(reInflateFlags and FLAG_CONTENT_VIEW_HEADS_UP != 0 &&
result.remoteViews.headsUp != null)
-
if (inflateContracted || inflateExpanded || inflateHeadsUp) {
logger.logAsyncTaskProgress(entry, "inflating contracted smart reply state")
result.inflatedSmartReplyState = inflater.inflateSmartReplyState(entry)
@@ -776,7 +670,6 @@
notifLayoutInflaterFactoryProvider: NotifLayoutInflaterFactory.Provider,
headsUpStyleProvider: HeadsUpStyleProvider,
conversationProcessor: ConversationNotificationProcessor,
- ronExtractor: RichOngoingNotificationContentExtractor,
logger: NotificationRowContentBinderLogger
): InflationProgress {
// process conversations and extract the messaging style
@@ -785,24 +678,9 @@
conversationProcessor.processNotification(entry, builder, logger)
} else null
- val richOngoingContentModel =
- if (reInflateFlags and CONTENT_VIEWS_TO_CREATE_RICH_ONGOING != 0) {
- ronExtractor.extractContentModel(
- entry = entry,
- builder = builder,
- systemUIContext = systemUIContext,
- packageContext = packageContext
- )
- } else {
- // if we're not re-inflating any RON views, make sure the model doesn't change
- entry.richOngoingContentModel.value
- }
-
- val remoteViewsFlags = getRemoteViewsFlags(reInflateFlags, richOngoingContentModel)
-
val remoteViews =
createRemoteViews(
- reInflateFlags = remoteViewsFlags,
+ reInflateFlags = reInflateFlags,
builder = builder,
isMinimized = isMinimized,
usesIncreasedHeight = usesIncreasedHeight,
@@ -850,7 +728,6 @@
headsUpStatusBarModel = headsUpStatusBarModel,
singleLineViewModel = singleLineViewModel,
publicSingleLineViewModel = publicSingleLineViewModel,
- richOngoingContentModel = richOngoingContentModel,
)
return InflationProgress(
@@ -1506,31 +1383,11 @@
}
logger.logAsyncTaskProgress(entry, "finishing")
- // before updating the content model, stop existing binding if necessary
- if (result.contractedRichOngoingNotificationViewHolder.shouldDisposeViewBinder()) {
- row.privateLayout.mContractedBinderHandle?.dispose()
- row.privateLayout.mContractedBinderHandle = null
- }
-
- if (result.expandedRichOngoingNotificationViewHolder.shouldDisposeViewBinder()) {
- row.privateLayout.mExpandedBinderHandle?.dispose()
- row.privateLayout.mExpandedBinderHandle = null
- }
-
- if (result.headsUpRichOngoingNotificationViewHolder.shouldDisposeViewBinder()) {
- row.privateLayout.mHeadsUpBinderHandle?.dispose()
- row.privateLayout.mHeadsUpBinderHandle = null
- }
-
- // set the content model after disposal and before setting new rich ongoing view
entry.setContentModel(result.contentModel)
result.inflatedSmartReplyState?.let { row.privateLayout.setInflatedSmartReplyState(it) }
- // set normal remote views (skipping rich ongoing states when that model exists)
- val remoteViewsFlags =
- getRemoteViewsFlags(reInflateFlags, result.contentModel.richOngoingContentModel)
setContentViewsFromRemoteViews(
- remoteViewsFlags,
+ reInflateFlags,
entry,
remoteViewCache,
result,
@@ -1538,7 +1395,6 @@
isMinimized,
)
- // set single line view
if (
AsyncHybridViewInflation.isEnabled &&
reInflateFlags and FLAG_CONTENT_VIEW_SINGLE_LINE != 0
@@ -1563,55 +1419,6 @@
}
}
- val hasRichOngoingViewHolder =
- result.contractedRichOngoingNotificationViewHolder != null ||
- result.expandedRichOngoingNotificationViewHolder != null ||
- result.headsUpRichOngoingNotificationViewHolder != null
-
- if (hasRichOngoingViewHolder) {
- // after updating the content model, set the view, then start the new binder
- result.contractedRichOngoingNotificationViewHolder?.let { contractedViewHolder ->
- if (contractedViewHolder is InflatedContentViewHolder) {
- row.privateLayout.contractedChild = contractedViewHolder.view
- row.privateLayout.mContractedBinderHandle =
- contractedViewHolder.binder.setupContentViewBinder()
- } else if (contractedViewHolder == NullContentView) {
- row.privateLayout.contractedChild = null
- }
- }
-
- result.expandedRichOngoingNotificationViewHolder?.let { expandedViewHolder ->
- if (expandedViewHolder is InflatedContentViewHolder) {
- row.privateLayout.expandedChild = expandedViewHolder.view
- row.privateLayout.mExpandedBinderHandle =
- expandedViewHolder.binder.setupContentViewBinder()
- } else if (expandedViewHolder == NullContentView) {
- row.privateLayout.expandedChild = null
- }
- }
-
- result.headsUpRichOngoingNotificationViewHolder?.let { headsUpViewHolder ->
- if (headsUpViewHolder is InflatedContentViewHolder) {
- row.privateLayout.headsUpChild = headsUpViewHolder.view
- row.privateLayout.mHeadsUpBinderHandle =
- headsUpViewHolder.binder.setupContentViewBinder()
- } else if (headsUpViewHolder == NullContentView) {
- row.privateLayout.headsUpChild = null
- }
- }
-
- // clean remoteViewCache when we don't keep existing views.
- remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED)
- remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED)
- remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP)
-
- // Since RONs don't support smart reply, remove them from HUNs and Expanded.
- row.privateLayout.setExpandedInflatedSmartReplies(null)
- row.privateLayout.setHeadsUpInflatedSmartReplies(null)
-
- row.setExpandable(row.privateLayout.expandedChild != null)
- }
-
Trace.endAsyncSection(APPLY_TRACE_METHOD, System.identityHashCode(row))
endListener?.onAsyncInflationFinished(entry)
return true
@@ -1775,21 +1582,6 @@
!oldView.hasFlags(RemoteViews.FLAG_REAPPLY_DISALLOWED)
}
- @InflationFlag
- private fun getRemoteViewsFlags(
- @InflationFlag reInflateFlags: Int,
- richOngoingContentModel: RichOngoingContentModel?
- ): Int =
- if (richOngoingContentModel != null) {
- reInflateFlags and CONTENT_VIEWS_TO_CREATE_RICH_ONGOING.inv()
- } else {
- reInflateFlags
- }
-
- @InflationFlag
- private const val CONTENT_VIEWS_TO_CREATE_RICH_ONGOING =
- FLAG_CONTENT_VIEW_CONTRACTED or FLAG_CONTENT_VIEW_EXPANDED or FLAG_CONTENT_VIEW_HEADS_UP
-
private const val ASYNC_TASK_TRACE_METHOD =
"NotificationRowContentBinderImpl.AsyncInflationTask"
private const val APPLY_TRACE_METHOD = "NotificationRowContentBinderImpl#apply"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
index c630c4d..84f2f66 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
@@ -18,8 +18,6 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor;
-import com.android.systemui.statusbar.notification.row.shared.RichOngoingNotificationFlag;
-import com.android.systemui.statusbar.notification.row.ui.viewmodel.RichOngoingViewModelComponent;
import dagger.Binds;
import dagger.Module;
@@ -30,7 +28,7 @@
/**
* Dagger Module containing notification row and view inflation implementations.
*/
-@Module(subcomponents = {RichOngoingViewModelComponent.class})
+@Module
public abstract class NotificationRowModule {
/**
@@ -49,25 +47,6 @@
}
}
- /** Provides ron content model extractor. */
- @Provides
- @SysUISingleton
- public static RichOngoingNotificationContentExtractor provideRonContentExtractor(
- Provider<RichOngoingNotificationContentExtractorImpl> realImpl
- ) {
- if (RichOngoingNotificationFlag.isEnabled()) {
- return realImpl.get();
- } else {
- return new NoOpRichOngoingNotificationContentExtractor();
- }
- }
-
- /** Provides ron view inflater. */
- @Binds
- @SysUISingleton
- public abstract RichOngoingNotificationViewInflater provideRonViewInflater(
- RichOngoingNotificationViewInflaterImpl impl);
-
/**
* Provides notification remote view cache instance.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt
deleted file mode 100644
index ec5ebc36..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt
+++ /dev/null
@@ -1,218 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.row
-
-import android.app.Notification
-import android.app.PendingIntent
-import android.content.Context
-import android.util.Log
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.notification.collection.NotificationEntry
-import com.android.systemui.statusbar.notification.row.shared.EnRouteContentModel
-import com.android.systemui.statusbar.notification.row.shared.IconModel
-import com.android.systemui.statusbar.notification.row.shared.RichOngoingContentModel
-import com.android.systemui.statusbar.notification.row.shared.RichOngoingNotificationFlag
-import com.android.systemui.statusbar.notification.row.shared.TimerContentModel
-import java.time.Duration
-import java.time.LocalDate
-import java.time.LocalDateTime
-import java.time.LocalTime
-import java.time.ZoneId
-import javax.inject.Inject
-
-/**
- * Interface which provides a [RichOngoingContentModel] for a given [Notification] when one is
- * applicable to the given style.
- */
-interface RichOngoingNotificationContentExtractor {
- fun extractContentModel(
- entry: NotificationEntry,
- builder: Notification.Builder,
- systemUIContext: Context,
- packageContext: Context,
- ): RichOngoingContentModel?
-}
-
-class NoOpRichOngoingNotificationContentExtractor : RichOngoingNotificationContentExtractor {
- override fun extractContentModel(
- entry: NotificationEntry,
- builder: Notification.Builder,
- systemUIContext: Context,
- packageContext: Context,
- ): RichOngoingContentModel? = null
-}
-
-@SysUISingleton
-class RichOngoingNotificationContentExtractorImpl @Inject constructor() :
- RichOngoingNotificationContentExtractor {
-
- init {
- /* check if */ RichOngoingNotificationFlag.isUnexpectedlyInLegacyMode()
- }
-
- override fun extractContentModel(
- entry: NotificationEntry,
- builder: Notification.Builder,
- systemUIContext: Context,
- packageContext: Context,
- ): RichOngoingContentModel? {
- val sbn = entry.sbn
- val notification = sbn.notification
- val icon = IconModel(notification.smallIcon)
-
- try {
- return if (sbn.packageName == "com.google.android.deskclock") {
- when (notification.channelId) {
- "Timers v2" -> {
- parseTimerNotification(notification, icon)
- }
- "Stopwatch v2" -> {
- Log.i("RONs", "Can't process stopwatch yet")
- null
- }
- else -> {
- Log.i("RONs", "Can't process channel '${notification.channelId}'")
- null
- }
- }
- } else if (builder.style is Notification.ProgressStyle) {
- parseEnRouteNotification(notification, icon)
- } else null
- } catch (e: Exception) {
- Log.e("RONs", "Error parsing RON", e)
- return null
- }
- }
-
- /**
- * FOR PROTOTYPING ONLY: create a RON TimerContentModel using the time information available
- * inside the sortKey of the clock app's timer notifications.
- */
- private fun parseTimerNotification(
- notification: Notification,
- icon: IconModel,
- ): TimerContentModel {
- // sortKey=1 0|↺7|RUNNING|▶16:21:58.523|Σ0:05:00|Δ0:00:03|⏳0:04:57
- // sortKey=1 0|↺7|PAUSED|Σ0:05:00|Δ0:04:54|⏳0:00:06
- // sortKey=1 1|↺7|RUNNING|▶16:30:28.433|Σ0:04:05|Δ0:00:06|⏳0:03:59
- // sortKey=1 0|↺7|RUNNING|▶16:36:18.350|Σ0:05:00|Δ0:01:42|⏳0:03:18
- // sortKey=1 2|↺7|RUNNING|▶16:38:37.816|Σ0:02:00|Δ0:01:09|⏳0:00:51
- // ▶ = "current" time (when updated)
- // Σ = total time
- // Δ = time elapsed
- // ⏳ = time remaining
- val sortKey = notification.sortKey
- val (_, _, state, extra) = sortKey.split("|", limit = 4)
- return when (state) {
- "PAUSED" -> {
- val (total, _, remaining) = extra.split("|")
- val timeRemaining = parseTimeDelta(remaining)
- TimerContentModel(
- icon = icon,
- // TODO: b/352142761 - define and use a string resource rather than " Timer".
- // (The UX isn't final so using " Timer" for now).
- name = total.replace("Σ", "") + " Timer",
- state =
- TimerContentModel.TimerState.Paused(
- timeRemaining = timeRemaining,
- resumeIntent = notification.findStartIntent(),
- addMinuteAction = notification.findAddMinuteAction(),
- resetAction = notification.findResetAction(),
- ),
- )
- }
- "RUNNING" -> {
- val (current, total, _, remaining) = extra.split("|")
- val finishTime = parseCurrentTime(current) + parseTimeDelta(remaining).toMillis()
- TimerContentModel(
- icon = icon,
- // TODO: b/352142761 - define and use a string resource rather than " Timer".
- // (The UX isn't final so using " Timer" for now).
- name = total.replace("Σ", "") + " Timer",
- state =
- TimerContentModel.TimerState.Running(
- finishTime = finishTime,
- pauseIntent = notification.findPauseIntent(),
- addMinuteAction = notification.findAddMinuteAction(),
- resetAction = notification.findResetAction(),
- ),
- )
- }
- else -> error("unknown state ($state) in sortKey=$sortKey")
- }
- }
-
- private fun Notification.findPauseIntent(): PendingIntent? {
- return actions
- .firstOrNull { it.actionIntent.intent?.action?.endsWith(".PAUSE_TIMER") == true }
- ?.actionIntent
- }
-
- private fun Notification.findStartIntent(): PendingIntent? {
- return actions
- .firstOrNull { it.actionIntent.intent?.action?.endsWith(".START_TIMER") == true }
- ?.actionIntent
- }
-
- // TODO: b/352142761 - switch to system attributes for label and icon.
- // - We probably want a consistent look for the Reset button. (Double check with UX.)
- // - Using the custom assets now since I couldn't an existing "Reset" icon.
- private fun Notification.findResetAction(): Notification.Action? {
- return actions.firstOrNull {
- it.actionIntent.intent?.action?.endsWith(".RESET_TIMER") == true
- }
- }
-
- // TODO: b/352142761 - check with UX on whether this should be required.
- // - Alternative is to allow for optional actions in addition to main and reset.
- // - For optional actions, we should take the custom label and icon.
- private fun Notification.findAddMinuteAction(): Notification.Action? {
- return actions.firstOrNull {
- it.actionIntent.intent?.action?.endsWith(".ADD_MINUTE_TIMER") == true
- }
- }
-
- private fun parseCurrentTime(current: String): Long {
- val (hour, minute, second, millis) = current.replace("▶", "").split(":", ".")
- // NOTE: this won't work correctly at/around midnight. It's just for prototyping.
- val localDateTime =
- LocalDateTime.of(
- LocalDate.now(),
- LocalTime.of(hour.toInt(), minute.toInt(), second.toInt(), millis.toInt() * 1000000),
- )
- val offset = ZoneId.systemDefault().rules.getOffset(localDateTime)
- return localDateTime.toInstant(offset).toEpochMilli()
- }
-
- private fun parseTimeDelta(delta: String): Duration {
- val (hour, minute, second) = delta.replace("Σ", "").replace("⏳", "").split(":")
- return Duration.ofHours(hour.toLong())
- .plusMinutes(minute.toLong())
- .plusSeconds(second.toLong())
- }
-
- private fun parseEnRouteNotification(
- notification: Notification,
- icon: IconModel,
- ): EnRouteContentModel {
- return EnRouteContentModel(
- smallIcon = icon,
- title = notification.extras.getCharSequence(Notification.EXTRA_TITLE),
- text = notification.extras.getCharSequence(Notification.EXTRA_TEXT),
- )
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationViewInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationViewInflater.kt
deleted file mode 100644
index 77c4130..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationViewInflater.kt
+++ /dev/null
@@ -1,226 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.row
-
-import android.app.Notification
-import android.content.Context
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.res.R
-import com.android.systemui.statusbar.notification.collection.NotificationEntry
-import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.InflatedContentViewHolder
-import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.KeepExistingView
-import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.NullContentView
-import com.android.systemui.statusbar.notification.row.shared.EnRouteContentModel
-import com.android.systemui.statusbar.notification.row.shared.RichOngoingContentModel
-import com.android.systemui.statusbar.notification.row.shared.RichOngoingNotificationFlag
-import com.android.systemui.statusbar.notification.row.shared.TimerContentModel
-import com.android.systemui.statusbar.notification.row.ui.view.EnRouteView
-import com.android.systemui.statusbar.notification.row.ui.view.TimerView
-import com.android.systemui.statusbar.notification.row.ui.viewbinder.EnRouteViewBinder
-import com.android.systemui.statusbar.notification.row.ui.viewbinder.TimerViewBinder
-import com.android.systemui.statusbar.notification.row.ui.viewmodel.EnRouteViewModel
-import com.android.systemui.statusbar.notification.row.ui.viewmodel.RichOngoingViewModelComponent
-import com.android.systemui.statusbar.notification.row.ui.viewmodel.TimerViewModel
-import javax.inject.Inject
-import kotlinx.coroutines.DisposableHandle
-
-fun interface DeferredContentViewBinder {
- fun setupContentViewBinder(): DisposableHandle
-}
-
-enum class RichOngoingNotificationViewType {
- Contracted,
- Expanded,
- HeadsUp,
-}
-
-/**
- * * Supertype of the 3 different possible result types of
- * [RichOngoingNotificationViewInflater.inflateView].
- */
-sealed interface ContentViewInflationResult {
-
- /** Indicates that the content view should be removed if present. */
- data object NullContentView : ContentViewInflationResult
-
- /**
- * Indicates that the content view (which *must be* present) should be unmodified during this
- * inflation.
- */
- data object KeepExistingView : ContentViewInflationResult
-
- /**
- * Contains the new view and binder that should replace any existing content view for this slot.
- */
- data class InflatedContentViewHolder(val view: View, val binder: DeferredContentViewBinder) :
- ContentViewInflationResult
-}
-
-fun ContentViewInflationResult?.shouldDisposeViewBinder() = this !is KeepExistingView
-
-/**
- * Interface which provides a [RichOngoingContentModel] for a given [Notification] when one is
- * applicable to the given style.
- */
-interface RichOngoingNotificationViewInflater {
- fun inflateView(
- contentModel: RichOngoingContentModel,
- existingView: View?,
- entry: NotificationEntry,
- systemUiContext: Context,
- parentView: ViewGroup,
- viewType: RichOngoingNotificationViewType,
- ): ContentViewInflationResult
-
- fun canKeepView(
- contentModel: RichOngoingContentModel,
- existingView: View?,
- viewType: RichOngoingNotificationViewType
- ): Boolean
-}
-
-@SysUISingleton
-class RichOngoingNotificationViewInflaterImpl
-@Inject
-constructor(
- private val viewModelComponentFactory: RichOngoingViewModelComponent.Factory,
-) : RichOngoingNotificationViewInflater {
-
- override fun inflateView(
- contentModel: RichOngoingContentModel,
- existingView: View?,
- entry: NotificationEntry,
- systemUiContext: Context,
- parentView: ViewGroup,
- viewType: RichOngoingNotificationViewType,
- ): ContentViewInflationResult {
- if (RichOngoingNotificationFlag.isUnexpectedlyInLegacyMode()) return NullContentView
- val component = viewModelComponentFactory.create(entry)
- return when (contentModel) {
- is TimerContentModel ->
- inflateTimerView(
- existingView,
- component::createTimerViewModel,
- systemUiContext,
- parentView,
- viewType
- )
- is EnRouteContentModel ->
- inflateEnRouteView(
- existingView,
- component::createEnRouteViewModel,
- systemUiContext,
- parentView,
- viewType
- )
- else -> TODO("Not yet implemented")
- }
- }
-
- override fun canKeepView(
- contentModel: RichOngoingContentModel,
- existingView: View?,
- viewType: RichOngoingNotificationViewType
- ): Boolean {
- if (RichOngoingNotificationFlag.isUnexpectedlyInLegacyMode()) return false
- return when (contentModel) {
- is TimerContentModel -> canKeepTimerView(contentModel, existingView, viewType)
- is EnRouteContentModel -> canKeepEnRouteView(contentModel, existingView, viewType)
- else -> TODO("Not yet implemented")
- }
- }
-
- private fun inflateTimerView(
- existingView: View?,
- createViewModel: () -> TimerViewModel,
- systemUiContext: Context,
- parentView: ViewGroup,
- viewType: RichOngoingNotificationViewType,
- ): ContentViewInflationResult {
- if (existingView is TimerView && !existingView.isReinflateNeeded()) return KeepExistingView
-
- return when (viewType) {
- RichOngoingNotificationViewType.Contracted -> {
- val newView =
- LayoutInflater.from(systemUiContext)
- .inflate(
- R.layout.rich_ongoing_timer_notification,
- parentView,
- /* attachToRoot= */ false
- ) as TimerView
- InflatedContentViewHolder(newView) {
- TimerViewBinder.bindWhileAttached(newView, createViewModel())
- }
- }
- RichOngoingNotificationViewType.Expanded,
- RichOngoingNotificationViewType.HeadsUp -> NullContentView
- }
- }
-
- private fun canKeepTimerView(
- contentModel: TimerContentModel,
- existingView: View?,
- viewType: RichOngoingNotificationViewType
- ): Boolean = true
-
- private fun inflateEnRouteView(
- existingView: View?,
- createViewModel: () -> EnRouteViewModel,
- systemUiContext: Context,
- parentView: ViewGroup,
- viewType: RichOngoingNotificationViewType,
- ): ContentViewInflationResult {
- if (existingView is EnRouteView && !existingView.isReinflateNeeded())
- return KeepExistingView
- return when (viewType) {
- RichOngoingNotificationViewType.Contracted -> {
- val newView =
- LayoutInflater.from(systemUiContext)
- .inflate(
- R.layout.notification_template_en_route_contracted,
- parentView,
- /* attachToRoot= */ false
- ) as EnRouteView
- InflatedContentViewHolder(newView) {
- EnRouteViewBinder.bindWhileAttached(newView, createViewModel())
- }
- }
- RichOngoingNotificationViewType.Expanded -> {
- val newView =
- LayoutInflater.from(systemUiContext)
- .inflate(
- R.layout.notification_template_en_route_expanded,
- parentView,
- /* attachToRoot= */ false
- ) as EnRouteView
- InflatedContentViewHolder(newView) {
- EnRouteViewBinder.bindWhileAttached(newView, createViewModel())
- }
- }
- RichOngoingNotificationViewType.HeadsUp -> NullContentView
- }
- }
-
- private fun canKeepEnRouteView(
- contentModel: EnRouteContentModel,
- existingView: View?,
- viewType: RichOngoingNotificationViewType
- ): Boolean = true
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/data/repository/NotificationRowRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/data/repository/NotificationRowRepository.kt
deleted file mode 100644
index bac887b..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/data/repository/NotificationRowRepository.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.row.data.repository
-
-import com.android.systemui.statusbar.notification.row.shared.RichOngoingContentModel
-import kotlinx.coroutines.flow.StateFlow
-
-/** A repository of states relating to a specific notification row. */
-interface NotificationRowRepository {
- /**
- * A flow of an immutable data class with the current state of the Rich Ongoing Notification
- * content, if applicable.
- */
- val richOngoingContentModel: StateFlow<RichOngoingContentModel?>
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractor.kt
deleted file mode 100644
index 72823a7..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractor.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- *
- */
-
-package com.android.systemui.statusbar.notification.row.domain.interactor
-
-import com.android.systemui.statusbar.notification.row.data.repository.NotificationRowRepository
-import com.android.systemui.statusbar.notification.row.shared.EnRouteContentModel
-import com.android.systemui.statusbar.notification.row.shared.TimerContentModel
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.filterIsInstance
-
-/** Interactor specific to a particular notification row. */
-class NotificationRowInteractor @Inject constructor(repository: NotificationRowRepository) {
- /** Content of a rich ongoing timer notification. */
- val timerContentModel: Flow<TimerContentModel> =
- repository.richOngoingContentModel.filterIsInstance<TimerContentModel>()
-
- /** Content of a rich ongoing timer notification. */
- val enRouteContentModel: Flow<EnRouteContentModel> =
- repository.richOngoingContentModel.filterIsInstance<EnRouteContentModel>()
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/EnRouteContentModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/EnRouteContentModel.kt
deleted file mode 100644
index 7e78cca..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/EnRouteContentModel.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.row.shared
-
-/**
- * Represents something en route.
- *
- * @param smallIcon the main small icon of the EnRoute notification.
- * @param title the title of the EnRoute notification.
- * @param text the text of the EnRoute notification.
- */
-data class EnRouteContentModel(
- val smallIcon: IconModel,
- val title: CharSequence?,
- val text: CharSequence?,
-) : RichOngoingContentModel
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/IconModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/IconModel.kt
deleted file mode 100644
index e611938..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/IconModel.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.row.shared
-
-import android.graphics.drawable.Drawable
-import android.graphics.drawable.Icon
-
-// TODO: figure out how to support lazy resolution of the drawable, e.g. on unrelated text change
-class IconModel(val icon: Icon) {
- var drawable: Drawable? = null
-
- override fun equals(other: Any?): Boolean =
- when (other) {
- null -> false
- (other === this) -> true
- !is IconModel -> false
- else -> other.icon.sameAs(icon)
- }
-
- override fun toString(): String = "IconModel(icon=$icon, drawable=$drawable)"
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/NotificationContentModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/NotificationContentModel.kt
index 0f9a5a3..004c66b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/NotificationContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/NotificationContentModel.kt
@@ -22,7 +22,4 @@
val headsUpStatusBarModel: HeadsUpStatusBarModel,
val singleLineViewModel: SingleLineViewModel? = null,
val publicSingleLineViewModel: SingleLineViewModel? = null,
- val richOngoingContentModel: RichOngoingContentModel? = null,
)
-
-sealed interface RichOngoingContentModel
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/RichOngoingClock.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/RichOngoingClock.kt
deleted file mode 100644
index 33b2564..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/RichOngoingClock.kt
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.row.shared
-
-import android.app.Notification
-import android.app.PendingIntent
-import java.time.Duration
-
-/**
- * Represents a simple timer that counts down to a time.
- *
- * @param name the label for the timer
- * @param state state of the timer, including time and whether it is paused or running
- */
-data class TimerContentModel(
- val icon: IconModel,
- val name: String,
- val state: TimerState,
-) : RichOngoingContentModel {
- /** The state (paused or running) of the timer, and relevant time */
- sealed interface TimerState {
- val addMinuteAction: Notification.Action?
- val resetAction: Notification.Action?
-
- /**
- * Indicates a running timer
- *
- * @param finishTime the time in ms since epoch that the timer will finish
- * @param pauseIntent the action for pausing the timer
- */
- data class Running(
- val finishTime: Long,
- val pauseIntent: PendingIntent?,
- override val addMinuteAction: Notification.Action?,
- override val resetAction: Notification.Action?,
- ) : TimerState
-
- /**
- * Indicates a paused timer
- *
- * @param timeRemaining the time in ms remaining on the paused timer
- * @param resumeIntent the action for resuming the timer
- */
- data class Paused(
- val timeRemaining: Duration,
- val resumeIntent: PendingIntent?,
- override val addMinuteAction: Notification.Action?,
- override val resetAction: Notification.Action?,
- ) : TimerState
- }
-}
-
-/**
- * Represents a simple stopwatch that counts up and allows tracking laps.
- *
- * @param state state of the stopwatch, including time and whether it is paused or running
- * @param lapDurations a list of durations of each completed lap
- */
-data class StopwatchContentModel(
- val icon: IconModel,
- val state: StopwatchState,
- val lapDurations: List<Long>,
-) : RichOngoingContentModel {
- /** The state (paused or running) of the stopwatch, and relevant time */
- sealed interface StopwatchState {
- /**
- * Indicates a running stopwatch
- *
- * @param startTime the time in ms since epoch that the stopwatch started, plus any
- * accumulated pause time
- * @param pauseIntent the action for pausing the stopwatch
- */
- data class Running(
- val startTime: Long,
- val pauseIntent: PendingIntent,
- ) : StopwatchState
-
- /**
- * Indicates a paused stopwatch
- *
- * @param timeElapsed the time in ms elapsed on the stopwatch
- * @param resumeIntent the action for resuming the stopwatch
- */
- data class Paused(
- val timeElapsed: Duration,
- val resumeIntent: PendingIntent,
- ) : StopwatchState
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/RichOngoingNotificationFlag.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/RichOngoingNotificationFlag.kt
deleted file mode 100644
index 4a7f7cd..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/RichOngoingNotificationFlag.kt
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.row.shared
-
-import android.app.Flags
-import com.android.systemui.flags.FlagToken
-import com.android.systemui.flags.RefactorFlagUtils
-
-/** Helper for reading or using the api rich ongoing flag state. */
-@Suppress("NOTHING_TO_INLINE")
-object RichOngoingNotificationFlag {
- /** The aconfig flag name */
- const val FLAG_NAME = Flags.FLAG_API_RICH_ONGOING
-
- /** A token used for dependency declaration */
- val token: FlagToken
- get() = FlagToken(FLAG_NAME, isEnabled)
-
- /** Is the refactor enabled */
- @JvmStatic
- inline val isEnabled
- get() = Flags.apiRichOngoing()
-
- /**
- * 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/row/ui/view/ConfigurationTracker.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/ConfigurationTracker.kt
deleted file mode 100644
index 95c507c..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/ConfigurationTracker.kt
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.row.ui.view
-
-import android.content.pm.ActivityInfo.CONFIG_ASSETS_PATHS
-import android.content.pm.ActivityInfo.CONFIG_DENSITY
-import android.content.pm.ActivityInfo.CONFIG_FONT_SCALE
-import android.content.pm.ActivityInfo.CONFIG_LAYOUT_DIRECTION
-import android.content.pm.ActivityInfo.CONFIG_LOCALE
-import android.content.pm.ActivityInfo.CONFIG_UI_MODE
-import android.content.res.Configuration
-import android.content.res.Resources
-
-/**
- * Tracks the active configuration when constructed and returns (when queried) whether the
- * configuration has unhandled changes.
- */
-class ConfigurationTracker(
- private val resources: Resources,
- private val unhandledConfigChanges: Int
-) {
- private val initialConfig = Configuration(resources.configuration)
-
- constructor(
- resources: Resources,
- handlesDensityFontScale: Boolean = false,
- handlesTheme: Boolean = false,
- handlesLocaleAndLayout: Boolean = true,
- ) : this(
- resources,
- unhandledConfigChanges =
- (if (handlesDensityFontScale) 0 else CONFIG_DENSITY or CONFIG_FONT_SCALE) or
- (if (handlesTheme) 0 else CONFIG_ASSETS_PATHS or CONFIG_UI_MODE) or
- (if (handlesLocaleAndLayout) 0 else CONFIG_LOCALE or CONFIG_LAYOUT_DIRECTION)
- )
-
- /**
- * Whether the current configuration has unhandled changes relative to the initial configuration
- */
- fun hasUnhandledConfigChange(): Boolean =
- initialConfig.diff(resources.configuration) and unhandledConfigChanges != 0
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/EnRouteView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/EnRouteView.kt
deleted file mode 100644
index e5c2b5f..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/EnRouteView.kt
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.row.ui.view
-
-import android.content.Context
-import android.graphics.drawable.Icon
-import android.util.AttributeSet
-import android.widget.FrameLayout
-import android.widget.ImageView
-import android.widget.TextView
-import com.android.internal.R
-import com.android.internal.widget.NotificationExpandButton
-
-class EnRouteView
-@JvmOverloads
-constructor(
- context: Context,
- attrs: AttributeSet? = null,
- defStyleAttr: Int = 0,
- defStyleRes: Int = 0,
-) : FrameLayout(context, attrs, defStyleAttr, defStyleRes) {
-
- private val configTracker = ConfigurationTracker(resources)
-
- private lateinit var icon: ImageView
- private lateinit var title: TextView
- private lateinit var text: TextView
- private lateinit var expandButton: NotificationExpandButton
-
- override fun onFinishInflate() {
- super.onFinishInflate()
- icon = requireViewById(R.id.icon)
- title = requireViewById(R.id.title)
- text = requireViewById(R.id.text)
-
- expandButton = requireViewById(R.id.expand_button)
- expandButton.setExpanded(false)
- }
-
- /** the resources configuration has changed such that the view needs to be reinflated */
- fun isReinflateNeeded(): Boolean = configTracker.hasUnhandledConfigChange()
-
- fun setIcon(icon: Icon?) {
- this.icon.setImageIcon(icon)
- }
-
- fun setTitle(title: CharSequence?) {
- this.title.text = title
- }
-
- fun setText(text: CharSequence?) {
- this.text.text = text
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/TimerButtonView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/TimerButtonView.kt
deleted file mode 100644
index 8c95187..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/TimerButtonView.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.row.ui.view
-
-import android.annotation.DrawableRes
-import android.content.Context
-import android.graphics.BlendMode
-import android.util.AttributeSet
-import com.android.internal.widget.EmphasizedNotificationButton
-
-class TimerButtonView
-@JvmOverloads
-constructor(
- context: Context,
- attrs: AttributeSet? = null,
- defStyleAttr: Int = 0,
- defStyleRes: Int = 0,
-) : EmphasizedNotificationButton(context, attrs, defStyleAttr, defStyleRes) {
-
- private val Int.dp: Int
- get() = (this * context.resources.displayMetrics.density).toInt()
-
- fun setIcon(@DrawableRes icon: Int) {
- val drawable = context.getDrawable(icon)
-
- drawable?.mutate()
- drawable?.setTintList(textColors)
- drawable?.setTintBlendMode(BlendMode.SRC_IN)
- drawable?.setBounds(0, 0, 24.dp, 24.dp)
-
- setCompoundDrawablesRelative(drawable, null, null, null)
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/TimerView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/TimerView.kt
deleted file mode 100644
index d481b50..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/TimerView.kt
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.row.ui.view
-
-import android.content.Context
-import android.graphics.drawable.Icon
-import android.os.SystemClock
-import android.util.AttributeSet
-import android.widget.Chronometer
-import android.widget.ImageView
-import android.widget.TextView
-import androidx.constraintlayout.widget.ConstraintLayout
-import androidx.core.view.isVisible
-import com.android.systemui.res.R
-
-class TimerView
-@JvmOverloads
-constructor(
- context: Context,
- attrs: AttributeSet? = null,
- defStyleAttr: Int = 0,
- defStyleRes: Int = 0,
-) : ConstraintLayout(context, attrs, defStyleAttr, defStyleRes) {
-
- private val configTracker = ConfigurationTracker(resources)
-
- private lateinit var icon: ImageView
- private lateinit var label: TextView
- private lateinit var chronometer: Chronometer
- private lateinit var pausedTimeRemaining: TextView
- lateinit var mainButton: TimerButtonView
- private set
-
- lateinit var altButton: TimerButtonView
- private set
-
- lateinit var resetButton: TimerButtonView
- private set
-
- override fun onFinishInflate() {
- super.onFinishInflate()
- icon = requireViewById(R.id.icon)
- label = requireViewById(R.id.label)
- chronometer = requireViewById(R.id.chronoRemaining)
- pausedTimeRemaining = requireViewById(R.id.pausedTimeRemaining)
- mainButton = requireViewById(R.id.mainButton)
- altButton = requireViewById(R.id.altButton)
- resetButton = requireViewById(R.id.resetButton)
- }
-
- /** the resources configuration has changed such that the view needs to be reinflated */
- fun isReinflateNeeded(): Boolean = configTracker.hasUnhandledConfigChange()
-
- fun setIcon(icon: Icon?) {
- this.icon.setImageIcon(icon)
- }
-
- fun setLabel(label: String) {
- this.label.text = label
- }
-
- fun setPausedTime(pausedTime: String?) {
- if (pausedTime != null) {
- pausedTimeRemaining.text = pausedTime
- pausedTimeRemaining.isVisible = true
- } else {
- pausedTimeRemaining.isVisible = false
- }
- }
-
- fun setCountdownTime(countdownTimeMs: Long?) {
- if (countdownTimeMs != null) {
- chronometer.base =
- countdownTimeMs - System.currentTimeMillis() + SystemClock.elapsedRealtime()
- chronometer.isVisible = true
- chronometer.start()
- } else {
- chronometer.isVisible = false
- chronometer.stop()
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/EnRouteViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/EnRouteViewBinder.kt
deleted file mode 100644
index 3b8957c..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/EnRouteViewBinder.kt
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.row.ui.viewbinder
-
-import androidx.lifecycle.lifecycleScope
-import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.statusbar.notification.row.ui.view.EnRouteView
-import com.android.systemui.statusbar.notification.row.ui.viewmodel.EnRouteViewModel
-import kotlinx.coroutines.DisposableHandle
-import kotlinx.coroutines.coroutineScope
-import kotlinx.coroutines.launch
-
-/** Binds a [EnRouteView] to its [view model][EnRouteViewModel]. */
-object EnRouteViewBinder {
- fun bindWhileAttached(
- view: EnRouteView,
- viewModel: EnRouteViewModel,
- ): DisposableHandle {
- return view.repeatWhenAttached { lifecycleScope.launch { bind(view, viewModel) } }
- }
-
- suspend fun bind(
- view: EnRouteView,
- viewModel: EnRouteViewModel,
- ) = coroutineScope {
- launch { viewModel.icon.collect { view.setIcon(it) } }
- launch { viewModel.title.collect { view.setTitle(it) } }
- launch { viewModel.text.collect { view.setText(it) } }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/TimerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/TimerViewBinder.kt
deleted file mode 100644
index 042d1bc..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/TimerViewBinder.kt
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.row.ui.viewbinder
-
-import android.content.res.ColorStateList
-import android.graphics.drawable.Icon
-import android.view.View
-import androidx.core.view.isGone
-import androidx.lifecycle.lifecycleScope
-import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.statusbar.notification.row.ui.view.TimerButtonView
-import com.android.systemui.statusbar.notification.row.ui.view.TimerView
-import com.android.systemui.statusbar.notification.row.ui.viewmodel.TimerViewModel
-import kotlinx.coroutines.DisposableHandle
-import kotlinx.coroutines.coroutineScope
-import kotlinx.coroutines.launch
-
-/** Binds a [TimerView] to its [view model][TimerViewModel]. */
-object TimerViewBinder {
- fun bindWhileAttached(
- view: TimerView,
- viewModel: TimerViewModel,
- ): DisposableHandle {
- return view.repeatWhenAttached { lifecycleScope.launch { bind(view, viewModel) } }
- }
-
- suspend fun bind(
- view: TimerView,
- viewModel: TimerViewModel,
- ) = coroutineScope {
- launch { viewModel.icon.collect { view.setIcon(it) } }
- launch { viewModel.label.collect { view.setLabel(it) } }
- launch { viewModel.pausedTime.collect { view.setPausedTime(it) } }
- launch { viewModel.countdownTime.collect { view.setCountdownTime(it) } }
- launch { viewModel.mainButtonModel.collect { bind(view.mainButton, it) } }
- launch { viewModel.altButtonModel.collect { bind(view.altButton, it) } }
- launch { viewModel.resetButtonModel.collect { bind(view.resetButton, it) } }
- }
-
- fun bind(buttonView: TimerButtonView, model: TimerViewModel.ButtonViewModel?) {
- if (model != null) {
- buttonView.setButtonBackground(
- ColorStateList.valueOf(
- buttonView.context.getColor(com.android.internal.R.color.system_accent2_100)
- )
- )
- buttonView.setTextColor(
- buttonView.context.getColor(
- com.android.internal.R.color.notification_primary_text_color_light
- )
- )
-
- when (model) {
- is TimerViewModel.ButtonViewModel.WithSystemAttrs -> {
- buttonView.setIcon(model.iconRes)
- buttonView.setText(model.labelRes)
- }
- is TimerViewModel.ButtonViewModel.WithCustomAttrs -> {
- // TODO: b/352142761 - is there a better way to deal with TYPE_RESOURCE icons
- // with empty resPackage? RemoteViews handles this by using a different
- // `contextForResources` for inflation.
- val icon =
- if (model.icon.type == Icon.TYPE_RESOURCE && model.icon.resPackage == "")
- Icon.createWithResource(
- "com.google.android.deskclock",
- model.icon.resId
- )
- else model.icon
- buttonView.setImageIcon(icon)
- buttonView.text = model.label
- }
- }
-
- buttonView.setOnClickListener(
- model.pendingIntent?.let { pendingIntent ->
- View.OnClickListener { pendingIntent.send() }
- }
- )
- buttonView.isEnabled = model.pendingIntent != null
- }
- buttonView.isGone = model == null
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModel.kt
deleted file mode 100644
index 307a983..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModel.kt
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.row.ui.viewmodel
-
-import android.graphics.drawable.Icon
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.statusbar.notification.row.domain.interactor.NotificationRowInteractor
-import com.android.systemui.statusbar.notification.row.shared.RichOngoingNotificationFlag
-import com.android.systemui.util.kotlin.FlowDumperImpl
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.mapNotNull
-
-/** A view model for EnRoute notifications. */
-class EnRouteViewModel
-@Inject
-constructor(
- dumpManager: DumpManager,
- rowInteractor: NotificationRowInteractor,
-) : FlowDumperImpl(dumpManager) {
- init {
- /* check if */ RichOngoingNotificationFlag.isUnexpectedlyInLegacyMode()
- }
-
- val icon: Flow<Icon?> = rowInteractor.enRouteContentModel.mapNotNull { it.smallIcon.icon }
-
- val title: Flow<CharSequence?> = rowInteractor.enRouteContentModel.map { it.title }
-
- val text: Flow<CharSequence?> = rowInteractor.enRouteContentModel.map { it.text }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/RichOngoingViewModelComponent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/RichOngoingViewModelComponent.kt
deleted file mode 100644
index 5552d89..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/RichOngoingViewModelComponent.kt
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.row.ui.viewmodel
-
-// noinspection CleanArchitectureDependencyViolation
-import com.android.systemui.statusbar.notification.row.data.repository.NotificationRowRepository
-import dagger.BindsInstance
-import dagger.Subcomponent
-
-@Subcomponent
-interface RichOngoingViewModelComponent {
-
- @Subcomponent.Factory
- interface Factory {
- /** Creates an instance of [RichOngoingViewModelComponent]. */
- fun create(
- @BindsInstance repository: NotificationRowRepository
- ): RichOngoingViewModelComponent
- }
-
- fun createTimerViewModel(): TimerViewModel
-
- fun createEnRouteViewModel(): EnRouteViewModel
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModel.kt
deleted file mode 100644
index 768a093..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModel.kt
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.row.ui.viewmodel
-
-import android.annotation.DrawableRes
-import android.annotation.StringRes
-import android.app.PendingIntent
-import android.graphics.drawable.Icon
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.statusbar.notification.row.domain.interactor.NotificationRowInteractor
-import com.android.systemui.statusbar.notification.row.shared.RichOngoingNotificationFlag
-import com.android.systemui.statusbar.notification.row.shared.TimerContentModel.TimerState
-import com.android.systemui.util.kotlin.FlowDumperImpl
-import java.time.Duration
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.mapNotNull
-
-/** A view model for Timer notifications. */
-class TimerViewModel
-@Inject
-constructor(
- dumpManager: DumpManager,
- rowInteractor: NotificationRowInteractor,
-) : FlowDumperImpl(dumpManager) {
- init {
- /* check if */ RichOngoingNotificationFlag.isUnexpectedlyInLegacyMode()
- }
-
- private val state: Flow<TimerState> = rowInteractor.timerContentModel.mapNotNull { it.state }
-
- val icon: Flow<Icon?> = rowInteractor.timerContentModel.mapNotNull { it.icon.icon }
-
- val label: Flow<String> = rowInteractor.timerContentModel.mapNotNull { it.name }
-
- val countdownTime: Flow<Long?> = state.map { (it as? TimerState.Running)?.finishTime }
-
- val pausedTime: Flow<String?> =
- state.map { (it as? TimerState.Paused)?.timeRemaining?.format() }
-
- val mainButtonModel: Flow<ButtonViewModel> =
- state.map {
- when (it) {
- is TimerState.Paused ->
- ButtonViewModel.WithSystemAttrs(
- it.resumeIntent,
- com.android.systemui.res.R.string.controls_media_resume, // "Resume",
- com.android.systemui.res.R.drawable.ic_media_play
- )
- is TimerState.Running ->
- ButtonViewModel.WithSystemAttrs(
- it.pauseIntent,
- com.android.systemui.res.R.string.controls_media_button_pause, // "Pause",
- com.android.systemui.res.R.drawable.ic_media_pause
- )
- }
- }
-
- val altButtonModel: Flow<ButtonViewModel?> =
- state.map {
- it.addMinuteAction?.let { action ->
- ButtonViewModel.WithCustomAttrs(
- action.actionIntent,
- action.title, // "1:00",
- action.getIcon()
- )
- }
- }
-
- val resetButtonModel: Flow<ButtonViewModel?> =
- state.map {
- it.resetAction?.let { action ->
- ButtonViewModel.WithCustomAttrs(
- action.actionIntent,
- action.title, // "Reset",
- action.getIcon()
- )
- }
- }
-
- sealed interface ButtonViewModel {
- val pendingIntent: PendingIntent?
-
- data class WithSystemAttrs(
- override val pendingIntent: PendingIntent?,
- @StringRes val labelRes: Int,
- @DrawableRes val iconRes: Int,
- ) : ButtonViewModel
-
- data class WithCustomAttrs(
- override val pendingIntent: PendingIntent?,
- val label: CharSequence,
- val icon: Icon,
- ) : ButtonViewModel
- }
-}
-
-private fun Duration.format(): String {
- val hours = this.toHours()
- return if (hours > 0) {
- String.format("%d:%02d:%02d", hours, toMinutesPart(), toSecondsPart())
- } else {
- String.format("%d:%02d", toMinutes(), toSecondsPart())
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index b466bf0..b171e87 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -1160,11 +1160,13 @@
@Override
public void addHeadsUpHeightChangedListener(@NonNull Runnable runnable) {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
mHeadsUpHeightChangedListeners.addIfAbsent(runnable);
}
@Override
public void removeHeadsUpHeightChangedListener(@NonNull Runnable runnable) {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
mHeadsUpHeightChangedListeners.remove(runnable);
}
@@ -1240,11 +1242,13 @@
@Override
public void setScrolledToTop(boolean scrolledToTop) {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
mScrollViewFields.setScrolledToTop(scrolledToTop);
}
@Override
public void setStackTop(float stackTop) {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
if (mAmbientState.getStackTop() != stackTop) {
mAmbientState.setStackTop(stackTop);
onTopPaddingChanged(/* animate = */ isAddOrRemoveAnimationPending());
@@ -1253,51 +1257,54 @@
@Override
public void setStackCutoff(float stackCutoff) {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
mAmbientState.setStackCutoff(stackCutoff);
}
@Override
public void setHeadsUpTop(float headsUpTop) {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
mAmbientState.setHeadsUpTop(headsUpTop);
requestChildrenUpdate();
}
@Override
public void setHeadsUpBottom(float headsUpBottom) {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
mAmbientState.setHeadsUpBottom(headsUpBottom);
mStateAnimator.setHeadsUpAppearHeightBottom(Math.round(headsUpBottom));
}
@Override
public void closeGutsOnSceneTouch() {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
mController.closeControlsDueToOutsideTouch();
}
@Override
public void setSyntheticScrollConsumer(@Nullable Consumer<Float> consumer) {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
mScrollViewFields.setSyntheticScrollConsumer(consumer);
}
@Override
public void setCurrentGestureOverscrollConsumer(@Nullable Consumer<Boolean> consumer) {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
mScrollViewFields.setCurrentGestureOverscrollConsumer(consumer);
}
@Override
public void setCurrentGestureInGutsConsumer(@Nullable Consumer<Boolean> consumer) {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
mScrollViewFields.setCurrentGestureInGutsConsumer(consumer);
}
@Override
public void setRemoteInputRowBottomBoundConsumer(@Nullable Consumer<Float> consumer) {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
mScrollViewFields.setRemoteInputRowBottomBoundConsumer(consumer);
}
- @Override
- public void setHeadsUpHeightConsumer(@Nullable Consumer<Float> consumer) {
- mScrollViewFields.setHeadsUpHeightConsumer(consumer);
- }
-
/**
* @param listener to be notified after the location of Notification children might have
* changed.
@@ -2621,11 +2628,13 @@
@Override
public int getTopHeadsUpHeight() {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return 0;
return getTopHeadsUpIntrinsicHeight();
}
@Override
public int getHeadsUpInset() {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return 0;
return mHeadsUpInset;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt
index c08ed61..f6e8b8f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt
@@ -86,9 +86,6 @@
fun sendRemoteInputRowBottomBound(bottomY: Float?) =
remoteInputRowBottomBoundConsumer?.accept(bottomY)
- /** send the [headsUpHeight] to the [headsUpHeightConsumer], if present. */
- fun sendHeadsUpHeight(headsUpHeight: Float) = headsUpHeightConsumer?.accept(headsUpHeight)
-
fun dump(pw: IndentingPrintWriter) {
pw.printSection("StackViewStates") {
pw.println("scrimClippingShape", scrimClippingShape)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
index dbe81c1..6ad9f01 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
@@ -77,9 +77,6 @@
/** Set a consumer for current remote input notification row bottom bound events */
fun setRemoteInputRowBottomBoundConsumer(consumer: Consumer<Float?>?)
- /** Set a consumer for heads up height changed events */
- fun setHeadsUpHeightConsumer(consumer: Consumer<Float>?)
-
/** sets that scrolling is allowed */
fun setScrollingEnabled(enabled: Boolean)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
index 935e2a3..38390e7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
@@ -356,11 +356,23 @@
}
}
- val pinnedHeadsUpRows: Flow<Set<HeadsUpRowKey>> by lazy {
+ val activeHeadsUpRowKeys: Flow<Set<HeadsUpRowKey>> by lazy {
if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
flowOf(emptySet())
} else {
- headsUpNotificationInteractor.pinnedHeadsUpRows.dumpWhileCollecting("pinnedHeadsUpRows")
+ headsUpNotificationInteractor.activeHeadsUpRowKeys.dumpWhileCollecting(
+ "pinnedHeadsUpRows"
+ )
+ }
+ }
+
+ val pinnedHeadsUpRowKeys: Flow<Set<HeadsUpRowKey>> by lazy {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
+ flowOf(emptySet())
+ } else {
+ headsUpNotificationInteractor.pinnedHeadsUpRowKeys.dumpWhileCollecting(
+ "pinnedHeadsUpRows"
+ )
}
}
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 c9eaec7..aec81b0 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
@@ -84,9 +84,9 @@
private fun fullyExpandedDuringSceneChange(change: ChangeScene): Boolean {
// The lockscreen stack is visible during all transitions away from the lockscreen, so keep
// the stack expanded until those transitions finish.
- return if (change.isFrom({ it == Scenes.Lockscreen }, to = { true })) {
+ return if (change.isTransitioning(from = Scenes.Lockscreen)) {
true
- } else if (change.isFrom({ it == Scenes.Shade }, to = { it == Scenes.Lockscreen })) {
+ } else if (change.isTransitioning(from = Scenes.Shade, to = Scenes.Lockscreen)) {
false
} else {
(expandedInScene(change.fromScene) && expandedInScene(change.toScene))
@@ -101,11 +101,11 @@
return if (fullyExpandedDuringSceneChange(change)) {
1f
} else if (
- change.isBetween({ it == Scenes.Gone }, { it == Scenes.Shade }) ||
- change.isFrom({ it == Scenes.Shade }, to = { it == Scenes.Lockscreen })
+ change.isTransitioningBetween(Scenes.Gone, Scenes.Shade) ||
+ change.isTransitioning(from = Scenes.Gone, to = Scenes.Lockscreen)
) {
shadeExpansion
- } else if (change.isBetween({ it == Scenes.Gone }, { it == Scenes.QuickSettings })) {
+ } else if (change.isTransitioningBetween(Scenes.Gone, Scenes.QuickSettings)) {
// during QS expansion, increase fraction at same rate as scrim alpha,
// but start when scrim alpha is at EXPANSION_FOR_DELAYED_STACK_FADE_IN.
(qsExpansion / EXPANSION_FOR_MAX_SCRIM_ALPHA - EXPANSION_FOR_DELAYED_STACK_FADE_IN)
@@ -213,7 +213,11 @@
private val qsAllowsClipping: Flow<Boolean> =
combine(shadeInteractor.shadeMode, shadeInteractor.qsExpansion) { shadeMode, qsExpansion ->
- qsExpansion < 0.5f || shadeMode != ShadeMode.Single
+ when (shadeMode) {
+ is ShadeMode.Dual -> false
+ is ShadeMode.Split -> true
+ is ShadeMode.Single -> qsExpansion < 0.5f
+ }
}
.distinctUntilChanged()
@@ -325,9 +329,3 @@
fun create(): NotificationScrollViewModel
}
}
-
-private fun ChangeScene.isBetween(a: (SceneKey) -> Boolean, b: (SceneKey) -> Boolean): Boolean =
- (a(fromScene) && b(toScene)) || (b(fromScene) && a(toScene))
-
-private fun ChangeScene.isFrom(from: (SceneKey) -> Boolean, to: (SceneKey) -> Boolean): Boolean =
- from(fromScene) && to(toScene)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt
index dc15970..e2e5c59 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt
@@ -26,6 +26,7 @@
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
class HeadsUpNotificationViewBinder
@@ -35,18 +36,21 @@
coroutineScope {
launch {
var previousKeys = emptySet<HeadsUpRowKey>()
- viewModel.pinnedHeadsUpRows
+ combine(viewModel.pinnedHeadsUpRowKeys, viewModel.activeHeadsUpRowKeys, ::Pair)
.sample(viewModel.headsUpAnimationsEnabled, ::Pair)
.collect { (newKeys, animationsEnabled) ->
- val added = newKeys - previousKeys
- val removed = previousKeys - newKeys
- previousKeys = newKeys
+ val pinned = newKeys.first
+ val all = newKeys.second
+ val added = all.union(pinned) - previousKeys
+ val removed = previousKeys - pinned
+ previousKeys = pinned
+ Pair(added, removed)
if (animationsEnabled) {
added.forEach { key ->
parentView.generateHeadsUpAnimation(
obtainView(key),
- /* isHeadsUp = */ true
+ /* isHeadsUp = */ true,
)
}
removed.forEach { key ->
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 5ae24f7..479ffb7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -55,6 +55,7 @@
import com.android.keyguard.KeyguardViewController;
import com.android.keyguard.TrustGrantFlags;
import com.android.keyguard.ViewMediatorCallback;
+import com.android.systemui.DejankUtils;
import com.android.systemui.Flags;
import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor;
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
@@ -1203,6 +1204,11 @@
@Override
public void hide(long startTime, long fadeoutDuration) {
Trace.beginSection("StatusBarKeyguardViewManager#hide");
+ if (Flags.checkLockscreenGoneTransition()) {
+ DejankUtils.notifyRendererOfExpensiveFrame(
+ mNotificationShadeWindowController.getWindowRootView(),
+ "StatusBarKeyguardViewManager#hide");
+ }
mKeyguardStateController.notifyKeyguardState(false,
mKeyguardStateController.isOccluded());
launchPendingWakeupAction();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLocationPublisher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLocationPublisher.kt
deleted file mode 100644
index 4e5ecfe..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLocationPublisher.kt
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone
-
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.policy.CallbackController
-import java.lang.ref.WeakReference
-import javax.inject.Inject
-
-/**
- * Publishes updates to the status bar's margins.
- *
- * While the status bar view consumes the entire width of the device, the status bar
- * contents are laid out with margins for rounded corners, padding from the absolute
- * edges, and potentially display cutouts in the corner.
- */
-@SysUISingleton
-class StatusBarLocationPublisher @Inject constructor()
-: CallbackController<StatusBarMarginUpdatedListener> {
- private val listeners = mutableSetOf<WeakReference<StatusBarMarginUpdatedListener>>()
-
- var marginLeft: Int = 0
- private set
- var marginRight: Int = 0
- private set
-
- override fun addCallback(listener: StatusBarMarginUpdatedListener) {
- listeners.add(WeakReference(listener))
- }
-
- override fun removeCallback(listener: StatusBarMarginUpdatedListener) {
- var toRemove: WeakReference<StatusBarMarginUpdatedListener>? = null
- for (l in listeners) {
- if (l.get() == listener) {
- toRemove = l
- }
- }
-
- if (toRemove != null) {
- listeners.remove(toRemove)
- }
- }
-
- fun updateStatusBarMargin(left: Int, right: Int) {
- marginLeft = left
- marginRight = right
-
- notifyListeners()
- }
-
- private fun notifyListeners() {
- var listenerList: List<WeakReference<StatusBarMarginUpdatedListener>>
- synchronized(this) {
- listenerList = listeners.toList()
- }
-
- listenerList.forEach { wrapper ->
- if (wrapper.get() == null) {
- listeners.remove(wrapper)
- }
-
- wrapper.get()?.onStatusBarMarginUpdated(marginLeft, marginRight)
- }
- }
-}
-
-interface StatusBarMarginUpdatedListener {
- fun onStatusBarMarginUpdated(marginLeft: Int, marginRight: Int)
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index a8b4728..c258095 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -65,7 +65,6 @@
import com.android.systemui.statusbar.phone.PhoneStatusBarView;
import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager;
import com.android.systemui.statusbar.phone.StatusBarLocation;
-import com.android.systemui.statusbar.phone.StatusBarLocationPublisher;
import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent;
import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent.Startable;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
@@ -140,7 +139,6 @@
private final OperatorNameViewController.Factory mOperatorNameViewControllerFactory;
private final OngoingCallController mOngoingCallController;
private final SystemStatusAnimationScheduler mAnimationScheduler;
- private final StatusBarLocationPublisher mLocationPublisher;
private final ShadeExpansionStateManager mShadeExpansionStateManager;
private final StatusBarIconController mStatusBarIconController;
private final CarrierConfigTracker mCarrierConfigTracker;
@@ -243,7 +241,6 @@
StatusBarFragmentComponent.Factory statusBarFragmentComponentFactory,
OngoingCallController ongoingCallController,
SystemStatusAnimationScheduler animationScheduler,
- StatusBarLocationPublisher locationPublisher,
ShadeExpansionStateManager shadeExpansionStateManager,
StatusBarIconController statusBarIconController,
DarkIconManager.Factory darkIconManagerFactory,
@@ -267,7 +264,6 @@
mStatusBarFragmentComponentFactory = statusBarFragmentComponentFactory;
mOngoingCallController = ongoingCallController;
mAnimationScheduler = animationScheduler;
- mLocationPublisher = locationPublisher;
mShadeExpansionStateManager = shadeExpansionStateManager;
mStatusBarIconController = statusBarIconController;
mCollapsedStatusBarViewModel = collapsedStatusBarViewModel;
@@ -349,9 +345,6 @@
}
mStatusBar = (PhoneStatusBarView) view;
- View contents = mStatusBar.findViewById(R.id.status_bar_contents);
- contents.addOnLayoutChangeListener(mStatusBarLayoutListener);
- updateStatusBarLocation(contents.getLeft(), contents.getRight());
if (savedInstanceState != null && savedInstanceState.containsKey(EXTRA_PANEL_STATE)) {
mStatusBar.restoreHierarchyState(
savedInstanceState.getSparseParcelableArray(EXTRA_PANEL_STATE));
@@ -977,13 +970,6 @@
}, /*isAnimationRunning*/ false);
}
- private void updateStatusBarLocation(int left, int right) {
- int leftMargin = left - mStatusBar.getLeft();
- int rightMargin = mStatusBar.getRight() - right;
-
- mLocationPublisher.updateStatusBarMargin(leftMargin, rightMargin);
- }
-
private final ContentObserver mVolumeSettingObserver = new ContentObserver(null) {
@Override
public void onChange(boolean selfChange) {
@@ -991,14 +977,6 @@
}
};
- // Listen for view end changes of PhoneStatusBarView and publish that to the privacy dot
- private View.OnLayoutChangeListener mStatusBarLayoutListener =
- (view, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
- if (left != oldLeft || right != oldRight) {
- updateStatusBarLocation(left, right);
- }
- };
-
@Override
public void dump(PrintWriter printWriter, String[] args) {
IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, /* singleIndent= */" ");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
index eea4c21..9c168be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
@@ -95,7 +95,8 @@
primaryChipView.show(shouldAnimateChange = true)
is OngoingActivityChipModel.Hidden ->
primaryChipView.hide(
- shouldAnimateChange = primaryChipModel.shouldAnimate
+ state = View.GONE,
+ shouldAnimateChange = primaryChipModel.shouldAnimate,
)
}
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerExt.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerExt.kt
new file mode 100644
index 0000000..441cbb3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerExt.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy
+
+import com.android.systemui.statusbar.policy.DevicePostureController.DevicePostureInt
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.onStart
+
+/** [DevicePostureController.getDevicePosture] as a [Flow]. */
+@DevicePostureInt
+fun DevicePostureController.devicePosture(): Flow<Int> =
+ conflatedCallbackFlow {
+ val callback = DevicePostureController.Callback { posture -> trySend(posture) }
+ addCallback(callback)
+ awaitClose { removeCallback(callback) }
+ }
+ .onStart { emit(devicePosture) }
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt
index 6acc891..94e19de 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt
@@ -20,19 +20,27 @@
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.outlined.ArrowBack
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.unit.dp
import com.android.systemui.inputdevice.tutorial.ui.composable.DoneButton
import com.android.systemui.res.R
@@ -47,20 +55,17 @@
Column(
verticalArrangement = Arrangement.Center,
modifier =
- Modifier.background(
- color = MaterialTheme.colorScheme.surfaceContainer,
- )
- .fillMaxSize()
+ Modifier.background(color = MaterialTheme.colorScheme.surfaceContainer).fillMaxSize(),
) {
TutorialSelectionButtons(
onBackTutorialClicked = onBackTutorialClicked,
onHomeTutorialClicked = onHomeTutorialClicked,
onRecentAppsTutorialClicked = onRecentAppsTutorialClicked,
- modifier = Modifier.padding(60.dp)
+ modifier = Modifier.padding(60.dp),
)
DoneButton(
onDoneButtonClicked = onDoneButtonClicked,
- modifier = Modifier.padding(horizontal = 60.dp)
+ modifier = Modifier.padding(horizontal = 60.dp),
)
}
}
@@ -70,30 +75,36 @@
onBackTutorialClicked: () -> Unit,
onHomeTutorialClicked: () -> Unit,
onRecentAppsTutorialClicked: () -> Unit,
- modifier: Modifier = Modifier
+ modifier: Modifier = Modifier,
) {
Row(
horizontalArrangement = Arrangement.spacedBy(20.dp),
verticalAlignment = Alignment.CenterVertically,
- modifier = modifier
+ modifier = modifier,
) {
TutorialButton(
text = stringResource(R.string.touchpad_tutorial_home_gesture_button),
+ icon = Icons.AutoMirrored.Outlined.ArrowBack,
+ iconColor = MaterialTheme.colorScheme.onPrimary,
onClick = onHomeTutorialClicked,
- color = MaterialTheme.colorScheme.primary,
- modifier = Modifier.weight(1f)
+ backgroundColor = MaterialTheme.colorScheme.primary,
+ modifier = Modifier.weight(1f),
)
TutorialButton(
text = stringResource(R.string.touchpad_tutorial_back_gesture_button),
+ icon = ImageVector.vectorResource(id = R.drawable.touchpad_tutorial_home_icon),
+ iconColor = MaterialTheme.colorScheme.onTertiary,
onClick = onBackTutorialClicked,
- color = MaterialTheme.colorScheme.tertiary,
- modifier = Modifier.weight(1f)
+ backgroundColor = MaterialTheme.colorScheme.tertiary,
+ modifier = Modifier.weight(1f),
)
TutorialButton(
text = stringResource(R.string.touchpad_tutorial_recent_apps_gesture_button),
+ icon = ImageVector.vectorResource(id = R.drawable.touchpad_tutorial_recents_icon),
+ iconColor = MaterialTheme.colorScheme.onSecondary,
onClick = onRecentAppsTutorialClicked,
- color = MaterialTheme.colorScheme.secondary,
- modifier = Modifier.weight(1f)
+ backgroundColor = MaterialTheme.colorScheme.secondary,
+ modifier = Modifier.weight(1f),
)
}
}
@@ -101,16 +112,30 @@
@Composable
private fun TutorialButton(
text: String,
+ icon: ImageVector,
+ iconColor: Color,
onClick: () -> Unit,
- color: Color,
- modifier: Modifier = Modifier
+ backgroundColor: Color,
+ modifier: Modifier = Modifier,
) {
Button(
onClick = onClick,
shape = RoundedCornerShape(16.dp),
- colors = ButtonDefaults.buttonColors(containerColor = color),
- modifier = modifier.aspectRatio(0.66f)
+ colors = ButtonDefaults.buttonColors(containerColor = backgroundColor),
+ modifier = modifier.aspectRatio(0.66f),
) {
- Text(text = text, style = MaterialTheme.typography.headlineLarge)
+ Column(
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Icon(
+ imageVector = icon,
+ contentDescription = text,
+ modifier = Modifier.width(30.dp).height(30.dp),
+ tint = iconColor,
+ )
+ Spacer(modifier = Modifier.height(16.dp))
+ Text(text = text, style = MaterialTheme.typography.headlineLarge)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/NewVolumeDialog.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/NewVolumeDialog.kt
deleted file mode 100644
index 869b3c6..0000000
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/NewVolumeDialog.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.volume.dialog
-
-import android.app.Dialog
-import android.content.Context
-import android.os.Bundle
-import android.view.ContextThemeWrapper
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.res.R
-import javax.inject.Inject
-
-class NewVolumeDialog @Inject constructor(@Application context: Context) :
- Dialog(ContextThemeWrapper(context, R.style.volume_dialog_theme)) {
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.volume_dialog)
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/NewVolumeDialogPlugin.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/NewVolumeDialogPlugin.kt
deleted file mode 100644
index b93714a..0000000
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/NewVolumeDialogPlugin.kt
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.volume.dialog
-
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.plugins.VolumeDialog
-import com.android.systemui.volume.dialog.dagger.VolumeDialogComponent
-import com.android.systemui.volume.dialog.dagger.VolumeDialogPluginComponent
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.cancel
-import kotlinx.coroutines.coroutineScope
-import kotlinx.coroutines.launch
-
-class NewVolumeDialogPlugin
-@Inject
-constructor(
- @Application private val applicationCoroutineScope: CoroutineScope,
- private val volumeDialogPluginComponentFactory: VolumeDialogPluginComponent.Factory,
-) : VolumeDialog {
-
- private var volumeDialogPluginComponent: VolumeDialogPluginComponent? = null
- private var job: Job? = null
-
- override fun init(windowType: Int, callback: VolumeDialog.Callback?) {
- job =
- applicationCoroutineScope.launch {
- coroutineScope {
- volumeDialogPluginComponent = volumeDialogPluginComponentFactory.create(this)
- }
- }
- }
-
- private fun showDialog() {
- val volumeDialogPluginComponent =
- volumeDialogPluginComponent ?: error("Creating dialog before init was called")
- volumeDialogPluginComponent.coroutineScope().launch {
- coroutineScope {
- val volumeDialogComponent: VolumeDialogComponent =
- volumeDialogPluginComponent.volumeDialogComponentFactory().create(this)
- with(volumeDialogComponent.volumeDialog()) {
- setOnDismissListener { volumeDialogComponent.coroutineScope().cancel() }
- show()
- }
- }
- }
- }
-
- override fun destroy() {
- job?.cancel()
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt
index 74e823e..7476c6a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt
@@ -20,15 +20,39 @@
import android.content.Context
import android.os.Bundle
import android.view.ContextThemeWrapper
+import android.view.MotionEvent
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.res.R
+import com.android.systemui.volume.Events
+import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor
+import com.android.systemui.volume.dialog.ui.binder.VolumeDialogBinder
import javax.inject.Inject
-class VolumeDialog @Inject constructor(@Application context: Context) :
- Dialog(ContextThemeWrapper(context, R.style.volume_dialog_theme)) {
+class VolumeDialog
+@Inject
+constructor(
+ @Application context: Context,
+ private val dialogBinder: VolumeDialogBinder,
+ private val visibilityInteractor: VolumeDialogVisibilityInteractor,
+) : Dialog(ContextThemeWrapper(context, R.style.volume_dialog_theme)) {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- setContentView(R.layout.volume_dialog)
+ dialogBinder.bind(this)
+ }
+
+ /**
+ * NOTE: This will be called with ACTION_OUTSIDE MotionEvents for touches that occur outside of
+ * the touchable region of the volume dialog (as returned by [.onComputeInternalInsets]) even if
+ * those touches occurred within the bounds of the volume dialog.
+ */
+ override fun onTouchEvent(event: MotionEvent): Boolean {
+ if (isShowing) {
+ if (event.action == MotionEvent.ACTION_OUTSIDE) {
+ visibilityInteractor.dismissDialog(Events.DISMISS_REASON_TOUCH_OUTSIDE)
+ return true
+ }
+ }
+ return false
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialogPlugin.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialogPlugin.kt
index a2e81d9..4b7a978 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialogPlugin.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialogPlugin.kt
@@ -18,12 +18,10 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.plugins.VolumeDialog
-import com.android.systemui.volume.dialog.dagger.VolumeDialogComponent
import com.android.systemui.volume.dialog.dagger.VolumeDialogPluginComponent
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
-import kotlinx.coroutines.cancel
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
@@ -34,31 +32,17 @@
private val volumeDialogPluginComponentFactory: VolumeDialogPluginComponent.Factory,
) : VolumeDialog {
- private var volumeDialogPluginComponent: VolumeDialogPluginComponent? = null
private var job: Job? = null
override fun init(windowType: Int, callback: VolumeDialog.Callback?) {
job =
applicationCoroutineScope.launch {
coroutineScope {
- volumeDialogPluginComponent = volumeDialogPluginComponentFactory.create(this)
- }
- }
- }
+ val component = volumeDialogPluginComponentFactory.create(this)
- private fun showDialog() {
- val volumeDialogPluginComponent =
- volumeDialogPluginComponent ?: error("Creating dialog before init was called")
- volumeDialogPluginComponent.coroutineScope().launch {
- coroutineScope {
- val volumeDialogComponent: VolumeDialogComponent =
- volumeDialogPluginComponent.volumeDialogComponentFactory().create(this)
- with(volumeDialogComponent.volumeDialog()) {
- setOnDismissListener { volumeDialogComponent.coroutineScope().cancel() }
- show()
+ component.viewModel().activate()
}
}
- }
}
override fun destroy() {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogPluginComponent.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogPluginComponent.kt
index 82612a7..4e0098c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogPluginComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogPluginComponent.kt
@@ -19,6 +19,7 @@
import com.android.systemui.volume.dialog.dagger.module.VolumeDialogPluginModule
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPlugin
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPluginScope
+import com.android.systemui.volume.dialog.ui.viewmodel.VolumeDialogPluginViewModel
import dagger.BindsInstance
import dagger.Subcomponent
import kotlinx.coroutines.CoroutineScope
@@ -31,15 +32,7 @@
@Subcomponent(modules = [VolumeDialogPluginModule::class])
interface VolumeDialogPluginComponent {
- /**
- * Provides a coroutine scope to use inside [VolumeDialogPluginScope].
- * [com.android.systemui.volume.dialog.VolumeDialogPlugin] manages the lifecycle of this scope.
- * It's cancelled when the dialog is disposed. This helps to free occupied resources when volume
- * dialog is not shown.
- */
- @VolumeDialogPlugin fun coroutineScope(): CoroutineScope
-
- fun volumeDialogComponentFactory(): VolumeDialogComponent.Factory
+ fun viewModel(): VolumeDialogPluginViewModel
@Subcomponent.Factory
interface Factory {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt
index ec7c6ce..2e26fd6 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt
@@ -20,7 +20,8 @@
import android.os.Handler
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.plugins.VolumeDialogController
-import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPlugin
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPluginScope
import com.android.systemui.volume.dialog.domain.model.VolumeDialogEventModel
import com.android.systemui.volume.dialog.domain.model.VolumeDialogStateModel
import javax.inject.Inject
@@ -40,12 +41,12 @@
*
* @see VolumeDialogController.Callbacks
*/
-@VolumeDialog
+@VolumeDialogPluginScope
class VolumeDialogCallbacksInteractor
@Inject
constructor(
private val volumeDialogController: VolumeDialogController,
- @VolumeDialog private val coroutineScope: CoroutineScope,
+ @VolumeDialogPlugin private val coroutineScope: CoroutineScope,
@Background private val bgHandler: Handler,
) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogStateInteractor.kt
index dd51108..4a709a44b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogStateInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogStateInteractor.kt
@@ -17,7 +17,8 @@
package com.android.systemui.volume.dialog.domain.interactor
import com.android.systemui.plugins.VolumeDialogController
-import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPlugin
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPluginScope
import com.android.systemui.volume.dialog.domain.model.VolumeDialogEventModel
import com.android.systemui.volume.dialog.domain.model.VolumeDialogStateModel
import javax.inject.Inject
@@ -35,13 +36,13 @@
*
* @see [VolumeDialogController]
*/
-@VolumeDialog
+@VolumeDialogPluginScope
class VolumeDialogStateInteractor
@Inject
constructor(
volumeDialogCallbacksInteractor: VolumeDialogCallbacksInteractor,
private val volumeDialogController: VolumeDialogController,
- @VolumeDialog private val coroutineScope: CoroutineScope,
+ @VolumeDialogPlugin private val coroutineScope: CoroutineScope,
) {
val volumeDialogState: Flow<VolumeDialogStateModel> =
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractor.kt
new file mode 100644
index 0000000..f7d6d90
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractor.kt
@@ -0,0 +1,102 @@
+/*
+ * 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.volume.dialog.domain.interactor
+
+import android.annotation.SuppressLint
+import com.android.systemui.volume.Events
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPlugin
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPluginScope
+import com.android.systemui.volume.dialog.domain.model.VolumeDialogEventModel
+import com.android.systemui.volume.dialog.domain.model.VolumeDialogVisibilityModel
+import javax.inject.Inject
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.update
+
+private val MAX_DIALOG_SHOW_TIME: Duration = 3.seconds
+
+/**
+ * Handles Volume Dialog visibility state. It might change from several sources:
+ * - [com.android.systemui.plugins.VolumeDialogController] requests visibility change;
+ * - it might be dismissed by the inactivity timeout;
+ * - it can be dismissed by the user;
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+@VolumeDialogPluginScope
+class VolumeDialogVisibilityInteractor
+@Inject
+constructor(
+ @VolumeDialogPlugin coroutineScope: CoroutineScope,
+ callbacksInteractor: VolumeDialogCallbacksInteractor,
+) {
+
+ @SuppressLint("SharedFlowCreation")
+ private val mutableDismissDialogEvents = MutableSharedFlow<Unit>()
+ private val mutableDialogVisibility =
+ MutableStateFlow<VolumeDialogVisibilityModel>(VolumeDialogVisibilityModel.Invisible)
+
+ val dialogVisibility: Flow<VolumeDialogVisibilityModel> = mutableDialogVisibility.asStateFlow()
+
+ init {
+ merge(
+ mutableDismissDialogEvents.mapLatest {
+ delay(MAX_DIALOG_SHOW_TIME)
+ VolumeDialogEventModel.DismissRequested(Events.DISMISS_REASON_TIMEOUT)
+ },
+ callbacksInteractor.event,
+ )
+ .onEach { event ->
+ VolumeDialogVisibilityModel.fromEvent(event)?.let { model ->
+ mutableDialogVisibility.value = model
+ if (model is VolumeDialogVisibilityModel.Visible) {
+ resetDismissTimeout()
+ }
+ }
+ }
+ .launchIn(coroutineScope)
+ }
+
+ /**
+ * Dismisses the dialog with a given [reason]. The new state will be emitted in the
+ * [dialogVisibility].
+ */
+ fun dismissDialog(reason: Int) {
+ mutableDialogVisibility.update {
+ if (it is VolumeDialogVisibilityModel.Dismissed) {
+ it
+ } else {
+ VolumeDialogVisibilityModel.Dismissed(reason)
+ }
+ }
+ }
+
+ /** Resets current dialog timeout. */
+ suspend fun resetDismissTimeout() {
+ mutableDismissDialogEvents.emit(Unit)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogVisibilityModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogVisibilityModel.kt
new file mode 100644
index 0000000..646445d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogVisibilityModel.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.volume.dialog.domain.model
+
+/** Models current Volume Dialog visibility state. */
+sealed interface VolumeDialogVisibilityModel {
+
+ /** Dialog is currently visible. */
+ data class Visible(val reason: Int, val keyguardLocked: Boolean, val lockTaskModeState: Int) :
+ VolumeDialogVisibilityModel
+
+ /** Dialog has never been shown. So it's just invisible. */
+ interface Invisible : VolumeDialogVisibilityModel {
+ companion object : Invisible
+ }
+
+ /** Dialog has been shown and then dismissed. */
+ data class Dismissed(val reason: Int) : Invisible
+
+ companion object {
+
+ /**
+ * Creates [VolumeDialogVisibilityModel] from appropriate events and returns null otherwise.
+ */
+ fun fromEvent(event: VolumeDialogEventModel): VolumeDialogVisibilityModel? {
+ return when (event) {
+ is VolumeDialogEventModel.DismissRequested -> Dismissed(event.reason)
+ is VolumeDialogEventModel.ShowRequested ->
+ Visible(event.reason, event.keyguardLocked, event.lockTaskModeState)
+ else -> null
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/domain/VolumeDialogSettingsButtonInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/domain/VolumeDialogSettingsButtonInteractor.kt
new file mode 100644
index 0000000..db19634
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/domain/VolumeDialogSettingsButtonInteractor.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.volume.dialog.settings.domain
+
+import android.app.ActivityManager
+import com.android.app.tracing.coroutines.flow.map
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.volume.Events
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
+import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor
+import com.android.systemui.volume.dialog.domain.model.VolumeDialogVisibilityModel
+import com.android.systemui.volume.panel.domain.interactor.VolumePanelGlobalStateInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.filterIsInstance
+import kotlinx.coroutines.flow.stateIn
+
+@VolumeDialogScope
+class VolumeDialogSettingsButtonInteractor
+@Inject
+constructor(
+ @VolumeDialog private val coroutineScope: CoroutineScope,
+ private val deviceProvisionedController: DeviceProvisionedController,
+ private val volumePanelGlobalStateInteractor: VolumePanelGlobalStateInteractor,
+ private val visibilityInteractor: VolumeDialogVisibilityInteractor,
+) {
+
+ val isVisible: StateFlow<Boolean> =
+ visibilityInteractor.dialogVisibility
+ .filterIsInstance(VolumeDialogVisibilityModel.Visible::class)
+ .map { model ->
+ deviceProvisionedController.isCurrentUserSetup() &&
+ model.lockTaskModeState == ActivityManager.LOCK_TASK_MODE_NONE
+ }
+ .stateIn(coroutineScope, SharingStarted.Eagerly, false)
+
+ fun onButtonClicked() {
+ volumePanelGlobalStateInteractor.setVisible(true)
+ visibilityInteractor.dismissDialog(Events.DISMISS_REASON_SETTINGS_CLICKED)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/binder/VolumeDialogSettingsButtonViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/binder/VolumeDialogSettingsButtonViewBinder.kt
new file mode 100644
index 0000000..ba08876
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/binder/VolumeDialogSettingsButtonViewBinder.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.volume.dialog.settings.ui.binder
+
+import android.view.View
+import com.android.systemui.lifecycle.WindowLifecycleState
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.lifecycle.setSnapshotBinding
+import com.android.systemui.lifecycle.viewModel
+import com.android.systemui.res.R
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
+import com.android.systemui.volume.dialog.settings.ui.viewmodel.VolumeDialogSettingsButtonViewModel
+import javax.inject.Inject
+import kotlinx.coroutines.awaitCancellation
+
+@VolumeDialogScope
+class VolumeDialogSettingsButtonViewBinder
+@Inject
+constructor(private val viewModelFactory: VolumeDialogSettingsButtonViewModel.Factory) {
+
+ fun bind(view: View) {
+ with(view) {
+ val button = requireViewById<View>(R.id.settings)
+ repeatWhenAttached {
+ viewModel(
+ traceName = "VolumeDialogViewBinder",
+ minWindowLifecycleState = WindowLifecycleState.ATTACHED,
+ factory = { viewModelFactory.create() },
+ ) { viewModel ->
+ setSnapshotBinding {
+ visibility = if (viewModel.isVisible) View.VISIBLE else View.GONE
+ button.setOnClickListener { viewModel.onButtonClicked() }
+ }
+
+ awaitCancellation()
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/viewmodel/VolumeDialogSettingsButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/viewmodel/VolumeDialogSettingsButtonViewModel.kt
new file mode 100644
index 0000000..2acc33b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/viewmodel/VolumeDialogSettingsButtonViewModel.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.settings.ui.viewmodel
+
+import androidx.compose.runtime.getValue
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.lifecycle.Hydrator
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
+import com.android.systemui.volume.dialog.settings.domain.VolumeDialogSettingsButtonInteractor
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+class VolumeDialogSettingsButtonViewModel
+@AssistedInject
+constructor(private val interactor: VolumeDialogSettingsButtonInteractor) : ExclusiveActivatable() {
+
+ private val hydrator = Hydrator("VolumeDialog_settings_button")
+
+ val isVisible by hydrator.hydratedStateOf("isVisible", interactor.isVisible)
+
+ override suspend fun onActivated(): Nothing {
+ hydrator.activate()
+ }
+
+ fun onButtonClicked() {
+ interactor.onButtonClicked()
+ }
+
+ @VolumeDialogScope
+ @AssistedFactory
+ interface Factory {
+
+ fun create(): VolumeDialogSettingsButtonViewModel
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/VolumeDialogLogger.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/VolumeDialogLogger.kt
new file mode 100644
index 0000000..59c38c0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/VolumeDialogLogger.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.volume.dialog.shared
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.dagger.VolumeLog
+import com.android.systemui.volume.Events
+import javax.inject.Inject
+
+private const val TAG = "SysUI_VolumeDialog"
+
+/** Logs events related to the Volume Panel. */
+class VolumeDialogLogger @Inject constructor(@VolumeLog private val logBuffer: LogBuffer) {
+
+ fun onShow(reason: Int) {
+ logBuffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ { int1 = reason },
+ { "Show: ${Events.SHOW_REASONS[int1]}" },
+ )
+ }
+
+ fun onDismiss(reason: Int) {
+ logBuffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ { int1 = reason },
+ { "Dismiss: ${Events.DISMISS_REASONS[int1]}" },
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogBinder.kt
new file mode 100644
index 0000000..9c88303
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogBinder.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.ui.binder
+
+import android.app.Dialog
+import android.graphics.Color
+import android.graphics.PixelFormat
+import android.graphics.drawable.ColorDrawable
+import android.view.ViewGroup
+import android.view.Window
+import android.view.WindowManager
+import com.android.systemui.res.R
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
+import com.android.systemui.volume.dialog.settings.ui.binder.VolumeDialogSettingsButtonViewBinder
+import com.android.systemui.volume.dialog.ui.viewmodel.VolumeDialogGravityViewModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+
+@VolumeDialogScope
+class VolumeDialogBinder
+@Inject
+constructor(
+ @VolumeDialog private val coroutineScope: CoroutineScope,
+ private val volumeDialogViewBinder: VolumeDialogViewBinder,
+ private val settingsButtonViewBinder: VolumeDialogSettingsButtonViewBinder,
+ private val gravityViewModel: VolumeDialogGravityViewModel,
+) {
+
+ fun bind(dialog: Dialog) {
+ with(dialog) {
+ setupWindow(window!!)
+ dialog.setContentView(R.layout.volume_dialog)
+
+ settingsButtonViewBinder.bind(dialog.requireViewById(R.id.settings_container))
+ volumeDialogViewBinder.bind(dialog.requireViewById(R.id.volume_dialog_container))
+ }
+ }
+
+ /** Configures [Window] for the [Dialog]. */
+ private fun setupWindow(window: Window) =
+ with(window) {
+ clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
+ addFlags(
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
+ WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
+ WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH or
+ WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
+ )
+ addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY)
+
+ requestFeature(Window.FEATURE_NO_TITLE)
+ setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
+ setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY)
+ setWindowAnimations(-1)
+ setFormat(PixelFormat.TRANSLUCENT)
+
+ attributes =
+ attributes.apply {
+ title = "VolumeDialog" // Not the same as Window#setTitle
+ }
+ setLayout(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
+
+ gravityViewModel.dialogGravity.onEach { window.setGravity(it) }.launchIn(coroutineScope)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt
index 700225d..600d176 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt
@@ -21,15 +21,17 @@
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.lifecycle.setSnapshotBinding
import com.android.systemui.lifecycle.viewModel
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
import com.android.systemui.volume.dialog.ui.viewmodel.VolumeDialogViewModel
import javax.inject.Inject
import kotlinx.coroutines.awaitCancellation
+@VolumeDialogScope
class VolumeDialogViewBinder
@Inject
constructor(private val volumeDialogViewModelFactory: VolumeDialogViewModel.Factory) {
- suspend fun bind(view: View) {
+ fun bind(view: View) {
view.repeatWhenAttached {
view.viewModel(
traceName = "VolumeDialogViewBinder",
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogGravityViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogGravityViewModel.kt
new file mode 100644
index 0000000..df6523c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogGravityViewModel.kt
@@ -0,0 +1,92 @@
+/*
+ * 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.volume.dialog.ui.viewmodel
+
+import android.content.Context
+import android.content.res.Configuration
+import android.view.Gravity
+import androidx.annotation.GravityInt
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.UiBackground
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.DevicePostureController
+import com.android.systemui.statusbar.policy.devicePosture
+import com.android.systemui.statusbar.policy.onConfigChanged
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
+
+@VolumeDialogScope
+class VolumeDialogGravityViewModel
+@Inject
+constructor(
+ @Application private val context: Context,
+ @VolumeDialog private val coroutineScope: CoroutineScope,
+ @UiBackground private val uiBackgroundCoroutineContext: CoroutineContext,
+ configurationController: ConfigurationController,
+ private val devicePostureController: DevicePostureController,
+) {
+
+ @GravityInt private var originalGravity: Int = context.getAbsoluteGravity()
+
+ val dialogGravity: Flow<Int> =
+ combine(
+ devicePostureController.devicePosture(),
+ configurationController.onConfigChanged.onEach { onConfigurationChanged() },
+ ) { devicePosture, configuration ->
+ context.calculateGravity(devicePosture, configuration)
+ }
+ .stateIn(
+ scope = coroutineScope,
+ started = SharingStarted.Eagerly,
+ context.calculateGravity(),
+ )
+
+ private suspend fun onConfigurationChanged() {
+ withContext(uiBackgroundCoroutineContext) { originalGravity = context.getAbsoluteGravity() }
+ }
+
+ @GravityInt
+ private fun Context.calculateGravity(
+ devicePosture: Int = devicePostureController.devicePosture,
+ config: Configuration = resources.configuration,
+ ): Int {
+ val isLandscape = config.orientation == Configuration.ORIENTATION_LANDSCAPE
+ val isHalfOpen = devicePosture == DevicePostureController.DEVICE_POSTURE_HALF_OPENED
+ val gravity =
+ if (isLandscape && isHalfOpen) {
+ originalGravity or Gravity.TOP
+ } else {
+ originalGravity
+ }
+ return getAbsoluteGravity(gravity)
+ }
+}
+
+@GravityInt
+private fun Context.getAbsoluteGravity(
+ gravity: Int = resources.getInteger(R.integer.volume_dialog_gravity)
+): Int = with(resources) { Gravity.getAbsoluteGravity(gravity, configuration.layoutDirection) }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogPluginViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogPluginViewModel.kt
new file mode 100644
index 0000000..8aa0d09
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogPluginViewModel.kt
@@ -0,0 +1,92 @@
+/*
+ * 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.volume.dialog.ui.viewmodel
+
+import android.app.Dialog
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.plugins.VolumeDialogController
+import com.android.systemui.volume.Events
+import com.android.systemui.volume.dialog.dagger.VolumeDialogComponent
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPluginScope
+import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor
+import com.android.systemui.volume.dialog.domain.model.VolumeDialogVisibilityModel
+import com.android.systemui.volume.dialog.shared.VolumeDialogLogger
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.suspendCancellableCoroutine
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@VolumeDialogPluginScope
+class VolumeDialogPluginViewModel
+@Inject
+constructor(
+ private val componentFactory: VolumeDialogComponent.Factory,
+ private val dialogVisibilityInteractor: VolumeDialogVisibilityInteractor,
+ private val controller: VolumeDialogController,
+ private val logger: VolumeDialogLogger,
+) : ExclusiveActivatable() {
+
+ override suspend fun onActivated(): Nothing {
+ coroutineScope {
+ dialogVisibilityInteractor.dialogVisibility
+ .mapLatest { visibilityModel ->
+ with(visibilityModel) {
+ if (this is VolumeDialogVisibilityModel.Visible) {
+ showDialog(reason, keyguardLocked)
+ }
+ if (this is VolumeDialogVisibilityModel.Dismissed) {
+ Events.writeEvent(Events.EVENT_DISMISS_DIALOG, reason)
+ logger.onDismiss(reason)
+ }
+ }
+ }
+ .launchIn(this)
+ }
+ awaitCancellation()
+ }
+
+ suspend fun showDialog(reason: Int, keyguardLocked: Boolean): Unit = coroutineScope {
+ logger.onShow(reason)
+
+ controller.notifyVisible(true)
+
+ val volumeDialogComponent: VolumeDialogComponent = componentFactory.create(this)
+ val dialog =
+ volumeDialogComponent.volumeDialog().apply {
+ setOnDismissListener {
+ volumeDialogComponent.coroutineScope().cancel()
+ dialogVisibilityInteractor.dismissDialog(Events.DISMISS_REASON_UNKNOWN)
+ }
+ }
+ launch { dialog.awaitShow() }
+
+ Events.writeEvent(Events.EVENT_SHOW_DIALOG, reason, keyguardLocked)
+ }
+}
+
+/** Shows [Dialog] until suspend function is cancelled. */
+private suspend fun Dialog.awaitShow() =
+ suspendCancellableCoroutine<Unit> {
+ show()
+ it.invokeOnCancellation { dismiss() }
+ }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt
index f9e91ae..30c8c15 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt
@@ -19,11 +19,12 @@
import com.android.systemui.lifecycle.ExclusiveActivatable
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
+import kotlinx.coroutines.awaitCancellation
class VolumeDialogViewModel @AssistedInject constructor() : ExclusiveActivatable() {
override suspend fun onActivated(): Nothing {
- TODO("Not yet implemented")
+ awaitCancellation()
}
@AssistedFactory
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 5f6ad92..02d0b57 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -61,7 +61,7 @@
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.wm.shell.desktopmode.DesktopMode;
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.onehanded.OneHandedEventCallback;
import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
@@ -393,7 +393,7 @@
void initDesktopMode(DesktopMode desktopMode) {
desktopMode.addVisibleTasksListener(
- new DesktopModeTaskRepository.VisibleTasksListener() {
+ new DesktopRepository.VisibleTasksListener() {
@Override
public void onTasksVisibilityChanged(int displayId, int visibleTasksCount) {
if (displayId == Display.DEFAULT_DISPLAY) {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
index 9cd5215..8206c21 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
@@ -85,8 +85,9 @@
`when`(telephonyManager.createForSubscriptionId(anyInt())).thenReturn(telephonyManager)
`when`(telephonyManager.supplyIccLockPin(anyString())).thenReturn(mock())
simPinView =
- LayoutInflater.from(context).inflate(R.layout.keyguard_sim_pin_view, null)
- as KeyguardSimPinView
+ LayoutInflater.from(context)
+ .inflate(R.layout.keyguard_sim_pin_view, null)
+ .requireViewById(R.id.keyguard_sim_pin_view) as KeyguardSimPinView
val fakeFeatureFlags = FakeFeatureFlags()
val keyguardKeyboardInteractor = KeyguardKeyboardInteractor(FakeKeyboardRepository())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
index 8075d11..6061063 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
@@ -19,6 +19,7 @@
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static com.android.systemui.Flags.FLAG_CLIPBOARD_SHARED_TRANSITIONS;
+import static com.android.systemui.Flags.FLAG_CLIPBOARD_USE_DESCRIPTION_MIMETYPE;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_SHOWN;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISS_TAPPED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_EXPANDED_FROM_MINIMIZED;
@@ -166,6 +167,7 @@
}
@Test
+ @DisableFlags(FLAG_CLIPBOARD_USE_DESCRIPTION_MIMETYPE)
public void test_setClipData_invalidImageData_legacy() {
initController();
@@ -238,6 +240,7 @@
}
@Test
+ @DisableFlags(FLAG_CLIPBOARD_USE_DESCRIPTION_MIMETYPE)
public void test_setClipData_invalidImageData() {
initController();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
index ca518f9..c7da03d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.qs.tiles
import android.os.Handler
+import android.os.Looper
import android.service.quicksettings.Tile
import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -31,9 +32,10 @@
import com.android.systemui.qs.QsEventLogger
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor
+import com.android.systemui.recordissue.IssueRecordingServiceConnection
import com.android.systemui.recordissue.IssueRecordingState
import com.android.systemui.recordissue.RecordIssueDialogDelegate
-import com.android.systemui.recordissue.TraceurMessageSender
+import com.android.systemui.recordissue.TraceurConnection
import com.android.systemui.res.R
import com.android.systemui.screenrecord.RecordingController
import com.android.systemui.settings.UserContextProvider
@@ -75,13 +77,14 @@
@Mock private lateinit var panelInteractor: PanelInteractor
@Mock private lateinit var userContextProvider: UserContextProvider
@Mock private lateinit var issueRecordingState: IssueRecordingState
- @Mock private lateinit var traceurMessageSender: TraceurMessageSender
@Mock private lateinit var delegateFactory: RecordIssueDialogDelegate.Factory
@Mock private lateinit var dialogDelegate: RecordIssueDialogDelegate
@Mock private lateinit var dialog: SystemUIDialog
private lateinit var testableLooper: TestableLooper
private lateinit var tile: RecordIssueTile
+ private lateinit var irsConnProvider: IssueRecordingServiceConnection.Provider
+ private lateinit var traceurConnProvider: TraceurConnection.Provider
@Before
fun setUp() {
@@ -90,6 +93,10 @@
whenever(delegateFactory.create(any())).thenReturn(dialogDelegate)
whenever(dialogDelegate.createDialog()).thenReturn(dialog)
+ irsConnProvider = IssueRecordingServiceConnection.Provider(userContextProvider)
+ traceurConnProvider =
+ TraceurConnection.Provider(userContextProvider, Looper.getMainLooper())
+
testableLooper = TestableLooper.get(this)
tile =
RecordIssueTile(
@@ -107,7 +114,8 @@
dialogLauncherAnimator,
panelInteractor,
userContextProvider,
- traceurMessageSender,
+ irsConnProvider,
+ traceurConnProvider,
Executors.newSingleThreadExecutor(),
issueRecordingState,
delegateFactory,
@@ -169,7 +177,7 @@
.executeWhenUnlocked(
isA(ActivityStarter.OnDismissAction::class.java),
eq(false),
- eq(true)
+ eq(true),
)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDataTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDataTest.kt
index 3ed0977..1d74e8b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDataTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDataTest.kt
@@ -17,12 +17,12 @@
package com.android.systemui.screenshot
import android.content.ComponentName
-import androidx.test.ext.junit.runners.AndroidJUnit4
import android.graphics.Insets
import android.graphics.Rect
import android.os.UserHandle
import android.view.Display
import android.view.WindowManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.internal.util.ScreenshotRequest
import com.google.common.truth.Truth.assertThat
import org.junit.Test
@@ -71,15 +71,6 @@
}
@Test
- fun testNegativeUserId() {
- val request = ScreenshotRequest.Builder(type, source).setUserId(-1).build()
-
- val data = ScreenshotData.fromRequest(request)
-
- assertThat(data.userHandle).isNull()
- }
-
- @Test
fun testPackageNameAsString() {
val request = ScreenshotRequest.Builder(type, source).setTopComponent(component).build()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
index 0bea560..27e9f07 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
@@ -20,6 +20,7 @@
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.display.data.repository.FakeDisplayRepository
+import com.android.systemui.display.data.repository.FakeFocusedDisplayRepository
import com.android.systemui.display.data.repository.display
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
@@ -58,6 +59,7 @@
private val testScope = TestScope(UnconfinedTestDispatcher())
private val eventLogger = UiEventLoggerFake()
private val headlessHandler = mock<HeadlessScreenshotHandler>()
+ private val focusedDisplayRepository = FakeFocusedDisplayRepository()
private val screenshotExecutor =
TakeScreenshotExecutorImpl(
@@ -68,6 +70,7 @@
eventLogger,
notificationControllerFactory,
headlessHandler,
+ focusedDisplayRepository,
)
@Before
@@ -309,6 +312,59 @@
}
@Test
+ @EnableFlags(Flags.FLAG_SCREENSHOT_MULTIDISPLAY_FOCUS_CHANGE)
+ fun executeScreenshots_keyOther_usesFocusedDisplay() =
+ testScope.runTest {
+ val displayId = 1
+ setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = displayId))
+ val onSaved = { _: Uri? -> }
+ focusedDisplayRepository.emit(displayId)
+
+ screenshotExecutor.executeScreenshots(
+ createScreenshotRequest(
+ source = WindowManager.ScreenshotSource.SCREENSHOT_KEY_OTHER
+ ),
+ onSaved,
+ callback,
+ )
+
+ val dataCaptor = ArgumentCaptor<ScreenshotData>()
+
+ verify(controller).handleScreenshot(dataCaptor.capture(), any(), any())
+
+ assertThat(dataCaptor.value.displayId).isEqualTo(displayId)
+
+ screenshotExecutor.onDestroy()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SCREENSHOT_MULTIDISPLAY_FOCUS_CHANGE)
+ fun executeScreenshots_keyOtherInvalidDisplay_usesDefault() =
+ testScope.runTest {
+ setDisplays(
+ display(TYPE_INTERNAL, id = Display.DEFAULT_DISPLAY),
+ display(TYPE_EXTERNAL, id = 1),
+ )
+ focusedDisplayRepository.emit(5) // invalid display
+ val onSaved = { _: Uri? -> }
+ screenshotExecutor.executeScreenshots(
+ createScreenshotRequest(
+ source = WindowManager.ScreenshotSource.SCREENSHOT_KEY_OTHER
+ ),
+ onSaved,
+ callback,
+ )
+
+ val dataCaptor = ArgumentCaptor<ScreenshotData>()
+
+ verify(controller).handleScreenshot(dataCaptor.capture(), any(), any())
+
+ assertThat(dataCaptor.value.displayId).isEqualTo(Display.DEFAULT_DISPLAY)
+
+ screenshotExecutor.onDestroy()
+ }
+
+ @Test
fun onDestroy_propagatedToControllers() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt
index bab9bbb..2fcacb9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt
@@ -58,13 +58,13 @@
ScreenshotData(
TAKE_SCREENSHOT_FULLSCREEN,
SCREENSHOT_KEY_CHORD,
- null,
+ UserHandle.CURRENT,
topComponent = null,
screenBounds = Rect(0, 0, 1, 1),
taskId = -1,
insets = Insets.NONE,
bitmap = null,
- displayId = DEFAULT_DISPLAY
+ displayId = DEFAULT_DISPLAY,
)
/* Create a policy request processor with no capture policies */
@@ -75,7 +75,7 @@
policies = emptyList(),
defaultOwner = UserHandle.of(PERSONAL),
defaultComponent = ComponentName("default", "Component"),
- displayTasks = fullScreenWork
+ displayTasks = fullScreenWork,
)
val result = runBlocking { requestProcessor.process(request) }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index f5a90196..0e9ef06 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -556,11 +556,13 @@
return null;
}).when(mView).setOnTouchListener(any(NotificationPanelViewController.TouchHandler.class));
- // Dreaming->Lockscreen
+ // Any edge transition
when(mKeyguardTransitionInteractor.transition(any()))
.thenReturn(emptyFlow());
when(mKeyguardTransitionInteractor.transition(any(), any()))
.thenReturn(emptyFlow());
+
+ // Dreaming->Lockscreen
when(mDreamingToLockscreenTransitionViewModel.getLockscreenAlpha())
.thenReturn(emptyFlow());
when(mDreamingToLockscreenTransitionViewModel.lockscreenTranslationY(anyInt()))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
index 7d5278e..d717fe4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
@@ -48,7 +48,7 @@
private val kosmos =
testKosmos().apply {
fakeKeyguardTransitionRepository =
- FakeKeyguardTransitionRepository(initInLockscreen = false)
+ FakeKeyguardTransitionRepository(initInLockscreen = false, testScope = testScope)
}
private val testScope = kosmos.testScope
private val faceAuthRepository by lazy { kosmos.fakeDeviceEntryFaceAuthRepository }
@@ -146,9 +146,17 @@
}
@Test
+ fun activeRows_noRows_isEmpty() =
+ testScope.runTest {
+ val activeHeadsUpRows by collectLastValue(underTest.activeHeadsUpRowKeys)
+
+ assertThat(activeHeadsUpRows).isEmpty()
+ }
+
+ @Test
fun pinnedRows_noRows_isEmpty() =
testScope.runTest {
- val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRows)
+ val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRowKeys)
assertThat(pinnedHeadsUpRows).isEmpty()
}
@@ -156,7 +164,7 @@
@Test
fun pinnedRows_noPinnedRows_isEmpty() =
testScope.runTest {
- val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRows)
+ val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRowKeys)
// WHEN no rows are pinned
headsUpRepository.setNotifications(
fakeHeadsUpRowRepository("key 0"),
@@ -170,9 +178,27 @@
}
@Test
+ fun activeRows_noPinnedRows_containsAllRows() =
+ testScope.runTest {
+ val activeHeadsUpRows by collectLastValue(underTest.activeHeadsUpRowKeys)
+ // WHEN no rows are pinned
+ val rows =
+ arrayListOf(
+ fakeHeadsUpRowRepository("key 0"),
+ fakeHeadsUpRowRepository("key 1"),
+ fakeHeadsUpRowRepository("key 2"),
+ )
+ headsUpRepository.setNotifications(rows)
+ runCurrent()
+
+ // THEN all rows are present
+ assertThat(activeHeadsUpRows).containsExactly(rows[0], rows[1], rows[2])
+ }
+
+ @Test
fun pinnedRows_hasPinnedRows_containsPinnedRows() =
testScope.runTest {
- val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRows)
+ val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRowKeys)
// WHEN some rows are pinned
val rows =
arrayListOf(
@@ -188,9 +214,27 @@
}
@Test
+ fun pinnedRows_hasPinnedRows_containsAllRows() =
+ testScope.runTest {
+ val activeHeadsUpRows by collectLastValue(underTest.activeHeadsUpRowKeys)
+ // WHEN no rows are pinned
+ val rows =
+ arrayListOf(
+ fakeHeadsUpRowRepository("key 0", isPinned = true),
+ fakeHeadsUpRowRepository("key 1", isPinned = true),
+ fakeHeadsUpRowRepository("key 2"),
+ )
+ headsUpRepository.setNotifications(rows)
+ runCurrent()
+
+ // THEN all rows are present
+ assertThat(activeHeadsUpRows).containsExactly(rows[0], rows[1], rows[2])
+ }
+
+ @Test
fun pinnedRows_rowGetsPinned_containsPinnedRows() =
testScope.runTest {
- val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRows)
+ val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRowKeys)
// GIVEN some rows are pinned
val rows =
arrayListOf(
@@ -210,9 +254,34 @@
}
@Test
+ fun activeRows_rowGetsPinned_containsAllRows() =
+ testScope.runTest {
+ val activeHeadsUpRows by collectLastValue(underTest.activeHeadsUpRowKeys)
+ // GIVEN some rows are pinned
+ val rows =
+ arrayListOf(
+ fakeHeadsUpRowRepository("key 0", isPinned = true),
+ fakeHeadsUpRowRepository("key 1", isPinned = true),
+ fakeHeadsUpRowRepository("key 2"),
+ )
+ headsUpRepository.setNotifications(rows)
+ runCurrent()
+
+ // THEN all rows are present
+ assertThat(activeHeadsUpRows).containsExactly(rows[0], rows[1], rows[2])
+
+ // WHEN all rows gets pinned
+ rows[2].isPinned.value = true
+ runCurrent()
+
+ // THEN no change
+ assertThat(activeHeadsUpRows).containsExactly(rows[0], rows[1], rows[2])
+ }
+
+ @Test
fun pinnedRows_allRowsPinned_containsAllRows() =
testScope.runTest {
- val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRows)
+ val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRowKeys)
// WHEN all rows are pinned
val rows =
arrayListOf(
@@ -228,9 +297,27 @@
}
@Test
+ fun activeRows_allRowsPinned_containsAllRows() =
+ testScope.runTest {
+ val activeHeadsUpRows by collectLastValue(underTest.activeHeadsUpRowKeys)
+ // WHEN all rows are pinned
+ val rows =
+ arrayListOf(
+ fakeHeadsUpRowRepository("key 0", isPinned = true),
+ fakeHeadsUpRowRepository("key 1", isPinned = true),
+ fakeHeadsUpRowRepository("key 2", isPinned = true),
+ )
+ headsUpRepository.setNotifications(rows)
+ runCurrent()
+
+ // THEN no rows are filtered
+ assertThat(activeHeadsUpRows).containsExactly(rows[0], rows[1], rows[2])
+ }
+
+ @Test
fun pinnedRows_rowGetsUnPinned_containsPinnedRows() =
testScope.runTest {
- val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRows)
+ val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRowKeys)
// GIVEN all rows are pinned
val rows =
arrayListOf(
@@ -250,9 +337,31 @@
}
@Test
+ fun activeRows_rowGetsUnPinned_containsAllRows() =
+ testScope.runTest {
+ val activeHeadsUpRows by collectLastValue(underTest.activeHeadsUpRowKeys)
+ // GIVEN all rows are pinned
+ val rows =
+ arrayListOf(
+ fakeHeadsUpRowRepository("key 0", isPinned = true),
+ fakeHeadsUpRowRepository("key 1", isPinned = true),
+ fakeHeadsUpRowRepository("key 2", isPinned = true),
+ )
+ headsUpRepository.setNotifications(rows)
+ runCurrent()
+
+ // WHEN a row gets unpinned
+ rows[0].isPinned.value = false
+ runCurrent()
+
+ // THEN all rows are still present
+ assertThat(activeHeadsUpRows).containsExactly(rows[0], rows[1], rows[2])
+ }
+
+ @Test
fun pinnedRows_rowGetsPinnedAndUnPinned_containsTheSameInstance() =
testScope.runTest {
- val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRows)
+ val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRowKeys)
val rows =
arrayListOf(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
index a099c9d..48608eb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
@@ -35,9 +35,6 @@
import com.android.systemui.res.R
import com.android.systemui.statusbar.notification.ConversationNotificationProcessor
import com.android.systemui.statusbar.notification.collection.NotificationEntry
-import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.InflatedContentViewHolder
-import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.KeepExistingView
-import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.NullContentView
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED
@@ -51,33 +48,23 @@
import com.android.systemui.statusbar.notification.row.shared.NewRemoteViews
import com.android.systemui.statusbar.notification.row.shared.NotificationContentModel
import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor
-import com.android.systemui.statusbar.notification.row.shared.RichOngoingContentModel
-import com.android.systemui.statusbar.notification.row.shared.TimerContentModel
import com.android.systemui.statusbar.policy.InflatedSmartReplyState
import com.android.systemui.statusbar.policy.InflatedSmartReplyViewHolder
import com.android.systemui.statusbar.policy.SmartReplyStateInflater
-import com.google.common.truth.Truth.assertThat
import java.util.concurrent.CountDownLatch
import java.util.concurrent.Executor
import java.util.concurrent.TimeUnit
-import kotlinx.coroutines.DisposableHandle
import org.junit.Assert
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.any
-import org.mockito.kotlin.argThat
-import org.mockito.kotlin.clearInvocations
-import org.mockito.kotlin.doReturn
import org.mockito.kotlin.eq
-import org.mockito.kotlin.inOrder
import org.mockito.kotlin.mock
-import org.mockito.kotlin.never
import org.mockito.kotlin.spy
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
-import org.mockito.kotlin.verifyNoMoreInteractions
import org.mockito.kotlin.whenever
@SmallTest
@@ -118,45 +105,6 @@
}
}
- private var fakeRonContentModel: RichOngoingContentModel? = null
- private val fakeRonExtractor =
- object : RichOngoingNotificationContentExtractor {
- override fun extractContentModel(
- entry: NotificationEntry,
- builder: Notification.Builder,
- systemUIContext: Context,
- packageContext: Context
- ): RichOngoingContentModel? = fakeRonContentModel
- }
-
- private var fakeContractedRonViewHolder: ContentViewInflationResult = NullContentView
- private var fakeExpandedRonViewHolder: ContentViewInflationResult = NullContentView
- private var fakeHeadsUpRonViewHolder: ContentViewInflationResult = NullContentView
- private var fakeRonViewInflater =
- spy(
- object : RichOngoingNotificationViewInflater {
- override fun inflateView(
- contentModel: RichOngoingContentModel,
- existingView: View?,
- entry: NotificationEntry,
- systemUiContext: Context,
- parentView: ViewGroup,
- viewType: RichOngoingNotificationViewType
- ): ContentViewInflationResult =
- when (viewType) {
- RichOngoingNotificationViewType.Contracted -> fakeContractedRonViewHolder
- RichOngoingNotificationViewType.Expanded -> fakeExpandedRonViewHolder
- RichOngoingNotificationViewType.HeadsUp -> fakeHeadsUpRonViewHolder
- }
-
- override fun canKeepView(
- contentModel: RichOngoingContentModel,
- existingView: View?,
- viewType: RichOngoingNotificationViewType
- ): Boolean = false
- }
- )
-
@Before
fun setUp() {
allowTestableLooperAsMainThread()
@@ -167,15 +115,12 @@
.setContentText("Text")
.setStyle(Notification.BigTextStyle().bigText("big text"))
testHelper = NotificationTestHelper(mContext, mDependency)
- testHelper.setDefaultInflationFlags(FLAG_CONTENT_VIEW_ALL)
row = spy(testHelper.createRow(builder.build()))
notificationInflater =
NotificationRowContentBinderImpl(
cache,
mock(),
mock<ConversationNotificationProcessor>(),
- fakeRonExtractor,
- fakeRonViewInflater,
mock(),
smartReplyStateInflater,
layoutInflaterFactoryProvider,
@@ -405,496 +350,6 @@
}
@Test
- fun testRonModelRequiredForRonView() {
- fakeRonContentModel = null
- val contractedRonView = View(context)
- val expandedRonView = View(context)
- val headsUpRonView = View(context)
- fakeContractedRonViewHolder =
- InflatedContentViewHolder(view = contractedRonView, binder = mock())
- fakeExpandedRonViewHolder =
- InflatedContentViewHolder(view = expandedRonView, binder = mock())
- fakeHeadsUpRonViewHolder = InflatedContentViewHolder(view = headsUpRonView, binder = mock())
-
- // WHEN inflater inflates
- val contentToInflate =
- FLAG_CONTENT_VIEW_CONTRACTED or FLAG_CONTENT_VIEW_EXPANDED or FLAG_CONTENT_VIEW_HEADS_UP
- inflateAndWait(notificationInflater, contentToInflate, row)
- verifyNoMoreInteractions(fakeRonViewInflater)
- }
-
- @Test
- fun testRonModelCleansUpRemoteViews() {
- val ronView = View(context)
-
- val entry = row.entry
-
- fakeRonContentModel = mock<TimerContentModel>()
- fakeContractedRonViewHolder =
- InflatedContentViewHolder(view = ronView, binder = mock<DeferredContentViewBinder>())
-
- // WHEN inflater inflates
- inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row)
-
- // VERIFY
- verify(cache).removeCachedView(eq(entry), eq(FLAG_CONTENT_VIEW_CONTRACTED))
- verify(cache).removeCachedView(eq(entry), eq(FLAG_CONTENT_VIEW_EXPANDED))
- verify(cache).removeCachedView(eq(entry), eq(FLAG_CONTENT_VIEW_HEADS_UP))
- }
-
- @Test
- fun testRonModelCleansUpSmartReplies() {
- val ronView = View(context)
-
- val privateLayout = spy(row.privateLayout)
-
- row.privateLayout = privateLayout
-
- fakeRonContentModel = mock<TimerContentModel>()
- fakeContractedRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mock())
-
- // WHEN inflater inflates
- inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row)
-
- // VERIFY
- verify(privateLayout).setExpandedInflatedSmartReplies(eq(null))
- verify(privateLayout).setHeadsUpInflatedSmartReplies(eq(null))
- }
-
- @Test
- fun testRonModelTriggersInflationOfContractedRonView() {
- val mockRonModel = mock<TimerContentModel>()
- val ronView = View(context)
- val mockBinder = mock<DeferredContentViewBinder>()
-
- val entry = row.entry
- val privateLayout = row.privateLayout
-
- fakeRonContentModel = mockRonModel
- fakeContractedRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder)
-
- // WHEN inflater inflates
- inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row)
-
- // VERIFY that the inflater is invoked
- verify(fakeRonViewInflater)
- .inflateView(
- eq(mockRonModel),
- any(),
- eq(entry),
- any(),
- eq(privateLayout),
- eq(RichOngoingNotificationViewType.Contracted)
- )
- assertThat(row.privateLayout.contractedChild).isSameInstanceAs(ronView)
- verify(mockBinder).setupContentViewBinder()
- }
-
- @Test
- fun testRonModelTriggersInflationOfExpandedRonView() {
- val mockRonModel = mock<TimerContentModel>()
- val ronView = View(context)
- val mockBinder = mock<DeferredContentViewBinder>()
-
- val entry = row.entry
- val privateLayout = row.privateLayout
-
- fakeRonContentModel = mockRonModel
- fakeExpandedRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder)
-
- // WHEN inflater inflates
- inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_EXPANDED, row)
-
- // VERIFY that the inflater is invoked
- verify(fakeRonViewInflater)
- .inflateView(
- eq(mockRonModel),
- any(),
- eq(entry),
- any(),
- eq(privateLayout),
- eq(RichOngoingNotificationViewType.Expanded)
- )
- assertThat(row.privateLayout.expandedChild).isSameInstanceAs(ronView)
- verify(mockBinder).setupContentViewBinder()
- }
-
- @Test
- fun testRonModelTriggersInflationOfHeadsUpRonView() {
- val mockRonModel = mock<TimerContentModel>()
- val ronView = View(context)
- val mockBinder = mock<DeferredContentViewBinder>()
-
- val entry = row.entry
- val privateLayout = row.privateLayout
-
- fakeRonContentModel = mockRonModel
- fakeHeadsUpRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder)
-
- // WHEN inflater inflates
- inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_HEADS_UP, row)
-
- // VERIFY that the inflater is invoked
- verify(fakeRonViewInflater)
- .inflateView(
- eq(mockRonModel),
- any(),
- eq(entry),
- any(),
- eq(privateLayout),
- eq(RichOngoingNotificationViewType.HeadsUp)
- )
- assertThat(row.privateLayout.headsUpChild).isSameInstanceAs(ronView)
- verify(mockBinder).setupContentViewBinder()
- }
-
- @Test
- fun keepExistingViewForContractedRonNotChangingContractedChild() {
- val oldHandle = mock<DisposableHandle>()
- val mockRonModel = mock<TimerContentModel>()
-
- row.privateLayout.mContractedBinderHandle = oldHandle
- val entry = spy(row.entry)
- row.entry = entry
- val privateLayout = spy(row.privateLayout)
- row.privateLayout = privateLayout
-
- fakeRonContentModel = mockRonModel
- fakeContractedRonViewHolder = KeepExistingView
-
- // WHEN inflater inflates
- inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row)
-
- // THEN do not dispose old contracted binder handle and change contracted child
- verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
- verifyNoMoreInteractions(oldHandle)
- verify(privateLayout, never()).setContractedChild(any())
- }
-
- @Test
- fun keepExistingViewForExpandedRonNotChangingExpandedChild() {
- val oldHandle = mock<DisposableHandle>()
- val mockRonModel = mock<TimerContentModel>()
-
- row.privateLayout.mExpandedBinderHandle = oldHandle
- val entry = spy(row.entry)
- row.entry = entry
- val privateLayout = spy(row.privateLayout)
- row.privateLayout = privateLayout
-
- fakeRonContentModel = mockRonModel
- fakeExpandedRonViewHolder = KeepExistingView
-
- // WHEN inflater inflates
- inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_EXPANDED, row)
-
- // THEN do not dispose old expanded binder handle and change expanded child
- verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
- verifyNoMoreInteractions(oldHandle)
- verify(privateLayout, never()).setExpandedChild(any())
- }
-
- @Test
- fun keepExistingViewForHeadsUpRonNotChangingHeadsUpChild() {
- val oldHandle = mock<DisposableHandle>()
- val mockRonModel = mock<TimerContentModel>()
-
- row.privateLayout.mHeadsUpBinderHandle = oldHandle
- val entry = spy(row.entry)
- row.entry = entry
- val privateLayout = spy(row.privateLayout)
- row.privateLayout = privateLayout
-
- fakeRonContentModel = mockRonModel
- fakeHeadsUpRonViewHolder = KeepExistingView
-
- // WHEN inflater inflates
- inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_HEADS_UP, row)
-
- // THEN - do not dispose old heads up binder handle and change heads up child
- verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
- verifyNoMoreInteractions(oldHandle)
- verify(privateLayout, never()).setHeadsUpChild(any())
- }
-
- @Test
- fun nullContentViewForContractedRonAppliesElementsInOrder() {
- val oldHandle = mock<DisposableHandle>()
- val mockRonModel = mock<TimerContentModel>()
-
- row.privateLayout.mContractedBinderHandle = oldHandle
- val entry = spy(row.entry)
- row.entry = entry
- val privateLayout = spy(row.privateLayout)
- row.privateLayout = privateLayout
-
- fakeRonContentModel = mockRonModel
- fakeContractedRonViewHolder = NullContentView
-
- // WHEN inflater inflates
- inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row)
-
- // Validate that these 4 steps happen in this precise order
- inOrder(oldHandle, entry, privateLayout, cache) {
- verify(oldHandle).dispose()
- verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
- verify(privateLayout).setContractedChild(eq(null))
- }
- }
-
- @Test
- fun nullContentViewForExpandedRonAppliesElementsInOrder() {
- val oldHandle = mock<DisposableHandle>()
- val mockRonModel = mock<TimerContentModel>()
-
- row.privateLayout.mExpandedBinderHandle = oldHandle
- val entry = spy(row.entry)
- row.entry = entry
- val privateLayout = spy(row.privateLayout)
- row.privateLayout = privateLayout
-
- fakeRonContentModel = mockRonModel
- fakeExpandedRonViewHolder = NullContentView
-
- // WHEN inflater inflates
- inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_EXPANDED, row)
-
- // Validate that these 4 steps happen in this precise order
- inOrder(oldHandle, entry, privateLayout, cache) {
- verify(oldHandle).dispose()
- verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
- verify(privateLayout).setExpandedChild(eq(null))
- }
- }
-
- @Test
- fun nullContentViewForHeadsUpRonAppliesElementsInOrder() {
- val oldHandle = mock<DisposableHandle>()
- val mockRonModel = mock<TimerContentModel>()
-
- row.privateLayout.mHeadsUpBinderHandle = oldHandle
- val entry = spy(row.entry)
- row.entry = entry
- val privateLayout = spy(row.privateLayout)
- row.privateLayout = privateLayout
-
- fakeRonContentModel = mockRonModel
- fakeHeadsUpRonViewHolder = NullContentView
-
- // WHEN inflater inflates
- inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_HEADS_UP, row)
-
- // Validate that these 4 steps happen in this precise order
- inOrder(oldHandle, entry, privateLayout, cache) {
- verify(oldHandle).dispose()
- verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
- verify(privateLayout).setHeadsUpChild(eq(null))
- }
- }
-
- @Test
- fun contractedRonViewAppliesElementsInOrder() {
- val oldHandle = mock<DisposableHandle>()
- val mockRonModel = mock<TimerContentModel>()
- val ronView = View(context)
- val mockBinder = mock<DeferredContentViewBinder>()
-
- row.privateLayout.mContractedBinderHandle = oldHandle
- val entry = spy(row.entry)
- row.entry = entry
- val privateLayout = spy(row.privateLayout)
- row.privateLayout = privateLayout
-
- fakeRonContentModel = mockRonModel
- fakeContractedRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder)
-
- // WHEN inflater inflates
- inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row)
-
- // Validate that these 4 steps happen in this precise order
- inOrder(oldHandle, entry, privateLayout, mockBinder) {
- verify(oldHandle).dispose()
- verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
- verify(privateLayout).setContractedChild(eq(ronView))
- verify(mockBinder).setupContentViewBinder()
- }
- }
-
- @Test
- fun expandedRonViewAppliesElementsInOrder() {
- val oldHandle = mock<DisposableHandle>()
- val mockRonModel = mock<TimerContentModel>()
- val ronView = View(context)
- val mockBinder = mock<DeferredContentViewBinder>()
-
- row.privateLayout.mExpandedBinderHandle = oldHandle
- val entry = spy(row.entry)
- row.entry = entry
- val privateLayout = spy(row.privateLayout)
- row.privateLayout = privateLayout
-
- fakeRonContentModel = mockRonModel
- fakeExpandedRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder)
-
- // WHEN inflater inflates
- inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_EXPANDED, row)
-
- // Validate that these 4 steps happen in this precise order
- inOrder(oldHandle, entry, privateLayout, mockBinder) {
- verify(oldHandle).dispose()
- verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
- verify(privateLayout).setExpandedChild(eq(ronView))
- verify(mockBinder).setupContentViewBinder()
- }
- }
-
- @Test
- fun headsUpRonViewAppliesElementsInOrder() {
- val oldHandle = mock<DisposableHandle>()
- val mockRonModel = mock<TimerContentModel>()
- val ronView = View(context)
- val mockBinder = mock<DeferredContentViewBinder>()
-
- row.privateLayout.mHeadsUpBinderHandle = oldHandle
- val entry = spy(row.entry)
- row.entry = entry
- val privateLayout = spy(row.privateLayout)
- row.privateLayout = privateLayout
-
- fakeRonContentModel = mockRonModel
- fakeHeadsUpRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder)
-
- // WHEN inflater inflates
- inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_HEADS_UP, row)
-
- // Validate that these 4 steps happen in this precise order
- inOrder(oldHandle, entry, privateLayout, mockBinder) {
- verify(oldHandle).dispose()
- verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
- verify(privateLayout).setHeadsUpChild(eq(ronView))
- verify(mockBinder).setupContentViewBinder()
- }
- }
-
- @Test
- fun testRonNotReinflating() {
- val oldContractedBinderHandle = mock<DisposableHandle>()
- val oldExpandedBinderHandle = mock<DisposableHandle>()
- val oldHeadsUpBinderHandle = mock<DisposableHandle>()
-
- val contractedBinderHandle = mock<DisposableHandle>()
- val expandedBinderHandle = mock<DisposableHandle>()
- val headsUpBinderHandle = mock<DisposableHandle>()
-
- val contractedRonView = View(context)
- val expandedRonView = View(context)
- val headsUpRonView = View(context)
-
- val mockRonModel1 = mock<TimerContentModel>()
- val mockRonModel2 = mock<TimerContentModel>()
-
- val mockContractedViewBinder = mock<DeferredContentViewBinder>()
- val mockExpandedViewBinder = mock<DeferredContentViewBinder>()
- val mockHeadsUpViewBinder = mock<DeferredContentViewBinder>()
-
- doReturn(contractedBinderHandle).whenever(mockContractedViewBinder).setupContentViewBinder()
- doReturn(expandedBinderHandle).whenever(mockExpandedViewBinder).setupContentViewBinder()
- doReturn(headsUpBinderHandle).whenever(mockHeadsUpViewBinder).setupContentViewBinder()
-
- row.privateLayout.mContractedBinderHandle = oldContractedBinderHandle
- row.privateLayout.mExpandedBinderHandle = oldExpandedBinderHandle
- row.privateLayout.mHeadsUpBinderHandle = oldHeadsUpBinderHandle
- val entry = spy(row.entry)
- row.entry = entry
- val privateLayout = spy(row.privateLayout)
- row.privateLayout = privateLayout
-
- // WHEN inflater inflates both a model and a view
- fakeRonContentModel = mockRonModel1
- fakeContractedRonViewHolder =
- InflatedContentViewHolder(view = contractedRonView, binder = mockContractedViewBinder)
- fakeExpandedRonViewHolder =
- InflatedContentViewHolder(view = expandedRonView, binder = mockExpandedViewBinder)
- fakeHeadsUpRonViewHolder =
- InflatedContentViewHolder(view = headsUpRonView, binder = mockHeadsUpViewBinder)
-
- val contentToInflate =
- FLAG_CONTENT_VIEW_CONTRACTED or FLAG_CONTENT_VIEW_EXPANDED or FLAG_CONTENT_VIEW_HEADS_UP
- inflateAndWait(notificationInflater, contentToInflate, row)
-
- // Validate that these 4 steps happen in this precise order
- inOrder(
- oldContractedBinderHandle,
- oldExpandedBinderHandle,
- oldHeadsUpBinderHandle,
- entry,
- privateLayout,
- mockContractedViewBinder,
- mockExpandedViewBinder,
- mockHeadsUpViewBinder,
- contractedBinderHandle,
- expandedBinderHandle,
- headsUpBinderHandle
- ) {
- verify(oldContractedBinderHandle).dispose()
- verify(oldExpandedBinderHandle).dispose()
- verify(oldHeadsUpBinderHandle).dispose()
-
- verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel1 })
-
- verify(privateLayout).setContractedChild(eq(contractedRonView))
- verify(mockContractedViewBinder).setupContentViewBinder()
-
- verify(privateLayout).setExpandedChild(eq(expandedRonView))
- verify(mockExpandedViewBinder).setupContentViewBinder()
-
- verify(privateLayout).setHeadsUpChild(eq(headsUpRonView))
- verify(mockHeadsUpViewBinder).setupContentViewBinder()
-
- verify(contractedBinderHandle, never()).dispose()
- verify(expandedBinderHandle, never()).dispose()
- verify(headsUpBinderHandle, never()).dispose()
- }
-
- clearInvocations(
- oldContractedBinderHandle,
- oldExpandedBinderHandle,
- oldHeadsUpBinderHandle,
- entry,
- privateLayout,
- mockContractedViewBinder,
- mockExpandedViewBinder,
- mockHeadsUpViewBinder,
- contractedBinderHandle,
- expandedBinderHandle,
- headsUpBinderHandle
- )
-
- // THEN when the inflater inflates just a model
- fakeRonContentModel = mockRonModel2
- fakeContractedRonViewHolder = KeepExistingView
- fakeExpandedRonViewHolder = KeepExistingView
- fakeHeadsUpRonViewHolder = KeepExistingView
-
- inflateAndWait(notificationInflater, contentToInflate, row)
-
- // Validate that for reinflation, the only thing we do us update the model
- verify(contractedBinderHandle, never()).dispose()
- verify(expandedBinderHandle, never()).dispose()
- verify(headsUpBinderHandle, never()).dispose()
- verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel2 })
- verify(privateLayout, never()).setContractedChild(any())
- verify(privateLayout, never()).setExpandedChild(any())
- verify(privateLayout, never()).setHeadsUpChild(any())
- verify(mockContractedViewBinder, never()).setupContentViewBinder()
- verify(mockExpandedViewBinder, never()).setupContentViewBinder()
- verify(mockHeadsUpViewBinder, never()).setupContentViewBinder()
- verify(contractedBinderHandle, never()).dispose()
- verify(expandedBinderHandle, never()).dispose()
- verify(headsUpBinderHandle, never()).dispose()
- }
-
- @Test
fun testNotificationViewHeightTooSmallFailsValidation() {
val validationError =
getValidationError(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index 75376e6..2340d02 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -200,8 +200,6 @@
mock(NotifRemoteViewCache.class),
mock(NotificationRemoteInputManager.class),
mock(ConversationNotificationProcessor.class),
- mock(RichOngoingNotificationContentExtractor.class),
- mock(RichOngoingNotificationViewInflater.class),
mock(Executor.class),
new MockSmartReplyInflater(),
mock(NotifLayoutInflaterFactory.Provider.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 63a560f..e57e8d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -66,7 +66,6 @@
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerStatusBarViewBinder;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager;
-import com.android.systemui.statusbar.phone.StatusBarLocationPublisher;
import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
import com.android.systemui.statusbar.phone.ui.DarkIconManager;
@@ -98,7 +97,6 @@
private ShadeExpansionStateManager mShadeExpansionStateManager;
private OngoingCallController mOngoingCallController;
private SystemStatusAnimationScheduler mAnimationScheduler;
- private StatusBarLocationPublisher mLocationPublisher;
// Set in instantiate()
private StatusBarIconController mStatusBarIconController;
private KeyguardStateController mKeyguardStateController;
@@ -1181,7 +1179,6 @@
setUpDaggerComponent();
mOngoingCallController = mock(OngoingCallController.class);
mAnimationScheduler = mock(SystemStatusAnimationScheduler.class);
- mLocationPublisher = mock(StatusBarLocationPublisher.class);
mStatusBarIconController = mock(StatusBarIconController.class);
mStatusBarStateController = mock(StatusBarStateController.class);
mKeyguardStateController = mock(KeyguardStateController.class);
@@ -1200,7 +1197,6 @@
mStatusBarFragmentComponentFactory,
mOngoingCallController,
mAnimationScheduler,
- mLocationPublisher,
mShadeExpansionStateManager,
mStatusBarIconController,
mIconManagerFactory,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
index 1302faa..62d221d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
@@ -6,6 +6,7 @@
import android.content.pm.ApplicationInfo
import android.graphics.Bitmap
import android.os.UserHandle
+import com.android.systemui.communal.shared.model.CommunalContentSize
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.communal.widgets.WidgetConfigurator
import kotlinx.coroutines.CoroutineScope
@@ -23,6 +24,10 @@
private var nextWidgetId = 1
+ private fun updateListFromDatabase() {
+ _communalWidgets.value = fakeDatabase.values.sortedWith(compareBy { it.rank }).toList()
+ }
+
fun setCommunalWidgets(inventory: List<CommunalWidgetContentModel>) {
_communalWidgets.value = inventory
}
@@ -49,6 +54,7 @@
rank: Int = 0,
category: Int = AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD,
userId: Int = 0,
+ spanY: Int = CommunalContentSize.HALF.span,
) {
fakeDatabase[appWidgetId] =
CommunalWidgetContentModel.Available(
@@ -66,8 +72,9 @@
}
}
},
+ spanY = spanY,
)
- _communalWidgets.value = fakeDatabase.values.toList()
+ updateListFromDatabase()
}
fun addPendingWidget(
@@ -84,8 +91,9 @@
componentName = ComponentName.unflattenFromString(componentName)!!,
icon = icon,
user = UserHandle(userId),
+ spanY = CommunalContentSize.HALF.span,
)
- _communalWidgets.value = fakeDatabase.values.toList()
+ updateListFromDatabase()
}
override fun deleteWidget(widgetId: Int) {
@@ -93,28 +101,54 @@
_communalWidgets.value = fakeDatabase.values.toList()
}
- override fun updateWidgetSpanY(widgetId: Int, spanY: Int) {
- coroutineScope.launch {
- fakeDatabase[widgetId]?.let { widget ->
+ private fun reorderDatabase(widgetIdToRankMap: Map<Int, Int>) {
+ for ((id, rank) in widgetIdToRankMap) {
+ val widget = fakeDatabase[id] ?: continue
+ fakeDatabase[id] =
when (widget) {
is CommunalWidgetContentModel.Available -> {
- fakeDatabase[widgetId] = widget.copy(spanY = spanY)
+ widget.copy(rank = rank)
}
is CommunalWidgetContentModel.Pending -> {
- fakeDatabase[widgetId] = widget.copy(spanY = spanY)
+ widget.copy(rank = rank)
}
}
- _communalWidgets.value = fakeDatabase.values.toList()
- }
}
}
+ override fun updateWidgetOrder(widgetIdToRankMap: Map<Int, Int>) {
+ reorderDatabase(widgetIdToRankMap)
+ updateListFromDatabase()
+ }
+
+ override fun resizeWidget(appWidgetId: Int, spanY: Int, widgetIdToRankMap: Map<Int, Int>) {
+ val widget = fakeDatabase[appWidgetId] ?: return
+
+ fakeDatabase[appWidgetId] =
+ when (widget) {
+ is CommunalWidgetContentModel.Available -> {
+ widget.copy(spanY = spanY)
+ }
+ is CommunalWidgetContentModel.Pending -> {
+ widget.copy(spanY = spanY)
+ }
+ }
+ reorderDatabase(widgetIdToRankMap)
+ updateListFromDatabase()
+ }
+
override fun restoreWidgets(oldToNewWidgetIdMap: Map<Int, Int>) {}
override fun abortRestoreWidgets() {}
private fun onConfigured(id: Int, providerInfo: AppWidgetProviderInfo, rank: Int) {
- _communalWidgets.value +=
- listOf(CommunalWidgetContentModel.Available(id, providerInfo, rank))
+ fakeDatabase[id] =
+ CommunalWidgetContentModel.Available(
+ appWidgetId = id,
+ providerInfo = providerInfo,
+ rank = rank,
+ spanY = CommunalContentSize.HALF.span,
+ )
+ updateListFromDatabase()
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModelKosmos.kt
similarity index 67%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractorKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModelKosmos.kt
index 3a7d7ba..8422942 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModelKosmos.kt
@@ -14,10 +14,8 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.row.domain.interactor
+package com.android.systemui.communal.ui.viewmodel
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.statusbar.notification.row.data.repository.NotificationRowRepository
-fun Kosmos.getNotificationRowInteractor(repository: NotificationRowRepository) =
- NotificationRowInteractor(repository = repository)
+val Kosmos.resizeableItemFrameViewModel by Kosmos.Fixture { ResizeableItemFrameViewModel() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeFocusedDisplayRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeFocusedDisplayRepository.kt
new file mode 100644
index 0000000..83df5d8
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeFocusedDisplayRepository.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.display.data.repository
+
+import android.view.Display
+import com.android.systemui.dagger.SysUISingleton
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+@SysUISingleton
+/** Fake [FocusedDisplayRepository] for testing. */
+class FakeFocusedDisplayRepository @Inject constructor() : FocusedDisplayRepository {
+ private val flow = MutableStateFlow<Int>(Display.DEFAULT_DISPLAY)
+
+ override val focusedDisplayId: StateFlow<Int>
+ get() = flow.asStateFlow()
+
+ suspend fun emit(focusedDisplay: Int) = flow.emit(focusedDisplay)
+}
+
+@Module
+interface FakeFocusedDisplayRepositoryModule {
+ @Binds fun bindFake(fake: FakeFocusedDisplayRepository): FocusedDisplayRepository
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
index 4d0e603..70b4f79 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
@@ -48,13 +48,35 @@
* with OFF -> GONE. Construct with initInLockscreen = false if your test requires this behavior.
*/
@SysUISingleton
-class FakeKeyguardTransitionRepository(private val initInLockscreen: Boolean = true) :
- KeyguardTransitionRepository {
+class FakeKeyguardTransitionRepository(
+ private val initInLockscreen: Boolean = true,
+
+ /**
+ * If true, calls to [startTransition] will automatically emit STARTED, RUNNING, and FINISHED
+ * transition steps from/to the given states.
+ *
+ * [startTransition] is what the From*TransitionInteractors call, so this more closely emulates
+ * the behavior of the real KeyguardTransitionRepository, and reduces the work needed to
+ * manually set up the repository state in each test. For example, setting dreaming=true will
+ * automatically cause FromDreamingTransitionInteractor to call startTransition(DREAMING), and
+ * then we'll send STARTED/RUNNING/FINISHED DREAMING TransitionSteps.
+ *
+ * If your test needs to make assertions at specific points between STARTED/FINISHED, or if it's
+ * difficult to set up all of the conditions to make the transition interactors actually call
+ * startTransition, then construct a FakeKeyguardTransitionRepository with this value false.
+ */
+ private val sendTransitionStepsOnStartTransition: Boolean = true,
+ private val testScope: TestScope,
+) : KeyguardTransitionRepository {
+
private val _transitions =
MutableSharedFlow<TransitionStep>(replay = 3, onBufferOverflow = BufferOverflow.DROP_OLDEST)
override val transitions: SharedFlow<TransitionStep> = _transitions
- @Inject constructor() : this(initInLockscreen = true)
+ @Inject
+ constructor(
+ testScope: TestScope
+ ) : this(initInLockscreen = true, sendTransitionStepsOnStartTransition = true, testScope)
private val _currentTransitionInfo: MutableStateFlow<TransitionInfo> =
MutableStateFlow(
@@ -287,6 +309,11 @@
override suspend fun startTransition(info: TransitionInfo): UUID? {
_currentTransitionInfo.value = info
+
+ if (sendTransitionStepsOnStartTransition) {
+ sendTransitionSteps(from = info.from, to = info.to, testScope = testScope)
+ }
+
return if (info.animator == null) UUID.randomUUID() else null
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryKosmos.kt
index 3e69e87..e9eea83 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryKosmos.kt
@@ -18,9 +18,14 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import org.mockito.Mockito.spy
var Kosmos.keyguardTransitionRepository: KeyguardTransitionRepository by
Kosmos.Fixture { fakeKeyguardTransitionRepository }
-var Kosmos.fakeKeyguardTransitionRepository by Kosmos.Fixture { FakeKeyguardTransitionRepository() }
+var Kosmos.fakeKeyguardTransitionRepository by
+ Kosmos.Fixture { FakeKeyguardTransitionRepository(testScope = testScope) }
+var Kosmos.fakeKeyguardTransitionRepositorySpy: FakeKeyguardTransitionRepository by
+ Kosmos.Fixture { spy(fakeKeyguardTransitionRepository) }
var Kosmos.realKeyguardTransitionRepository: KeyguardTransitionRepository by
Kosmos.Fixture { KeyguardTransitionRepositoryImpl(testDispatcher) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt
index ef789d1..93a59eb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt
@@ -17,7 +17,7 @@
package com.android.systemui.keyguard.domain.interactor
import com.android.systemui.deviceentry.data.repository.deviceEntryRepository
-import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
@@ -27,7 +27,7 @@
val Kosmos.fromAodTransitionInteractor by
Kosmos.Fixture {
FromAodTransitionInteractor(
- transitionRepository = fakeKeyguardTransitionRepository,
+ transitionRepository = keyguardTransitionRepository,
transitionInteractor = keyguardTransitionInteractor,
internalTransitionInteractor = internalKeyguardTransitionInteractor,
scope = applicationCoroutineScope,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt
index c694114..700d7e9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt
@@ -18,8 +18,8 @@
import com.android.systemui.communal.domain.interactor.communalSceneInteractor
import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.keyguardRepository
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
@@ -29,7 +29,7 @@
val Kosmos.fromGoneTransitionInteractor by
Kosmos.Fixture {
FromGoneTransitionInteractor(
- transitionRepository = fakeKeyguardTransitionRepository,
+ transitionRepository = keyguardTransitionRepository,
transitionInteractor = keyguardTransitionInteractor,
internalTransitionInteractor = internalKeyguardTransitionInteractor,
scope = applicationCoroutineScope,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModelKosmos.kt
new file mode 100644
index 0000000..fc4f3a5
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModelKosmos.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import android.content.applicationContext
+import com.android.systemui.common.ui.domain.interactor.configurationInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@ExperimentalCoroutinesApi
+val Kosmos.deviceEntryBackgroundViewModel by Fixture {
+ DeviceEntryBackgroundViewModel(
+ context = applicationContext,
+ deviceEntryIconViewModel = deviceEntryIconViewModel,
+ configurationInteractor = configurationInteractor,
+ keyguardTransitionInteractor = keyguardTransitionInteractor,
+ alternateBouncerToAodTransitionViewModel = alternateBouncerToAodTransitionViewModel,
+ alternateBouncerToDozingTransitionViewModel = alternateBouncerToDozingTransitionViewModel,
+ aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel,
+ dozingToLockscreenTransitionViewModel = dozingToLockscreenTransitionViewModel,
+ dreamingToAodTransitionViewModel = dreamingToAodTransitionViewModel,
+ dreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel,
+ goneToAodTransitionViewModel = goneToAodTransitionViewModel,
+ goneToDozingTransitionViewModel = goneToDozingTransitionViewModel,
+ goneToLockscreenTransitionViewModel = goneToLockscreenTransitionViewModel,
+ lockscreenToAodTransitionViewModel = lockscreenToAodTransitionViewModel,
+ occludedToAodTransitionViewModel = occludedToAodTransitionViewModel,
+ occludedToDozingTransitionViewModel = occludedToDozingTransitionViewModel,
+ occludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel,
+ offToLockscreenTransitionViewModel = offToLockscreenTransitionViewModel,
+ primaryBouncerToAodTransitionViewModel = primaryBouncerToAodTransitionViewModel,
+ primaryBouncerToDozingTransitionViewModel = primaryBouncerToDozingTransitionViewModel,
+ primaryBouncerToLockscreenTransitionViewModel =
+ primaryBouncerToLockscreenTransitionViewModel,
+ lockscreenToDozingTransitionViewModel = lockscreenToDozingTransitionViewModel,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/FakeVolumeDialogController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/FakeVolumeDialogController.kt
new file mode 100644
index 0000000..e4a2a87
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/FakeVolumeDialogController.kt
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.plugins
+
+import android.media.AudioManager
+import android.media.AudioManager.CsdWarning
+import android.os.Handler
+import android.os.VibrationEffect
+import androidx.core.util.getOrElse
+import java.util.concurrent.CopyOnWriteArraySet
+
+class FakeVolumeDialogController(private val audioManager: AudioManager) : VolumeDialogController {
+
+ var isVisible: Boolean = false
+ private set
+
+ var hasScheduledTouchFeedback: Boolean = false
+ private set
+
+ var vibrationEffect: VibrationEffect? = null
+ private set
+
+ var hasUserActivity: Boolean = false
+ private set
+
+ private var hasVibrator: Boolean = true
+
+ private val state = VolumeDialogController.State()
+ private val callbacks = CopyOnWriteArraySet<VolumeDialogController.Callbacks>()
+
+ override fun setActiveStream(stream: Int) {
+ // ensure streamState existence for the active stream
+ state.states.getOrElse(stream) {
+ VolumeDialogController.StreamState().also { streamState ->
+ state.states.put(stream, streamState)
+ }
+ }
+ state.activeStream = stream
+ }
+
+ override fun setStreamVolume(stream: Int, userLevel: Int) {
+ val streamState =
+ state.states.getOrElse(stream) {
+ VolumeDialogController.StreamState().also { streamState ->
+ state.states.put(stream, streamState)
+ }
+ }
+ streamState.level = userLevel.coerceIn(streamState.levelMin, streamState.levelMax)
+ }
+
+ override fun setRingerMode(ringerModeNormal: Int, external: Boolean) {
+ if (external) {
+ state.ringerModeExternal = ringerModeNormal
+ } else {
+ state.ringerModeInternal = ringerModeNormal
+ }
+ }
+
+ fun setHasVibrator(hasVibrator: Boolean) {
+ this.hasVibrator = hasVibrator
+ }
+
+ override fun hasVibrator(): Boolean = hasVibrator
+
+ override fun vibrate(effect: VibrationEffect) {
+ vibrationEffect = effect
+ }
+
+ override fun scheduleTouchFeedback() {
+ hasScheduledTouchFeedback = true
+ }
+
+ fun resetScheduledTouchFeedback() {
+ hasScheduledTouchFeedback = false
+ }
+
+ override fun getAudioManager(): AudioManager = audioManager
+
+ override fun notifyVisible(visible: Boolean) {
+ isVisible = visible
+ }
+
+ override fun addCallback(callbacks: VolumeDialogController.Callbacks?, handler: Handler?) {
+ this.callbacks.add(callbacks)
+ }
+
+ override fun removeCallback(callbacks: VolumeDialogController.Callbacks?) {
+ this.callbacks.remove(callbacks)
+ }
+
+ override fun userActivity() {
+ hasUserActivity = true
+ }
+
+ fun resetUserActivity() {
+ hasUserActivity = false
+ }
+
+ override fun getState() {
+ callbacks.sendEvent { it.onStateChanged(state) }
+ }
+
+ /** @see com.android.systemui.plugins.VolumeDialogController.Callbacks.onShowRequested */
+ fun onShowRequested(reason: Int, keyguardLocked: Boolean, lockTaskModeState: Int) {
+ callbacks.sendEvent { it.onShowRequested(reason, keyguardLocked, lockTaskModeState) }
+ }
+
+ /** @see com.android.systemui.plugins.VolumeDialogController.Callbacks.onDismissRequested */
+ fun onDismissRequested(reason: Int) {
+ callbacks.sendEvent { it.onDismissRequested(reason) }
+ }
+
+ /**
+ * @see com.android.systemui.plugins.VolumeDialogController.Callbacks.onLayoutDirectionChanged
+ */
+ fun onLayoutDirectionChanged(layoutDirection: Int) {
+ callbacks.sendEvent { it.onLayoutDirectionChanged(layoutDirection) }
+ }
+
+ /** @see com.android.systemui.plugins.VolumeDialogController.Callbacks.onConfigurationChanged */
+ fun onConfigurationChanged() {
+ callbacks.sendEvent { it.onConfigurationChanged() }
+ }
+
+ /** @see com.android.systemui.plugins.VolumeDialogController.Callbacks.onShowVibrateHint */
+ fun onShowVibrateHint() {
+ callbacks.sendEvent { it.onShowVibrateHint() }
+ }
+
+ /** @see com.android.systemui.plugins.VolumeDialogController.Callbacks.onShowSilentHint */
+ fun onShowSilentHint() {
+ callbacks.sendEvent { it.onShowSilentHint() }
+ }
+
+ /** @see com.android.systemui.plugins.VolumeDialogController.Callbacks.onScreenOff */
+ fun onScreenOff() {
+ callbacks.sendEvent { it.onScreenOff() }
+ }
+
+ /** @see com.android.systemui.plugins.VolumeDialogController.Callbacks.onShowSafetyWarning */
+ fun onShowSafetyWarning(flags: Int) {
+ callbacks.sendEvent { it.onShowSafetyWarning(flags) }
+ }
+
+ /**
+ * @see com.android.systemui.plugins.VolumeDialogController.Callbacks.onAccessibilityModeChanged
+ */
+ fun onAccessibilityModeChanged(showA11yStream: Boolean?) {
+ callbacks.sendEvent { it.onAccessibilityModeChanged(showA11yStream) }
+ }
+
+ /** @see com.android.systemui.plugins.VolumeDialogController.Callbacks.onShowCsdWarning */
+ fun onShowCsdWarning(@CsdWarning csdWarning: Int, durationMs: Int) {
+ callbacks.sendEvent { it.onShowCsdWarning(csdWarning, durationMs) }
+ }
+
+ /** @see com.android.systemui.plugins.VolumeDialogController.Callbacks.onVolumeChangedFromKey */
+ fun onVolumeChangedFromKey() {
+ callbacks.sendEvent { it.onVolumeChangedFromKey() }
+ }
+
+ override fun getCaptionsEnabledState(checkForSwitchState: Boolean) {
+ error("Unsupported for the new Volume Dialog")
+ }
+
+ override fun setCaptionsEnabledState(enabled: Boolean) {
+ error("Unsupported for the new Volume Dialog")
+ }
+
+ override fun getCaptionsComponentState(fromTooltip: Boolean) {
+ error("Unsupported for the new Volume Dialog")
+ }
+}
+
+private inline fun CopyOnWriteArraySet<VolumeDialogController.Callbacks>.sendEvent(
+ event: (callback: VolumeDialogController.Callbacks) -> Unit
+) {
+ for (callback in this) {
+ event(callback)
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/VolumeDialogControllerKosmos.kt
similarity index 68%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractorKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/plugins/VolumeDialogControllerKosmos.kt
index 3a7d7ba..2f6d4fa 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/VolumeDialogControllerKosmos.kt
@@ -14,10 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.row.domain.interactor
+package com.android.systemui.plugins
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.statusbar.notification.row.data.repository.NotificationRowRepository
+import org.mockito.kotlin.mock
-fun Kosmos.getNotificationRowInteractor(repository: NotificationRowRepository) =
- NotificationRowInteractor(repository = repository)
+val Kosmos.fakeVolumeDialogController by Kosmos.Fixture { FakeVolumeDialogController(mock {}) }
+var Kosmos.volumeDialogController: VolumeDialogController by
+ Kosmos.Fixture { fakeVolumeDialogController }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt
index dbb3e38..c218ff6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt
@@ -24,6 +24,7 @@
import com.android.systemui.qs.footerActionsController
import com.android.systemui.qs.footerActionsViewModelFactory
import com.android.systemui.qs.panels.domain.interactor.tileSquishinessInteractor
+import com.android.systemui.qs.panels.ui.viewmodel.paginatedGridViewModel
import com.android.systemui.qs.ui.viewmodel.quickSettingsContainerViewModel
import com.android.systemui.shade.largeScreenHeaderHelper
import com.android.systemui.shade.transition.largeScreenShadeInterpolator
@@ -48,6 +49,7 @@
configurationInteractor,
largeScreenHeaderHelper,
tileSquishinessInteractor,
+ paginatedGridViewModel,
lifecycleScope,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
index 020f0a6..4f414d9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
@@ -28,6 +28,7 @@
Scenes.Bouncer,
Scenes.Gone,
Scenes.Communal,
+ Scenes.Dream,
)
}
@@ -49,9 +50,10 @@
Scenes.Gone to 0,
Scenes.Lockscreen to 0,
Scenes.Communal to 1,
- Scenes.Shade to 2,
- Scenes.QuickSettings to 3,
- Scenes.Bouncer to 4,
+ Scenes.Dream to 2,
+ Scenes.Shade to 3,
+ Scenes.QuickSettings to 4,
+ Scenes.Bouncer to 5,
)
SceneContainerConfig(
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelKosmos.kt
index 8fdb948..ca33a86 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelKosmos.kt
@@ -19,6 +19,7 @@
import android.content.applicationContext
import com.android.systemui.dump.dumpManager
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.shared.notifications.domain.interactor.notificationSettingsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor
import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
@@ -30,6 +31,7 @@
zenModeInteractor,
seenNotificationsInteractor,
notificationSettingsInteractor,
+ testDispatcher,
dumpManager,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt
index 2eb1573..fc4f05d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt
@@ -222,8 +222,6 @@
Mockito.mock(NotifRemoteViewCache::class.java, STUB_ONLY),
remoteInputManager,
conversationProcessor,
- Mockito.mock(RichOngoingNotificationContentExtractor::class.java, STUB_ONLY),
- Mockito.mock(RichOngoingNotificationViewInflater::class.java, STUB_ONLY),
Mockito.mock(Executor::class.java, STUB_ONLY),
smartReplyStateInflater,
notifLayoutInflaterFactoryProvider,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/data/repository/NotificationRowRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/data/repository/NotificationRowRepositoryKosmos.kt
deleted file mode 100644
index 84ef4b5..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/data/repository/NotificationRowRepositoryKosmos.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.row.data.repository
-
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.statusbar.notification.row.shared.RichOngoingContentModel
-import kotlinx.coroutines.flow.MutableStateFlow
-
-val Kosmos.fakeNotificationRowRepository by Fixture { FakeNotificationRowRepository() }
-
-class FakeNotificationRowRepository : NotificationRowRepository {
- override val richOngoingContentModel = MutableStateFlow<RichOngoingContentModel?>(null)
-}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModelKosmos.kt
deleted file mode 100644
index 7e51135..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModelKosmos.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.row.ui.viewmodel
-
-import com.android.systemui.dump.dumpManager
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.statusbar.notification.row.data.repository.NotificationRowRepository
-import com.android.systemui.statusbar.notification.row.domain.interactor.getNotificationRowInteractor
-
-fun Kosmos.getEnRouteViewModel(repository: NotificationRowRepository) =
- EnRouteViewModel(
- dumpManager = dumpManager,
- rowInteractor = getNotificationRowInteractor(repository),
- )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModelKosmos.kt
deleted file mode 100644
index 00f45b2..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModelKosmos.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.row.ui.viewmodel
-
-import com.android.systemui.dump.dumpManager
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.statusbar.notification.row.data.repository.NotificationRowRepository
-import com.android.systemui.statusbar.notification.row.domain.interactor.getNotificationRowInteractor
-
-fun Kosmos.getTimerViewModel(repository: NotificationRowRepository) =
- TimerViewModel(
- dumpManager = dumpManager,
- rowInteractor = getNotificationRowInteractor(repository),
- )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractorKosmos.kt
new file mode 100644
index 0000000..db9c48d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractorKosmos.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.domain.interactor
+
+import android.os.Handler
+import android.os.looper
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.plugins.volumeDialogController
+
+val Kosmos.volumeDialogCallbacksInteractor: VolumeDialogCallbacksInteractor by
+ Kosmos.Fixture {
+ VolumeDialogCallbacksInteractor(
+ volumeDialogController = volumeDialogController,
+ coroutineScope = applicationCoroutineScope,
+ bgHandler = Handler(looper),
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorKosmos.kt
similarity index 68%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractorKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorKosmos.kt
index 3a7d7ba..e73539e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorKosmos.kt
@@ -14,10 +14,12 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.row.domain.interactor
+package com.android.systemui.volume.dialog.domain.interactor
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.statusbar.notification.row.data.repository.NotificationRowRepository
+import com.android.systemui.kosmos.applicationCoroutineScope
-fun Kosmos.getNotificationRowInteractor(repository: NotificationRowRepository) =
- NotificationRowInteractor(repository = repository)
+val Kosmos.volumeDialogVisibilityInteractor by
+ Kosmos.Fixture {
+ VolumeDialogVisibilityInteractor(applicationCoroutineScope, volumeDialogCallbacksInteractor)
+ }
diff --git a/ravenwood/TEST_MAPPING b/ravenwood/TEST_MAPPING
index 86246e2..72f62c5 100644
--- a/ravenwood/TEST_MAPPING
+++ b/ravenwood/TEST_MAPPING
@@ -5,7 +5,8 @@
{ "name": "hoststubgen-test-tiny-test" },
{ "name": "hoststubgen-invoke-test" },
{ "name": "RavenwoodMockitoTest_device" },
- { "name": "RavenwoodBivalentTest_device" },
+ // TODO(b/371215487): Re-enable when the test is fixed.
+ // { "name": "RavenwoodBivalentTest_device" },
{ "name": "RavenwoodBivalentInstTest_nonself_inst" },
{ "name": "RavenwoodBivalentInstTest_self_inst_device" },
diff --git a/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java b/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java
index ef795c6..520f050 100644
--- a/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java
+++ b/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java
@@ -37,8 +37,6 @@
private RavenwoodCommonUtils() {
}
- private static final Object sLock = new Object();
-
/**
* If set to "1", we enable the verbose logging.
*
@@ -68,9 +66,6 @@
public static final String RAVENWOOD_VERSION_JAVA_SYSPROP = "android.ravenwood.version";
- // @GuardedBy("sLock")
- private static boolean sIntegrityChecked = false;
-
/**
* @return if we're running on Ravenwood.
*/
diff --git a/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NativeAllocationRegistry.java b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NativeAllocationRegistry.java
index 4e7dc5d..ad86135 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NativeAllocationRegistry.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NativeAllocationRegistry.java
@@ -32,25 +32,20 @@
public static NativeAllocationRegistry createNonmalloced(
ClassLoader classLoader, long freeFunction, long size) {
- return new NativeAllocationRegistry(classLoader, freeFunction, size, false);
+ return new NativeAllocationRegistry(classLoader, freeFunction, size);
}
public static NativeAllocationRegistry createMalloced(
ClassLoader classLoader, long freeFunction, long size) {
- return new NativeAllocationRegistry(classLoader, freeFunction, size, true);
+ return new NativeAllocationRegistry(classLoader, freeFunction, size);
}
public static NativeAllocationRegistry createMalloced(
ClassLoader classLoader, long freeFunction) {
- return new NativeAllocationRegistry(classLoader, freeFunction, 0, true);
+ return new NativeAllocationRegistry(classLoader, freeFunction, 0);
}
public NativeAllocationRegistry(ClassLoader classLoader, long freeFunction, long size) {
- this(classLoader, freeFunction, size, size == 0);
- }
-
- private NativeAllocationRegistry(ClassLoader classLoader, long freeFunction, long size,
- boolean mallocAllocation) {
if (size < 0) {
throw new IllegalArgumentException("Invalid native allocation size: " + size);
}
diff --git a/ravenwood/bivalenttest/Android.bp b/ravenwood/tests/bivalenttest/Android.bp
similarity index 63%
rename from ravenwood/bivalenttest/Android.bp
rename to ravenwood/tests/bivalenttest/Android.bp
index e897735..ac499b9 100644
--- a/ravenwood/bivalenttest/Android.bp
+++ b/ravenwood/tests/bivalenttest/Android.bp
@@ -54,32 +54,34 @@
auto_gen_config: true,
}
-android_test {
- name: "RavenwoodBivalentTest_device",
+// TODO(b/371215487): migrate bivalenttest.ravenizer tests to another architecture
- srcs: [
- "test/**/*.java",
- ],
- static_libs: [
- "junit",
- "truth",
-
- "androidx.annotation_annotation",
- "androidx.test.ext.junit",
- "androidx.test.rules",
-
- "junit-params",
- "platform-parametric-runner-lib",
-
- "ravenwood-junit",
- ],
- jni_libs: [
- "libravenwoodbivalenttest_jni",
- ],
- test_suites: [
- "device-tests",
- ],
- optimize: {
- enabled: false,
- },
-}
+// android_test {
+// name: "RavenwoodBivalentTest_device",
+//
+// srcs: [
+// "test/**/*.java",
+// ],
+// static_libs: [
+// "junit",
+// "truth",
+//
+// "androidx.annotation_annotation",
+// "androidx.test.ext.junit",
+// "androidx.test.rules",
+//
+// "junit-params",
+// "platform-parametric-runner-lib",
+//
+// "ravenwood-junit",
+// ],
+// jni_libs: [
+// "libravenwoodbivalenttest_jni",
+// ],
+// test_suites: [
+// "device-tests",
+// ],
+// optimize: {
+// enabled: false,
+// },
+// }
diff --git a/ravenwood/bivalenttest/AndroidManifest.xml b/ravenwood/tests/bivalenttest/AndroidManifest.xml
similarity index 100%
rename from ravenwood/bivalenttest/AndroidManifest.xml
rename to ravenwood/tests/bivalenttest/AndroidManifest.xml
diff --git a/ravenwood/bivalenttest/AndroidTest.xml b/ravenwood/tests/bivalenttest/AndroidTest.xml
similarity index 100%
rename from ravenwood/bivalenttest/AndroidTest.xml
rename to ravenwood/tests/bivalenttest/AndroidTest.xml
diff --git a/ravenwood/bivalenttest/README.md b/ravenwood/tests/bivalenttest/README.md
similarity index 100%
rename from ravenwood/bivalenttest/README.md
rename to ravenwood/tests/bivalenttest/README.md
diff --git a/ravenwood/bivalenttest/jni/ravenwood_core_test_jni.cpp b/ravenwood/tests/bivalenttest/jni/ravenwood_core_test_jni.cpp
similarity index 100%
rename from ravenwood/bivalenttest/jni/ravenwood_core_test_jni.cpp
rename to ravenwood/tests/bivalenttest/jni/ravenwood_core_test_jni.cpp
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodAndroidApiTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodAndroidApiTest.java
similarity index 100%
rename from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodAndroidApiTest.java
rename to ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodAndroidApiTest.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodClassRuleDeviceOnlyTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodClassRuleDeviceOnlyTest.java
similarity index 100%
rename from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodClassRuleDeviceOnlyTest.java
rename to ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodClassRuleDeviceOnlyTest.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodConfigTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodConfigTest.java
similarity index 100%
rename from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodConfigTest.java
rename to ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodConfigTest.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodJniTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodJniTest.java
similarity index 100%
rename from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodJniTest.java
rename to ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodJniTest.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodMultipleRuleTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodMultipleRuleTest.java
similarity index 100%
rename from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodMultipleRuleTest.java
rename to ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodMultipleRuleTest.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodNativeAllocationRegistryTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodNativeAllocationRegistryTest.java
similarity index 100%
rename from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodNativeAllocationRegistryTest.java
rename to ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodNativeAllocationRegistryTest.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodNoConfigNoRuleTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodNoConfigNoRuleTest.java
similarity index 100%
rename from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodNoConfigNoRuleTest.java
rename to ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodNoConfigNoRuleTest.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodRuleTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodRuleTest.java
similarity index 100%
rename from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodRuleTest.java
rename to ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodRuleTest.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/CallTracker.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/CallTracker.java
similarity index 100%
rename from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/CallTracker.java
rename to ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/CallTracker.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodAwareTestRunnerTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodAwareTestRunnerTest.java
similarity index 100%
rename from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodAwareTestRunnerTest.java
rename to ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodAwareTestRunnerTest.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitClassRuleDeviceOnlyTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitClassRuleDeviceOnlyTest.java
similarity index 100%
rename from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitClassRuleDeviceOnlyTest.java
rename to ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitClassRuleDeviceOnlyTest.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleOrderRewriteTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleOrderRewriteTest.java
similarity index 100%
rename from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleOrderRewriteTest.java
rename to ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleOrderRewriteTest.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleShadowingTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleShadowingTest.java
similarity index 100%
rename from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleShadowingTest.java
rename to ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleShadowingTest.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleShadowingTestBase.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleShadowingTestBase.java
similarity index 100%
rename from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleShadowingTestBase.java
rename to ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleShadowingTestBase.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodNoRavenizerTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodNoRavenizerTest.java
similarity index 100%
rename from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodNoRavenizerTest.java
rename to ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodNoRavenizerTest.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsReallyDisabledTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsReallyDisabledTest.java
similarity index 100%
rename from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsReallyDisabledTest.java
rename to ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsReallyDisabledTest.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsTest.java
similarity index 100%
rename from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsTest.java
rename to ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsTest.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithAndroidXRunnerTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithAndroidXRunnerTest.java
similarity index 100%
rename from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithAndroidXRunnerTest.java
rename to ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithAndroidXRunnerTest.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithJUnitParamsRunnerTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithJUnitParamsRunnerTest.java
similarity index 100%
rename from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithJUnitParamsRunnerTest.java
rename to ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithJUnitParamsRunnerTest.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithParameterizedAndroidJunit4Test.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithParameterizedAndroidJunit4Test.java
similarity index 100%
rename from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithParameterizedAndroidJunit4Test.java
rename to ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithParameterizedAndroidJunit4Test.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodSuiteTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodSuiteTest.java
similarity index 100%
rename from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodSuiteTest.java
rename to ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodSuiteTest.java
diff --git a/ravenwood/tests/coretest/Android.bp b/ravenwood/tests/coretest/Android.bp
index d94475c..85f1baf 100644
--- a/ravenwood/tests/coretest/Android.bp
+++ b/ravenwood/tests/coretest/Android.bp
@@ -17,9 +17,15 @@
"junit-params",
"platform-parametric-runner-lib",
"truth",
+
+ // This library should be removed by Ravenizer
+ "mockito-target-minus-junit4",
],
srcs: [
"test/**/*.java",
],
+ ravenizer: {
+ strip_mockito: true,
+ },
auto_gen_config: true,
}
diff --git a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodMockitoTest.java b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodMockitoTest.java
new file mode 100644
index 0000000..dd6d259
--- /dev/null
+++ b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodMockitoTest.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ravenwoodtest.coretest;
+
+import static org.junit.Assert.assertThrows;
+
+import org.junit.Test;
+
+public class RavenwoodMockitoTest {
+
+ @Test
+ public void checkMockitoClasses() {
+ // DexMaker should not exist
+ assertThrows(
+ ClassNotFoundException.class,
+ () -> Class.forName("com.android.dx.DexMaker"));
+ // Mockito 2 should not exist
+ assertThrows(
+ ClassNotFoundException.class,
+ () -> Class.forName("org.mockito.Matchers"));
+ }
+}
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt
index f7f9a85..49f0b59 100644
--- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt
@@ -85,18 +85,17 @@
/**
* Main class.
*/
-class Ravenizer(val options: RavenizerOptions) {
- fun run() {
+class Ravenizer {
+ fun run(options: RavenizerOptions) {
val stats = RavenizerStats()
- val fatalValidation = options.fatalValidation.get
-
stats.totalTime = log.nTime {
process(
options.inJar.get,
options.outJar.get,
options.enableValidation.get,
- fatalValidation,
+ options.fatalValidation.get,
+ options.stripMockito.get,
stats,
)
}
@@ -108,6 +107,7 @@
outJar: String,
enableValidation: Boolean,
fatalValidation: Boolean,
+ stripMockito: Boolean,
stats: RavenizerStats,
) {
var allClasses = ClassNodes.loadClassStructures(inJar) {
@@ -126,6 +126,9 @@
}
}
}
+ if (includeUnsupportedMockito(allClasses)) {
+ log.w("Unsupported Mockito detected in $inJar}!")
+ }
stats.totalProcessTime = log.vTime("$executableName processing $inJar") {
ZipFile(inJar).use { inZip ->
@@ -145,6 +148,11 @@
)
}
+ if (stripMockito && entry.name.isMockitoFile()) {
+ // Skip this entry
+ continue
+ }
+
val className = zipEntryNameToClassName(entry.name)
if (className != null) {
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerMain.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerMain.kt
index ff41818..aee4530 100644
--- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerMain.kt
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerMain.kt
@@ -36,6 +36,6 @@
log.v("Options: $options")
// Run.
- Ravenizer(options).run()
+ Ravenizer().run(options)
}
}
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt
index 10fe0a3..32dcbe5 100644
--- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt
@@ -47,6 +47,9 @@
/** Whether the validation failure is fatal or not. */
var fatalValidation: SetOnce<Boolean> = SetOnce(false),
+
+ /** Whether to remove mockito and dexmaker classes. */
+ var stripMockito: SetOnce<Boolean> = SetOnce(false),
) {
companion object {
@@ -85,6 +88,9 @@
"--fatal-validation" -> ret.fatalValidation.set(true)
"--no-fatal-validation" -> ret.fatalValidation.set(false)
+ "--strip-mockito" -> ret.stripMockito.set(true)
+ "--no-strip-mockito" -> ret.stripMockito.set(false)
+
else -> throw ArgumentsException("Unknown option: $arg")
}
} catch (e: SetOnce.SetMoreThanOnceException) {
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt
index 1aa70c08..37a7975 100644
--- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt
@@ -100,3 +100,19 @@
// TODO -- anything else?
)
}
+
+/**
+ * Files that should be removed when "--strip-mockito" is set.
+ */
+fun String.isMockitoFile(): Boolean {
+ return this.startsWithAny(
+ "org/mockito/", // Mockito
+ "com/android/dx/", // DexMaker
+ "mockito-extensions/", // DexMaker overrides
+ )
+}
+
+fun includeUnsupportedMockito(classes: ClassNodes): Boolean {
+ return classes.findClass("com/android/dx/DexMaker") != null
+ || classes.findClass("org/mockito/Matchers") != null
+}
diff --git a/services/appfunctions/TEST_MAPPING b/services/appfunctions/TEST_MAPPING
index 91cfa06..851d754 100644
--- a/services/appfunctions/TEST_MAPPING
+++ b/services/appfunctions/TEST_MAPPING
@@ -2,11 +2,6 @@
"presubmit": [
{
"name": "FrameworksAppFunctionsTests"
- }
- ],
- "postsubmit": [
- {
- "name": "FrameworksAppFunctionsTests"
},
{
"name": "CtsAppFunctionTestCases"
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
index d0c3daf..d31ced3 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
@@ -16,8 +16,6 @@
package com.android.server.appfunctions;
-import static android.app.appfunctions.AppFunctionManager.APP_FUNCTION_STATE_DISABLED;
-import static android.app.appfunctions.AppFunctionManager.APP_FUNCTION_STATE_ENABLED;
import static android.app.appfunctions.AppFunctionRuntimeMetadata.APP_FUNCTION_RUNTIME_METADATA_DB;
import static android.app.appfunctions.AppFunctionRuntimeMetadata.APP_FUNCTION_RUNTIME_NAMESPACE;
@@ -363,26 +361,14 @@
callingPackage,
functionIdentifier,
runtimeMetadataSearchSession));
- AppFunctionRuntimeMetadata.Builder newMetadata =
- new AppFunctionRuntimeMetadata.Builder(existingMetadata);
- switch (enabledState) {
- case AppFunctionManager.APP_FUNCTION_STATE_DEFAULT -> {
- newMetadata.setEnabled(null);
- }
- case APP_FUNCTION_STATE_ENABLED -> {
- newMetadata.setEnabled(true);
- }
- case APP_FUNCTION_STATE_DISABLED -> {
- newMetadata.setEnabled(false);
- }
- default ->
- throw new IllegalArgumentException("Value of EnabledState is unsupported.");
- }
+ AppFunctionRuntimeMetadata newMetadata =
+ new AppFunctionRuntimeMetadata.Builder(existingMetadata)
+ .setEnabled(enabledState).build();
AppSearchBatchResult<String, Void> putDocumentBatchResult =
runtimeMetadataSearchSession
.put(
new PutDocumentsRequest.Builder()
- .addGenericDocuments(newMetadata.build())
+ .addGenericDocuments(newMetadata)
.build())
.get();
if (!putDocumentBatchResult.isSuccess()) {
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 81ae717..3bcca1c 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -99,6 +99,7 @@
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ResultReceiver;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.ArrayMap;
@@ -203,6 +204,7 @@
private IVirtualDeviceSoundEffectListener mSoundEffectListener;
private final DisplayManagerGlobal mDisplayManager;
private final DisplayManagerInternal mDisplayManagerInternal;
+ private final PowerManager mPowerManager;
@GuardedBy("mVirtualDeviceLock")
private final Map<IBinder, IntentFilter> mIntentInterceptors = new ArrayMap<>();
@NonNull
@@ -213,6 +215,10 @@
@GuardedBy("mVirtualDeviceLock")
@Nullable
private LocaleList mLocaleList = null;
+ @GuardedBy("mVirtualDeviceLock")
+ private boolean mLockdownActive = false;
+ @GuardedBy("mVirtualDeviceLock")
+ private boolean mRequestedToBeAwake = true;
@NonNull
private final VirtualDevice mPublicVirtualDeviceObject;
@@ -418,6 +424,7 @@
mDevicePolicies = params.getDevicePolicies();
mDisplayManager = displayManager;
mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
+ mPowerManager = context.getSystemService(PowerManager.class);
if (inputController == null) {
mInputController = new InputController(
context.getMainThreadHandler(),
@@ -475,6 +482,20 @@
}
}
+ void onLockdownChanged(boolean lockdownActive) {
+ synchronized (mVirtualDeviceLock) {
+ if (lockdownActive != mLockdownActive) {
+ mLockdownActive = lockdownActive;
+ if (mLockdownActive) {
+ goToSleepInternal(PowerManager.GO_TO_SLEEP_REASON_DISPLAY_GROUPS_TURNED_OFF);
+ } else if (mRequestedToBeAwake) {
+ wakeUpInternal(PowerManager.WAKE_REASON_DISPLAY_GROUP_TURNED_ON,
+ "android.server.companion.virtual:LOCKDOWN_ENDED");
+ }
+ }
+ }
+ }
+
@VisibleForTesting
SensorController getSensorControllerForTest() {
return mSensorController;
@@ -576,8 +597,45 @@
}
@Override // Binder call
+ @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ public void goToSleep() {
+ super.goToSleep_enforcePermission();
+ synchronized (mVirtualDeviceLock) {
+ mRequestedToBeAwake = false;
+ }
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ goToSleepInternal(PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override // Binder call
+ @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ public void wakeUp() {
+ super.wakeUp_enforcePermission();
+ synchronized (mVirtualDeviceLock) {
+ mRequestedToBeAwake = true;
+ if (mLockdownActive) {
+ Slog.w(TAG, "Cannot wake up device during lockdown.");
+ return;
+ }
+ }
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ wakeUpInternal(PowerManager.WAKE_REASON_POWER_BUTTON,
+ "android.server.companion.virtual:DEVICE_ON");
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override // Binder call
+ @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void launchPendingIntent(int displayId, PendingIntent pendingIntent,
ResultReceiver resultReceiver) {
+ super.launchPendingIntent_enforcePermission();
Objects.requireNonNull(pendingIntent);
synchronized (mVirtualDeviceLock) {
if (!mVirtualDisplays.contains(displayId)) {
@@ -1042,7 +1100,9 @@
}
@Override // Binder call
+ @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public int getInputDeviceId(IBinder token) {
+ super.getInputDeviceId_enforcePermission();
final long ident = Binder.clearCallingIdentity();
try {
return mInputController.getInputDeviceId(token);
@@ -1125,7 +1185,9 @@
}
@Override // Binder call
+ @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public PointF getCursorPosition(IBinder token) {
+ super.getCursorPosition_enforcePermission();
final long ident = Binder.clearCallingIdentity();
try {
return mInputController.getCursorPosition(token);
@@ -1411,18 +1473,24 @@
return gwpc;
}
- int createVirtualDisplay(@NonNull VirtualDisplayConfig virtualDisplayConfig,
- @NonNull IVirtualDisplayCallback callback, String packageName) {
+ @Override // Binder call
+ @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ public int createVirtualDisplay(@NonNull VirtualDisplayConfig virtualDisplayConfig,
+ @NonNull IVirtualDisplayCallback callback) {
+ super.createVirtualDisplay_enforcePermission();
GenericWindowPolicyController gwpc;
synchronized (mVirtualDeviceLock) {
gwpc = createWindowPolicyControllerLocked(virtualDisplayConfig.getDisplayCategories());
}
int displayId;
displayId = mDisplayManagerInternal.createVirtualDisplay(virtualDisplayConfig, callback,
- this, gwpc, packageName);
+ this, gwpc, mOwnerPackageName);
boolean isMirrorDisplay =
mDisplayManagerInternal.getDisplayIdToMirror(displayId) != Display.INVALID_DISPLAY;
gwpc.setDisplayId(displayId, isMirrorDisplay);
+ boolean isTrustedDisplay =
+ (mDisplayManagerInternal.getDisplayInfo(displayId).flags & Display.FLAG_TRUSTED)
+ == Display.FLAG_TRUSTED;
boolean showPointer;
synchronized (mVirtualDeviceLock) {
@@ -1433,7 +1501,8 @@
}
PowerManager.WakeLock wakeLock = createAndAcquireWakeLockForDisplay(displayId);
- mVirtualDisplays.put(displayId, new VirtualDisplayWrapper(callback, gwpc, wakeLock));
+ mVirtualDisplays.put(displayId, new VirtualDisplayWrapper(callback, gwpc, wakeLock,
+ isTrustedDisplay, isMirrorDisplay));
showPointer = mDefaultShowPointerIcon;
}
@@ -1444,8 +1513,7 @@
mInputController.setDisplayEligibilityForPointerCapture(/* isEligible= */ false,
displayId);
// WM throws a SecurityException if the display is untrusted.
- if ((mDisplayManagerInternal.getDisplayInfo(displayId).flags & Display.FLAG_TRUSTED)
- == Display.FLAG_TRUSTED) {
+ if (isTrustedDisplay) {
mInputController.setDisplayImePolicy(displayId,
WindowManager.DISPLAY_IME_POLICY_LOCAL);
}
@@ -1511,7 +1579,6 @@
return result;
}
-
void onVirtualDisplayRemoved(int displayId) {
/* This is callback invoked by VirtualDeviceManagerService when VirtualDisplay was released
* by DisplayManager (most probably caused by someone calling VirtualDisplay.close()).
@@ -1562,6 +1629,34 @@
}
}
+ void goToSleepInternal(@PowerManager.GoToSleepReason int reason) {
+ final long now = SystemClock.uptimeMillis();
+ synchronized (mVirtualDeviceLock) {
+ for (int i = 0; i < mVirtualDisplays.size(); i++) {
+ VirtualDisplayWrapper wrapper = mVirtualDisplays.valueAt(i);
+ if (!wrapper.isTrusted() || wrapper.isMirror()) {
+ continue;
+ }
+ int displayId = mVirtualDisplays.keyAt(i);
+ mPowerManager.goToSleep(displayId, now, reason, /* flags= */ 0);
+ }
+ }
+ }
+
+ void wakeUpInternal(@PowerManager.WakeReason int reason, String details) {
+ final long now = SystemClock.uptimeMillis();
+ synchronized (mVirtualDeviceLock) {
+ for (int i = 0; i < mVirtualDisplays.size(); i++) {
+ VirtualDisplayWrapper wrapper = mVirtualDisplays.valueAt(i);
+ if (!wrapper.isTrusted() || wrapper.isMirror()) {
+ continue;
+ }
+ int displayId = mVirtualDisplays.keyAt(i);
+ mPowerManager.wakeUp(now, reason, details, displayId);
+ }
+ }
+ }
+
/**
* Release resources tied to virtual display owned by this VirtualDevice instance.
*
@@ -1665,6 +1760,7 @@
return mInputController.getInputDeviceDescriptors().values().stream().anyMatch(
inputDeviceDescriptor -> inputDeviceDescriptor.getInputDeviceId() == inputDeviceId);
}
+
void playSoundEffect(int effectType) {
try {
mSoundEffectListener.onPlaySoundEffect(effectType);
@@ -1732,13 +1828,17 @@
private final IVirtualDisplayCallback mToken;
private final GenericWindowPolicyController mWindowPolicyController;
private final PowerManager.WakeLock mWakeLock;
+ private final boolean mIsTrusted;
+ private final boolean mIsMirror;
VirtualDisplayWrapper(@NonNull IVirtualDisplayCallback token,
@NonNull GenericWindowPolicyController windowPolicyController,
- @NonNull PowerManager.WakeLock wakeLock) {
+ @NonNull PowerManager.WakeLock wakeLock, boolean isTrusted, boolean isMirror) {
mToken = Objects.requireNonNull(token);
mWindowPolicyController = Objects.requireNonNull(windowPolicyController);
mWakeLock = Objects.requireNonNull(wakeLock);
+ mIsTrusted = isTrusted;
+ mIsMirror = isMirror;
}
GenericWindowPolicyController getWindowPolicyController() {
@@ -1749,6 +1849,14 @@
return mWakeLock;
}
+ boolean isTrusted() {
+ return mIsTrusted;
+ }
+
+ boolean isMirror() {
+ return mIsMirror;
+ }
+
IVirtualDisplayCallback getToken() {
return mToken;
}
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index 3cd1ca4..41b6a85 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -44,8 +44,6 @@
import android.content.Context;
import android.content.Intent;
import android.hardware.display.DisplayManagerInternal;
-import android.hardware.display.IVirtualDisplayCallback;
-import android.hardware.display.VirtualDisplayConfig;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -68,6 +66,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DumpUtils;
+import com.android.internal.widget.LockPatternUtils;
import com.android.modules.expresslog.Counter;
import com.android.server.LocalServices;
import com.android.server.SystemService;
@@ -129,6 +128,26 @@
}
};
+ private class StrongAuthTracker extends LockPatternUtils.StrongAuthTracker {
+ final Set<Integer> mUsersInLockdown = new ArraySet<>();
+
+ StrongAuthTracker(Context context) {
+ super(context);
+ }
+
+ @Override
+ public synchronized void onStrongAuthRequiredChanged(int userId) {
+ if ((getStrongAuthForUser(userId) & STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN) > 0) {
+ if (mUsersInLockdown.add(userId) && mUsersInLockdown.size() == 1) {
+ onLockdownChanged(true);
+ }
+ } else if (mUsersInLockdown.remove(userId) && mUsersInLockdown.isEmpty()) {
+ onLockdownChanged(false);
+ }
+ }
+ }
+ private StrongAuthTracker mStrongAuthTracker;
+
private final RemoteCallbackList<IVirtualDeviceListener> mVirtualDeviceListeners =
new RemoteCallbackList<>();
@@ -201,6 +220,20 @@
+ " will be available.");
}
}
+ if (android.companion.virtualdevice.flags.Flags.deviceAwareDisplayPower()) {
+ mStrongAuthTracker = new StrongAuthTracker(getContext());
+ new LockPatternUtils(getContext()).registerStrongAuthTracker(mStrongAuthTracker);
+ }
+ }
+
+ // Called when the global lockdown state changes, i.e. lockdown is considered active if any user
+ // is in lockdown mode, and inactive if no users are in lockdown mode.
+ void onLockdownChanged(boolean lockdownActive) {
+ synchronized (mVirtualDeviceManagerLock) {
+ for (int i = 0; i < mVirtualDevices.size(); i++) {
+ mVirtualDevices.valueAt(i).onLockdownChanged(lockdownActive);
+ }
+ }
}
void onCameraAccessBlocked(int appUid) {
@@ -505,37 +538,6 @@
}
@Override // Binder call
- public int createVirtualDisplay(VirtualDisplayConfig virtualDisplayConfig,
- IVirtualDisplayCallback callback, IVirtualDevice virtualDevice, String packageName)
- throws RemoteException {
- Objects.requireNonNull(virtualDisplayConfig);
- final int callingUid = getCallingUid();
- if (!PermissionUtils.validateCallingPackageName(getContext(), packageName)) {
- throw new SecurityException(
- "Package name " + packageName + " does not belong to calling uid "
- + callingUid);
- }
- VirtualDeviceImpl virtualDeviceImpl;
- synchronized (mVirtualDeviceManagerLock) {
- virtualDeviceImpl = mVirtualDevices.get(virtualDevice.getDeviceId());
- if (virtualDeviceImpl == null) {
- throw new SecurityException(
- "Invalid VirtualDevice (deviceId = " + virtualDevice.getDeviceId()
- + ")");
- }
- }
- if (virtualDeviceImpl.getOwnerUid() != callingUid) {
- throw new SecurityException(
- "uid " + callingUid
- + " is not the owner of the supplied VirtualDevice (deviceId = "
- + virtualDevice.getDeviceId() + ")");
- }
-
- return virtualDeviceImpl.createVirtualDisplay(
- virtualDisplayConfig, callback, packageName);
- }
-
- @Override // Binder call
public List<VirtualDevice> getVirtualDevices() {
List<VirtualDevice> virtualDevices = new ArrayList<>();
synchronized (mVirtualDeviceManagerLock) {
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index f32031de..f8857d3 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -17,6 +17,7 @@
package com.android.server;
import static android.app.Flags.modesApi;
+import static android.app.Flags.enableCurrentModeTypeBinderCache;
import static android.app.Flags.enableNightModeBinderCache;
import static android.app.UiModeManager.ContrastUtils.CONTRAST_DEFAULT_VALUE;
import static android.app.UiModeManager.DEFAULT_PRIORITY;
@@ -138,7 +139,7 @@
private int mLastBroadcastState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
- private final NightMode mNightMode = new NightMode(){
+ private final IntProperty mNightMode = new IntProperty(){
private int mNightModeValue = UiModeManager.MODE_NIGHT_NO;
@Override
@@ -192,7 +193,22 @@
// flag set by resource, whether to night mode change for normal all or not.
private boolean mNightModeLocked = false;
- int mCurUiMode = 0;
+ private final IntProperty mCurUiMode = new IntProperty(){
+ private int mCurrentModeTypeValue = 0;
+
+ @Override
+ public int get() {
+ return mCurrentModeTypeValue;
+ }
+
+ @Override
+ public void set(int mode) {
+ mCurrentModeTypeValue = mode;
+ if (enableCurrentModeTypeBinderCache()) {
+ UiModeManager.invalidateCurrentModeTypeCache();
+ }
+ }
+ };
private int mSetUiMode = 0;
private boolean mHoldingConfiguration = false;
private int mCurrentUser;
@@ -810,7 +826,7 @@
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- return mCurUiMode & Configuration.UI_MODE_TYPE_MASK;
+ return mCurUiMode.get() & Configuration.UI_MODE_TYPE_MASK;
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -1492,7 +1508,7 @@
pw.print(" mCarModeEnableFlags="); pw.print(mCarModeEnableFlags);
pw.print(" mEnableCarDockLaunch="); pw.println(mEnableCarDockLaunch);
- pw.print(" mCurUiMode=0x"); pw.print(Integer.toHexString(mCurUiMode));
+ pw.print(" mCurUiMode=0x"); pw.print(Integer.toHexString(mCurUiMode.get()));
pw.print(" mUiModeLocked="); pw.print(mUiModeLocked);
pw.print(" mSetUiMode=0x"); pw.println(Integer.toHexString(mSetUiMode));
@@ -1745,7 +1761,7 @@
+ "; uiMode=" + uiMode);
}
- mCurUiMode = uiMode;
+ mCurUiMode.set(uiMode);
if (!mHoldingConfiguration && (!mWaitForDeviceInactive || mPowerSave)) {
mConfiguration.uiMode = uiMode;
}
@@ -1892,7 +1908,7 @@
boolean keepScreenOn = mCharging &&
((mCarModeEnabled && mCarModeKeepsScreenOn &&
(mCarModeEnableFlags & UiModeManager.ENABLE_CAR_MODE_ALLOW_SLEEP) == 0) ||
- (mCurUiMode == Configuration.UI_MODE_TYPE_DESK && mDeskModeKeepsScreenOn));
+ (mCurUiMode.get() == Configuration.UI_MODE_TYPE_DESK && mDeskModeKeepsScreenOn));
if (keepScreenOn != mWakeLock.isHeld()) {
if (keepScreenOn) {
mWakeLock.acquire();
@@ -2048,12 +2064,14 @@
private void updateComputedNightModeLocked(boolean activate) {
boolean newComputedValue = activate;
+ boolean appliedOverrides = false;
if (mNightMode.get() != MODE_NIGHT_YES && mNightMode.get() != UiModeManager.MODE_NIGHT_NO) {
if (mOverrideNightModeOn && !newComputedValue) {
newComputedValue = true;
} else if (mOverrideNightModeOff && newComputedValue) {
newComputedValue = false;
}
+ appliedOverrides = true;
}
if (modesApi()) {
@@ -2063,8 +2081,10 @@
case (UiModeManager.MODE_ATTENTION_THEME_OVERLAY_DAY) -> false;
default -> newComputedValue; // case OFF
};
- } else {
- mComputedNightMode = newComputedValue;
+ }
+
+ if (appliedOverrides) {
+ return;
}
if (mNightMode.get() != MODE_NIGHT_AUTO || (mTwilightManager != null
@@ -2319,11 +2339,12 @@
}
/**
- * Interface to contain the value for system night mode. We make the night mode accessible
- * through this class to ensure that the reassignment of this value invalidates the cache.
+ * Interface to contain the value for an integral property. We make the property
+ * accessible through this class to ensure that the reassignment of this value invalidates the
+ * cache.
*/
- private interface NightMode {
+ private interface IntProperty {
int get();
- void set(int mode);
+ void set(int value);
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 6fd281e..f5a297b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -685,11 +685,6 @@
// default. Controlled by Settings.Global.FORCE_ENABLE_PSS_PROFILING
volatile boolean mForceEnablePssProfiling = false;
- // Indicates whether to use ApplicationInfo to determine launched state instead of PM user state
- // This is a temporary workaround until the trunk-stable flag is pushed to nextfood.
- // TODO: b/365979852 - remove this workaround when redundant
- volatile boolean mFlagUseAppInfoNotLaunched = false;
-
/**
* Indicates whether the foreground service background start restriction is enabled for
* caller app that is targeting S+.
@@ -1022,9 +1017,6 @@
private static final Uri FORCE_ENABLE_PSS_PROFILING_URI =
Settings.Global.getUriFor(Settings.Global.FORCE_ENABLE_PSS_PROFILING);
- private static final Uri ENABLE_USE_APP_INFO_NOT_LAUNCHED_URI =
- Settings.Global.getUriFor(Settings.Global.ENABLE_USE_APP_INFO_NOT_LAUNCHED);
-
/**
* The threshold to decide if a given association should be dumped into metrics.
*/
@@ -1487,7 +1479,6 @@
false, this);
}
mResolver.registerContentObserver(FORCE_ENABLE_PSS_PROFILING_URI, false, this);
- mResolver.registerContentObserver(ENABLE_USE_APP_INFO_NOT_LAUNCHED_URI, false, this);
updateConstants();
if (mSystemServerAutomaticHeapDumpEnabled) {
updateEnableAutomaticSystemServerHeapDumps();
@@ -1504,7 +1495,6 @@
updateActivityStartsLoggingEnabled();
updateForegroundServiceStartsLoggingEnabled();
updateForceEnablePssProfiling();
- updateEnableUseAppInfoNotLaunched();
// Read DropboxRateLimiter params from flags.
mService.initDropboxRateLimiter();
}
@@ -1550,8 +1540,6 @@
updateEnableAutomaticSystemServerHeapDumps();
} else if (FORCE_ENABLE_PSS_PROFILING_URI.equals(uri)) {
updateForceEnablePssProfiling();
- } else if (ENABLE_USE_APP_INFO_NOT_LAUNCHED_URI.equals(uri)) {
- updateEnableUseAppInfoNotLaunched();
}
}
@@ -1671,11 +1659,6 @@
Settings.Global.FORCE_ENABLE_PSS_PROFILING, 0) == 1;
}
- private void updateEnableUseAppInfoNotLaunched() {
- mFlagUseAppInfoNotLaunched = Settings.Global.getInt(mResolver,
- Settings.Global.ENABLE_USE_APP_INFO_NOT_LAUNCHED, 0) == 1;
- }
-
private void updateBackgroundActivityStarts() {
mFlagBackgroundActivityStartsEnabled = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -2555,8 +2538,6 @@
pw.print(" OOMADJ_UPDATE_QUICK="); pw.println(OOMADJ_UPDATE_QUICK);
pw.print(" ENABLE_WAIT_FOR_FINISH_ATTACH_APPLICATION=");
pw.println(mEnableWaitForFinishAttachApplication);
- pw.print(" FLAG_USE_APP_INFO_NOT_LAUNCHED=");
- pw.println(mFlagUseAppInfoNotLaunched);
pw.print(" "); pw.print(KEY_FOLLOW_UP_OOMADJ_UPDATE_WAIT_DURATION);
pw.print("="); pw.println(FOLLOW_UP_OOMADJ_UPDATE_WAIT_DURATION);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 83bc75e..3c95b9a 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -130,6 +130,7 @@
import static android.provider.Settings.Global.ALWAYS_FINISH_ACTIVITIES;
import static android.provider.Settings.Global.DEBUG_APP;
import static android.provider.Settings.Global.WAIT_FOR_DEBUGGER;
+import static android.security.Flags.preventIntentRedirect;
import static android.util.FeatureFlagUtils.SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS;
import static android.view.Display.INVALID_DISPLAY;
@@ -420,6 +421,7 @@
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.MemInfoReader;
import com.android.internal.util.Preconditions;
+import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.AlarmManagerInternal;
import com.android.server.BootReceiver;
import com.android.server.DeviceIdleInternal;
@@ -2420,6 +2422,7 @@
mBroadcastController = new BroadcastController(mContext, this, mBroadcastQueue);
mComponentAliasResolver = new ComponentAliasResolver(this);
mApplicationSharedMemoryReadOnlyFd = null;
+ sCreatorTokenCacheCleaner = new Handler(mHandlerThread.getLooper());
}
// Note: This method is invoked on the main thread but may need to attach various
@@ -2526,6 +2529,7 @@
mPendingStartActivityUids = new PendingStartActivityUids();
mTraceErrorLogger = new TraceErrorLogger();
mComponentAliasResolver = new ComponentAliasResolver(this);
+ sCreatorTokenCacheCleaner = new Handler(mHandlerThread.getLooper());
try {
mApplicationSharedMemoryReadOnlyFd =
ApplicationSharedMemory.getInstance().getReadOnlyFileDescriptor();
@@ -5532,6 +5536,7 @@
public int sendIntentSender(IApplicationThread caller, IIntentSender target,
IBinder allowlistToken, int code, Intent intent, String resolvedType,
IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
+ addCreatorToken(intent);
if (target instanceof PendingIntentRecord) {
final PendingIntentRecord originalRecord = (PendingIntentRecord) target;
@@ -13610,6 +13615,7 @@
throws TransactionTooLargeException {
enforceNotIsolatedCaller("startService");
enforceAllowedToStartOrBindServiceIfSdkSandbox(service);
+ addCreatorToken(service);
if (service != null) {
// Refuse possible leaked file descriptors
if (service.hasFileDescriptors()) {
@@ -13871,6 +13877,7 @@
validateServiceInstanceName(instanceName);
+ addCreatorToken(service);
try {
if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
final ComponentName cn = service.getComponent();
@@ -17148,6 +17155,7 @@
Slog.v(TAG_SERVICE,
"startServiceInPackage: " + service + " type=" + resolvedType);
}
+ addCreatorToken(service);
final long origId = Binder.clearCallingIdentity();
ComponentName res;
try {
@@ -17973,6 +17981,11 @@
userId, reason, exitInfoReason);
}
}
+
+ @Override
+ public void addCreatorToken(Intent intent) {
+ ActivityManagerService.this.addCreatorToken(intent);
+ }
}
long inputDispatchingTimedOut(int pid, final boolean aboveSystem, TimeoutRecord timeoutRecord) {
@@ -18398,25 +18411,34 @@
"Cannot kill the dependents of a package without its name.");
}
+ userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+ userId, true, ALLOW_FULL_ONLY, "killPackageDependents", null);
+ final int[] userIds = mUserController.expandUserId(userId);
+
final long callingId = Binder.clearCallingIdentity();
IPackageManager pm = AppGlobals.getPackageManager();
- int pkgUid = -1;
try {
- pkgUid = pm.getPackageUid(packageName, MATCH_DEBUG_TRIAGED_MISSING, userId);
- } catch (RemoteException e) {
- }
- if (userId != UserHandle.USER_ALL && pkgUid == -1) {
- throw new IllegalArgumentException(
- "Cannot kill dependents of non-existing package " + packageName);
- }
- try {
- synchronized(this) {
- synchronized (mProcLock) {
- mProcessList.killPackageProcessesLSP(packageName, UserHandle.getAppId(pkgUid),
- userId, ProcessList.FOREGROUND_APP_ADJ,
- ApplicationExitInfo.REASON_DEPENDENCY_DIED,
- ApplicationExitInfo.SUBREASON_UNKNOWN,
- "dep: " + packageName);
+ for (int targetUserId : userIds) {
+ int pkgUid = -1;
+ try {
+ pkgUid = pm.getPackageUid(packageName, MATCH_DEBUG_TRIAGED_MISSING,
+ targetUserId);
+ } catch (RemoteException e) {
+ }
+ if (userId != UserHandle.USER_ALL && pkgUid == -1) {
+ throw new IllegalArgumentException(
+ "Cannot kill dependents of non-existing package " + packageName);
+ }
+ synchronized (this) {
+ synchronized (mProcLock) {
+ mProcessList.killPackageProcessesLSP(packageName,
+ UserHandle.getAppId(pkgUid),
+ targetUserId,
+ ProcessList.FOREGROUND_APP_ADJ,
+ ApplicationExitInfo.REASON_DEPENDENCY_DIED,
+ ApplicationExitInfo.SUBREASON_UNKNOWN,
+ "dep: " + packageName);
+ }
}
}
} finally {
@@ -19105,6 +19127,7 @@
private static final Map<IntentCreatorToken.Key, WeakReference<IntentCreatorToken>>
sIntentCreatorTokenCache = new ConcurrentHashMap<>();
+ private static Handler sCreatorTokenCacheCleaner;
/**
* A binder token used to keep track of which app created the intent. This token can be used to
* defend against intent redirect attacks. It stores uid of the intent creator and key fields of
@@ -19112,13 +19135,16 @@
*
* @hide
*/
+ @VisibleForTesting
public static final class IntentCreatorToken extends Binder {
@NonNull
private final Key mKeyFields;
+ private final WeakReference<IntentCreatorToken> mRef;
public IntentCreatorToken(int creatorUid, Intent intent) {
super();
this.mKeyFields = new Key(creatorUid, intent);
+ mRef = new WeakReference<>(this);
}
public int getCreatorUid() {
@@ -19136,6 +19162,26 @@
new Key(token.mKeyFields.mCreatorUid, intent));
}
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ sCreatorTokenCacheCleaner.sendMessage(PooledLambda.obtainMessage(
+ IntentCreatorToken::completeFinalize, this));
+ } finally {
+ super.finalize();
+ }
+ }
+
+ private void completeFinalize() {
+ synchronized (sIntentCreatorTokenCache) {
+ WeakReference<IntentCreatorToken> current = sIntentCreatorTokenCache.get(
+ mKeyFields);
+ if (current == mRef) {
+ sIntentCreatorTokenCache.remove(mKeyFields);
+ }
+ }
+ }
+
private static class Key {
private Key(int creatorUid, Intent intent) {
this.mCreatorUid = creatorUid;
@@ -19178,9 +19224,57 @@
@Override
public int hashCode() {
return Objects.hash(mCreatorUid, mAction, mData, mType, mPackage, mComponent,
- mFlags,
- mClipDataUris);
+ mFlags, mClipDataUris);
}
}
}
+
+ /**
+ * Add a creator token for all embedded intents (stored as extra) of the given intent.
+ *
+ * @param intent The given intent
+ * @hide
+ */
+ public void addCreatorToken(@Nullable Intent intent) {
+ if (!preventIntentRedirect()) return;
+
+ if (intent == null || intent.getExtraIntentKeys() == null) return;
+ for (String key : intent.getExtraIntentKeys()) {
+ try {
+ Intent extraIntent = intent.getParcelableExtra(key, Intent.class);
+ if (extraIntent == null) {
+ Slog.w(TAG, "The key {" + key
+ + "} does not correspond to an intent in the extra bundle.");
+ continue;
+ }
+ Slog.wtf(TAG, "A creator token is added to an intent.");
+ IBinder creatorToken = createIntentCreatorToken(extraIntent);
+ if (creatorToken != null) {
+ extraIntent.setCreatorToken(creatorToken);
+ }
+ } catch (Exception e) {
+ Slog.wtf(TAG,
+ "Something went wrong when trying to add creator token for embedded "
+ + "intents of intent: ."
+ + intent, e);
+ }
+ }
+ }
+
+ private IBinder createIntentCreatorToken(Intent intent) {
+ if (IntentCreatorToken.isValid(intent)) return null;
+ int creatorUid = getCallingUid();
+ IntentCreatorToken.Key key = new IntentCreatorToken.Key(creatorUid, intent);
+ IntentCreatorToken token;
+ synchronized (sIntentCreatorTokenCache) {
+ WeakReference<IntentCreatorToken> ref = sIntentCreatorTokenCache.get(key);
+ if (ref == null || ref.get() == null) {
+ token = new IntentCreatorToken(creatorUid, intent);
+ sIntentCreatorTokenCache.put(key, token.mRef);
+ } else {
+ token = ref.get();
+ }
+ }
+ return token;
+ }
}
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index a93ae72..57922d5 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -3401,8 +3401,7 @@
// Check if we should mark the processrecord for first launch after force-stopping
if (wasStopped) {
boolean wasEverLaunched = false;
- if (android.app.Flags.useAppInfoNotLaunched()
- || mService.mConstants.mFlagUseAppInfoNotLaunched) {
+ if (android.app.Flags.useAppInfoNotLaunched()) {
wasEverLaunched = !info.isNotLaunched();
} else {
try {
@@ -3423,8 +3422,7 @@
: STOPPED_STATE_FIRST_LAUNCH;
r.getWindowProcessController().setStoppedState(stoppedState);
} else {
- if (android.app.Flags.useAppInfoNotLaunched()
- || mService.mConstants.mFlagUseAppInfoNotLaunched) {
+ if (android.app.Flags.useAppInfoNotLaunched()) {
// If it was launched before, then it must be a force-stop
r.setWasForceStopped(wasEverLaunched);
} else {
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index bae9a67..a815f72 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -138,6 +138,7 @@
// The list is sorted.
@VisibleForTesting
static final String[] sDeviceConfigAconfigScopes = new String[] {
+ "aaos_sdv",
"accessibility",
"android_core_networking",
"android_health_services",
@@ -150,6 +151,7 @@
"art_performance",
"attack_tools",
"avic",
+ "desktop_firmware",
"biometrics",
"biometrics_framework",
"biometrics_integration",
@@ -211,6 +213,7 @@
"preload_safety",
"printing",
"privacy_infra_policy",
+ "ravenwood",
"resource_manager",
"responsible_apis",
"rust",
@@ -243,8 +246,10 @@
"wear_system_health",
"wear_systems",
"wear_sysui",
+ "wear_system_managed_surfaces",
"window_surfaces",
"windowing_frontend",
+ "xr",
};
public static final String NAMESPACE_REBOOT_STAGING = "staged";
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index cc66378..7873d34 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -198,13 +198,10 @@
flag {
name: "logcat_longer_timeout"
- namespace: "backstage_power"
+ namespace: "stability"
description: "Wait longer during the logcat gathering operation"
bug: "292533246"
is_fixed_read_only: true
- metadata {
- purpose: PURPOSE_BUGFIX
- }
}
flag {
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index e0cf96f..596e375 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -72,6 +72,9 @@
import static android.content.pm.PermissionInfo.PROTECTION_FLAG_APPOP;
import static android.permission.flags.Flags.deviceAwareAppOpNewSchemaEnabled;
+import static com.android.internal.util.FrameworkStatsLog.APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__CHECK_OPERATION;
+import static com.android.internal.util.FrameworkStatsLog.APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__NOTE_OPERATION;
+import static com.android.internal.util.FrameworkStatsLog.APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__NOTE_PROXY_OPERATION;
import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS;
import android.Manifest;
@@ -160,6 +163,7 @@
import com.android.internal.pm.pkg.component.ParsedAttribution;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
import com.android.internal.util.function.pooled.PooledLambda;
@@ -2829,12 +2833,26 @@
@Override
public int checkOperation(int code, int uid, String packageName) {
+ if (Flags.appopAccessTrackingLoggingEnabled()) {
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED,
+ uid, code,
+ APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__CHECK_OPERATION,
+ false);
+ }
return mCheckOpsDelegateDispatcher.checkOperation(code, uid, packageName, null,
Context.DEVICE_ID_DEFAULT, false /*raw*/);
}
@Override
public int checkOperationForDevice(int code, int uid, String packageName, int virtualDeviceId) {
+ if (Flags.appopAccessTrackingLoggingEnabled()) {
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED,
+ uid, code,
+ APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__CHECK_OPERATION,
+ false);
+ }
return mCheckOpsDelegateDispatcher.checkOperation(code, uid, packageName, null,
virtualDeviceId, false /*raw*/);
}
@@ -3015,6 +3033,14 @@
public SyncNotedAppOp noteProxyOperationWithState(int code,
AttributionSourceState attributionSourceState, boolean shouldCollectAsyncNotedOp,
String message, boolean shouldCollectMessage, boolean skipProxyOperation) {
+ if (Flags.appopAccessTrackingLoggingEnabled()) {
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED,
+ attributionSourceState.uid, code,
+ APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__NOTE_PROXY_OPERATION,
+ attributionSourceState.attributionTag != null);
+ }
+
AttributionSource attributionSource = new AttributionSource(attributionSourceState);
return mCheckOpsDelegateDispatcher.noteProxyOperation(code, attributionSource,
shouldCollectAsyncNotedOp, message, shouldCollectMessage, skipProxyOperation);
@@ -3096,6 +3122,14 @@
public SyncNotedAppOp noteOperation(int code, int uid, String packageName,
String attributionTag, boolean shouldCollectAsyncNotedOp, String message,
boolean shouldCollectMessage) {
+ if (Flags.appopAccessTrackingLoggingEnabled()) {
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED,
+ uid, code,
+ APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__NOTE_OPERATION,
+ attributionTag != null);
+ }
+
return mCheckOpsDelegateDispatcher.noteOperation(code, uid, packageName,
attributionTag, Context.DEVICE_ID_DEFAULT, shouldCollectAsyncNotedOp, message,
shouldCollectMessage);
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 0475b94..60dbf3f 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -389,7 +389,8 @@
*/
private boolean shouldStartScoForUid(int uid) {
return !(UserHandle.isSameApp(uid, Process.BLUETOOTH_UID)
- || UserHandle.isSameApp(uid, Process.PHONE_UID));
+ || UserHandle.isSameApp(uid, Process.PHONE_UID)
+ || UserHandle.isSameApp(uid, Process.SYSTEM_UID));
}
@GuardedBy("mDeviceStateLock")
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index c37d471..1563a62 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -286,6 +286,7 @@
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.CancellationException;
+import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
@@ -4030,7 +4031,6 @@
&& isFullVolumeDevice(device);
boolean tvConditions = mHdmiTvClient != null
&& mHdmiSystemAudioSupported
- && isFullVolumeDevice(device)
&& !isAbsoluteVolumeDevice(device)
&& !isA2dpAbsoluteVolumeDevice(device);
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java b/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java
index bc58501..93b0e66 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java
@@ -123,7 +123,8 @@
enrollSuccessful,
-1, /* sensorId */
ambientLightLux,
- source);
+ source,
+ -1 /* templateId*/);
}
/** {@see FrameworkStatsLog.BIOMETRIC_ERROR_OCCURRED}. */
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index bb503aa..d71826f 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -2302,6 +2302,9 @@
updateLogicalDisplayState(display);
mExternalDisplayPolicy.handleLogicalDisplayAddedLocked(display);
+ if (mDisplayTopologyCoordinator != null) {
+ mDisplayTopologyCoordinator.onDisplayAdded(display.getDisplayInfoLocked());
+ }
}
private void handleLogicalDisplayChangedLocked(@NonNull LogicalDisplay display) {
@@ -2389,6 +2392,9 @@
} else {
releaseDisplayAndEmitEvent(display, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED);
}
+ if (mDisplayTopologyCoordinator != null) {
+ mDisplayTopologyCoordinator.onDisplayRemoved(display.getDisplayIdLocked());
+ }
Slog.i(TAG, "Logical display removed: " + display.getDisplayIdLocked());
}
@@ -5233,10 +5239,9 @@
}
@Override
- public boolean isProximitySensorAvailable() {
+ public boolean isProximitySensorAvailable(int displayId) {
synchronized (mSyncRoot) {
- return mDisplayPowerControllers.get(Display.DEFAULT_DISPLAY)
- .isProximitySensorAvailable();
+ return mDisplayPowerControllers.get(displayId).isProximitySensorAvailable();
}
}
diff --git a/services/core/java/com/android/server/display/DisplayTopology.java b/services/core/java/com/android/server/display/DisplayTopology.java
index 90038a0..b01d617 100644
--- a/services/core/java/com/android/server/display/DisplayTopology.java
+++ b/services/core/java/com/android/server/display/DisplayTopology.java
@@ -20,12 +20,15 @@
import android.util.IndentingPrintWriter;
import android.util.Pair;
import android.util.Slog;
+import android.view.Display;
import com.android.internal.annotations.VisibleForTesting;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.LinkedList;
import java.util.List;
+import java.util.Queue;
/**
* Represents the relative placement of extended displays.
@@ -45,35 +48,50 @@
* This is not necessarily the same as the default display.
*/
@VisibleForTesting
- int mPrimaryDisplayId;
+ int mPrimaryDisplayId = Display.INVALID_DISPLAY;
/**
* Add a display to the topology.
* If this is the second display in the topology, it will be placed above the first display.
* Subsequent displays will be places to the left or right of the second display.
- * @param displayId The ID of the display
+ * @param displayId The logical display ID
* @param width The width of the display
* @param height The height of the display
*/
void addDisplay(int displayId, double width, double height) {
- if (mRoot == null) {
- mRoot = new TreeNode(displayId, width, height, /* position= */ null, /* offset= */ 0);
- mPrimaryDisplayId = displayId;
- Slog.i(TAG, "First display added: " + mRoot);
- } else if (mRoot.mChildren.isEmpty()) {
- // This is the 2nd display. Align the middles of the top and bottom edges.
- double offset = mRoot.mWidth / 2 - width / 2;
- TreeNode display = new TreeNode(displayId, width, height,
- TreeNode.Position.POSITION_TOP, offset);
- mRoot.mChildren.add(display);
- Slog.i(TAG, "Second display added: " + display + ", parent ID: " + mRoot.mDisplayId);
+ addDisplay(displayId, width, height, /* shouldLog= */ true);
+ }
+
+ /**
+ * Remove a display from the topology.
+ * The default topology is created from the remaining displays, as if they were reconnected
+ * one by one.
+ * @param displayId The logical display ID
+ */
+ void removeDisplay(int displayId) {
+ if (!isDisplayPresent(displayId, mRoot)) {
+ return;
+ }
+ Queue<TreeNode> queue = new LinkedList<>();
+ queue.add(mRoot);
+ mRoot = null;
+ while (!queue.isEmpty()) {
+ TreeNode node = queue.poll();
+ if (node.mDisplayId != displayId) {
+ addDisplay(node.mDisplayId, node.mWidth, node.mHeight, /* shouldLog= */ false);
+ }
+ queue.addAll(node.mChildren);
+ }
+ if (mPrimaryDisplayId == displayId) {
+ if (mRoot != null) {
+ mPrimaryDisplayId = mRoot.mDisplayId;
+ } else {
+ mPrimaryDisplayId = Display.INVALID_DISPLAY;
+ }
+ Slog.i(TAG, "Primary display with ID " + displayId
+ + " removed, new primary display: " + mPrimaryDisplayId);
} else {
- TreeNode rightMostDisplay = findRightMostDisplay(mRoot, mRoot.mWidth).first;
- TreeNode newDisplay = new TreeNode(displayId, width, height,
- TreeNode.Position.POSITION_RIGHT, /* offset= */ 0);
- rightMostDisplay.mChildren.add(newDisplay);
- Slog.i(TAG, "Display added: " + newDisplay + ", parent ID: "
- + rightMostDisplay.mDisplayId);
+ Slog.i(TAG, "Display with ID " + displayId + " removed");
}
}
@@ -97,6 +115,35 @@
}
}
+ private void addDisplay(int displayId, double width, double height, boolean shouldLog) {
+ if (mRoot == null) {
+ mRoot = new TreeNode(displayId, width, height, /* position= */ null, /* offset= */ 0);
+ mPrimaryDisplayId = displayId;
+ if (shouldLog) {
+ Slog.i(TAG, "First display added: " + mRoot);
+ }
+ } else if (mRoot.mChildren.isEmpty()) {
+ // This is the 2nd display. Align the middles of the top and bottom edges.
+ double offset = mRoot.mWidth / 2 - width / 2;
+ TreeNode display = new TreeNode(displayId, width, height,
+ TreeNode.Position.POSITION_TOP, offset);
+ mRoot.mChildren.add(display);
+ if (shouldLog) {
+ Slog.i(TAG, "Second display added: " + display + ", parent ID: "
+ + mRoot.mDisplayId);
+ }
+ } else {
+ TreeNode rightMostDisplay = findRightMostDisplay(mRoot, mRoot.mWidth).first;
+ TreeNode newDisplay = new TreeNode(displayId, width, height,
+ TreeNode.Position.POSITION_RIGHT, /* offset= */ 0);
+ rightMostDisplay.mChildren.add(newDisplay);
+ if (shouldLog) {
+ Slog.i(TAG, "Display added: " + newDisplay + ", parent ID: "
+ + rightMostDisplay.mDisplayId);
+ }
+ }
+ }
+
/**
* @param display The display from which the search should start.
* @param xPos The x position of the right edge of that display.
@@ -126,6 +173,21 @@
return result;
}
+ private boolean isDisplayPresent(int displayId, TreeNode node) {
+ if (node == null) {
+ return false;
+ }
+ if (node.mDisplayId == displayId) {
+ return true;
+ }
+ for (TreeNode child : node.mChildren) {
+ if (isDisplayPresent(displayId, child)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
@VisibleForTesting
static class TreeNode {
diff --git a/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java b/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java
index cbd224c..46358dfd 100644
--- a/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java
+++ b/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java
@@ -66,6 +66,16 @@
}
/**
+ * Remove a display from the topology.
+ * @param displayId The logical display ID
+ */
+ void onDisplayRemoved(int displayId) {
+ synchronized (mLock) {
+ mTopology.removeDisplay(displayId);
+ }
+ }
+
+ /**
* Print the object's state and debug information into the given stream.
* @param pw The stream to dump information to.
*/
diff --git a/services/core/java/com/android/server/hdmi/HdmiEarcController.java b/services/core/java/com/android/server/hdmi/HdmiEarcController.java
index 46a8f03..1c947e9 100644
--- a/services/core/java/com/android/server/hdmi/HdmiEarcController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiEarcController.java
@@ -87,8 +87,8 @@
} catch (ServiceSpecificException sse) {
HdmiLogger.error(
"Could not set eARC enabled to " + enabled + ". Error: ", sse.errorCode);
- } catch (RemoteException re) {
- HdmiLogger.error("Could not set eARC enabled to " + enabled + ":. Exception: ", re);
+ } catch (RemoteException | NullPointerException e) {
+ HdmiLogger.error("Could not set eARC enabled to " + enabled + ":. Exception: ", e);
}
}
@@ -96,8 +96,8 @@
public boolean nativeIsEarcEnabled() {
try {
return mEarc.isEArcEnabled();
- } catch (RemoteException re) {
- HdmiLogger.error("Could not read if eARC is enabled. Exception: ", re);
+ } catch (RemoteException | NullPointerException e) {
+ HdmiLogger.error("Could not read if eARC is enabled. Exception: ", e);
return false;
}
}
@@ -107,8 +107,8 @@
mEarcCallback = callback;
try {
mEarc.setCallback(callback);
- } catch (RemoteException re) {
- HdmiLogger.error("Could not set callback. Exception: ", re);
+ } catch (RemoteException | NullPointerException e) {
+ HdmiLogger.error("Could not set callback. Exception: ", e);
}
}
@@ -116,8 +116,8 @@
public byte nativeGetState(int portId) {
try {
return mEarc.getState(portId);
- } catch (RemoteException re) {
- HdmiLogger.error("Could not get eARC state. Exception: ", re);
+ } catch (RemoteException | NullPointerException e) {
+ HdmiLogger.error("Could not get eARC state. Exception: ", e);
return -1;
}
}
@@ -126,9 +126,9 @@
public byte[] nativeGetLastReportedAudioCapabilities(int portId) {
try {
return mEarc.getLastReportedAudioCapabilities(portId);
- } catch (RemoteException re) {
+ } catch (RemoteException | NullPointerException e) {
HdmiLogger.error(
- "Could not read last reported audio capabilities. Exception: ", re);
+ "Could not read last reported audio capabilities. Exception: ", e);
return null;
}
}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 3780fbd..bbdac56 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -99,6 +99,7 @@
import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.ShellCallback;
+import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
@@ -126,6 +127,7 @@
import android.util.ArraySet;
import android.util.Log;
import android.util.LongSparseArray;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
@@ -253,6 +255,8 @@
private static final String MIGRATED_KEYSTORE_NS = "migrated_keystore_namespace";
private static final String MIGRATED_SP_CE_ONLY = "migrated_all_users_to_sp_and_bound_ce";
private static final String MIGRATED_SP_FULL = "migrated_all_users_to_sp_and_bound_keys";
+ private static final String MIGRATED_WEAVER_DISABLED_ON_UNSECURED_USERS =
+ "migrated_weaver_disabled_on_unsecured_users";
// Duration that LockSettingsService will store the gatekeeper password for. This allows
// multiple biometric enrollments without prompting the user to enter their password via
@@ -309,6 +313,10 @@
@GuardedBy("mUserCreationAndRemovalLock")
private boolean mThirdPartyAppsStarted;
+ // This list contains the (protectorId, userId) of any protectors that were by replaced by a
+ // migration and should be destroyed once rollback to the old build is no longer possible.
+ private ArrayList<Pair<Long, Integer>> mProtectorsToDestroyOnBootCompleted = new ArrayList<>();
+
// Current password metrics for all secured users on the device. Updated when user unlocks the
// device or changes password. Removed if user is stopped with its CE key evicted.
@GuardedBy("this")
@@ -363,6 +371,10 @@
mLockSettingsService.migrateOldDataAfterSystemReady();
mLockSettingsService.deleteRepairModePersistentDataIfNeeded();
} else if (phase == PHASE_BOOT_COMPLETED) {
+ // In the case of an upgrade, PHASE_BOOT_COMPLETED means that a rollback to the old
+ // build can no longer occur. This is the time to destroy any migrated protectors.
+ mLockSettingsService.destroyMigratedProtectors();
+
mLockSettingsService.loadEscrowData();
}
}
@@ -1076,6 +1088,11 @@
mStorage.deleteRepairModePersistentData();
}
+ private boolean isWeaverDisabledOnUnsecuredUsers() {
+ return mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_disableWeaverOnUnsecuredUsers);
+ }
+
// This is called when Weaver is guaranteed to be available (if the device supports Weaver).
// It does any synthetic password related work that was delayed from earlier in the boot.
private void onThirdPartyAppsStarted() {
@@ -1114,13 +1131,20 @@
//
// - Upgrading from Android 14, where unsecured users didn't have Keystore super keys.
//
+ // - Upgrading from a build with config_disableWeaverOnUnsecuredUsers=false to one with
+ // config_disableWeaverOnUnsecuredUsers=true. (We don't bother to proactively add
+ // Weaver for the reverse update to false, as it's too late to help in that case.)
+ //
// The end result is that all users, regardless of whether they are secured or not, have
- // a synthetic password with all keys initialized and protected by it.
+ // a synthetic password with all keys initialized and protected by it, and honoring
+ // config_disableWeaverOnUnsecuredUsers=true when applicable.
//
// Note: if this migration gets interrupted (e.g. by the device powering off), there
// shouldn't be a problem since this will run again on the next boot, and
// setCeStorageProtection() and initKeystoreSuperKeys(..., true) are idempotent.
- if (!getBoolean(MIGRATED_SP_FULL, false, 0)) {
+ if (!getBoolean(MIGRATED_SP_FULL, false, 0)
+ || (isWeaverDisabledOnUnsecuredUsers()
+ && !getBoolean(MIGRATED_WEAVER_DISABLED_ON_UNSECURED_USERS, false, 0))) {
for (UserInfo user : mUserManager.getAliveUsers()) {
removeStateForReusedUserIdIfNecessary(user.id, user.serialNumber);
synchronized (mSpManager) {
@@ -1128,6 +1152,9 @@
}
}
setBoolean(MIGRATED_SP_FULL, true, 0);
+ if (isWeaverDisabledOnUnsecuredUsers()) {
+ setBoolean(MIGRATED_WEAVER_DISABLED_ON_UNSECURED_USERS, true, 0);
+ }
}
mThirdPartyAppsStarted = true;
@@ -1151,13 +1178,61 @@
getGateKeeperService(), protectorId, LockscreenCredential.createNone(), userId,
null);
SyntheticPassword sp = result.syntheticPassword;
- if (sp == null) {
+ if (isWeaverDisabledOnUnsecuredUsers()) {
+ Slog.i(TAG, "config_disableWeaverOnUnsecuredUsers=true");
+
+ // If config_disableWeaverOnUnsecuredUsers=true, then the Weaver HAL may be buggy and
+ // need multiple retries before it works here to unwrap the SP, if the SP was already
+ // protected by Weaver. Note that the problematic HAL can also deadlock if called with
+ // the ActivityManagerService lock held, but that should not be a problem here since
+ // that lock isn't held here, unlike unlockUserKeyIfUnsecured() where it is.
+ for (int i = 0; i < 12 && sp == null; i++) {
+ Slog.e(TAG, "Failed to unwrap synthetic password. Waiting 5 seconds to retry.");
+ SystemClock.sleep(5000);
+ result = mSpManager.unlockLskfBasedProtector(getGateKeeperService(), protectorId,
+ LockscreenCredential.createNone(), userId, null);
+ sp = result.syntheticPassword;
+ }
+ if (sp == null) {
+ throw new IllegalStateException(
+ "Failed to unwrap synthetic password for unsecured user");
+ }
+ // If the SP is protected by Weaver, then remove the Weaver protection in order to make
+ // config_disableWeaverOnUnsecuredUsers=true take effect.
+ if (result.usedWeaver) {
+ Slog.i(TAG, "Removing Weaver protection from the synthetic password");
+ // Create a new protector, which will not use Weaver.
+ long newProtectorId = mSpManager.createLskfBasedProtector(
+ getGateKeeperService(), LockscreenCredential.createNone(), sp, userId);
+
+ // Out of paranoia, make sure the new protector really works.
+ result = mSpManager.unlockLskfBasedProtector(getGateKeeperService(),
+ newProtectorId, LockscreenCredential.createNone(), userId, null);
+ sp = result.syntheticPassword;
+ if (sp == null) {
+ throw new IllegalStateException("New SP protector does not work");
+ }
+
+ // Replace the protector. Wait until PHASE_BOOT_COMPLETED to destroy the old
+ // protector, since the Weaver slot erasure and freeing cannot be rolled back.
+ setCurrentLskfBasedProtectorId(newProtectorId, userId);
+ mProtectorsToDestroyOnBootCompleted.add(new Pair(protectorId, userId));
+ } else {
+ Slog.i(TAG, "Synthetic password is already not protected by Weaver");
+ }
+ } else if (sp == null) {
Slogf.wtf(TAG, "Failed to unwrap synthetic password for unsecured user %d", userId);
return;
}
- // While setCeStorageProtection() is idempotent, it does log some error messages when called
- // again. Skip it if we know it was already handled by an earlier upgrade to Android 14.
- if (getString(MIGRATED_SP_CE_ONLY, null, 0) == null) {
+
+ // Call setCeStorageProtection(), to re-encrypt the CE key with the SP if it's currently
+ // encrypted by an empty secret. Skip this if it was definitely already done as part of the
+ // upgrade to Android 14, since while setCeStorageProtection() is idempotent it does log
+ // some error messages when called again. Do not skip this if
+ // config_disableWeaverOnUnsecuredUsers=true, since in that case we'd like to recover from
+ // the case where an earlier upgrade to Android 14 incorrectly skipped this step.
+ if (getString(MIGRATED_SP_CE_ONLY, null, 0) == null
+ || isWeaverDisabledOnUnsecuredUsers()) {
Slogf.i(TAG, "Encrypting CE key of user %d with synthetic password", userId);
setCeStorageProtection(userId, sp);
}
@@ -1165,6 +1240,17 @@
initKeystoreSuperKeys(userId, sp, /* allowExisting= */ true);
}
+ private void destroyMigratedProtectors() {
+ if (!mProtectorsToDestroyOnBootCompleted.isEmpty()) {
+ synchronized (mSpManager) {
+ for (Pair<Long, Integer> pair : mProtectorsToDestroyOnBootCompleted) {
+ mSpManager.destroyLskfBasedProtector(pair.first, pair.second);
+ }
+ }
+ }
+ mProtectorsToDestroyOnBootCompleted = null; // The list is no longer needed.
+ }
+
/**
* Returns the lowest password quality that still presents the same UI for entering it.
*
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index 3a429b0..47788f2 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -195,6 +195,8 @@
// ERROR: password / token fails verification
// RETRY: password / token verification is throttled at the moment.
@Nullable public VerifyCredentialResponse gkResponse;
+ // For unlockLskfBasedProtector() this is set to true if the protector uses Weaver.
+ public boolean usedWeaver;
}
/**
@@ -532,6 +534,11 @@
Settings.Global.DEVICE_PROVISIONED, 0) != 0;
}
+ private boolean isWeaverDisabledOnUnsecuredUsers() {
+ return mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_disableWeaverOnUnsecuredUsers);
+ }
+
@VisibleForTesting
protected android.hardware.weaver.V1_0.IWeaver getWeaverHidlService() throws RemoteException {
try {
@@ -1011,7 +1018,13 @@
Slogf.i(TAG, "Creating LSKF-based protector %016x for user %d", protectorId, userId);
- final IWeaver weaver = getWeaverService();
+ final IWeaver weaver;
+ if (credential.isNone() && isWeaverDisabledOnUnsecuredUsers()) {
+ weaver = null;
+ Slog.w(TAG, "Not using Weaver for unsecured user (disabled by config)");
+ } else {
+ weaver = getWeaverService();
+ }
if (weaver != null) {
// Weaver is available, so make the protector use it to verify the LSKF. Do this even
// if the LSKF is empty, as that gives us support for securely deleting the protector.
@@ -1404,6 +1417,7 @@
int weaverSlot = loadWeaverSlot(protectorId, userId);
if (weaverSlot != INVALID_WEAVER_SLOT) {
// Protector uses Weaver to verify the LSKF
+ result.usedWeaver = true;
final IWeaver weaver = getWeaverService();
if (weaver == null) {
Slog.e(TAG, "Protector uses Weaver, but Weaver is unavailable");
diff --git a/services/core/java/com/android/server/notification/GroupHelper.java b/services/core/java/com/android/server/notification/GroupHelper.java
index e8d14cb..9b9be4c 100644
--- a/services/core/java/com/android/server/notification/GroupHelper.java
+++ b/services/core/java/com/android/server/notification/GroupHelper.java
@@ -832,8 +832,9 @@
FullyQualifiedGroupKey newGroup) { }
/**
- * Called when a notification channel is updated, so that this helper can adjust
- * the aggregate groups by moving children if their section has changed.
+ * Called when a notification channel is updated (channel attributes have changed),
+ * so that this helper can adjust the aggregate groups by moving children
+ * if their section has changed.
* see {@link #onNotificationPostedWithDelay(NotificationRecord, List, Map)}
* @param userId the userId of the channel
* @param pkgName the channel's package
@@ -853,24 +854,48 @@
}
}
- // The list of notification operations required after the channel update
- final ArrayList<NotificationMoveOp> notificationsToMove = new ArrayList<>();
+ regroupNotifications(userId, pkgName, notificationsToCheck);
+ }
+ }
- // Check any already auto-grouped notifications that may need to be re-grouped
- // after the channel update
- notificationsToMove.addAll(
- getAutogroupedNotificationsMoveOps(userId, pkgName,
- notificationsToCheck));
+ /**
+ * Called when an individuial notification's channel is updated (moved to a new channel),
+ * so that this helper can adjust the aggregate groups by moving children
+ * if their section has changed.
+ * see {@link #onNotificationPostedWithDelay(NotificationRecord, List, Map)}
+ *
+ * @param record the notification which had its channel updated
+ */
+ @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void onChannelUpdated(final NotificationRecord record) {
+ synchronized (mAggregatedNotifications) {
+ ArrayMap<String, NotificationRecord> notificationsToCheck = new ArrayMap<>();
+ notificationsToCheck.put(record.getKey(), record);
+ regroupNotifications(record.getUserId(), record.getSbn().getPackageName(),
+ notificationsToCheck);
+ }
+ }
- // Check any ungrouped notifications that may need to be auto-grouped
- // after the channel update
- notificationsToMove.addAll(
- getUngroupedNotificationsMoveOps(userId, pkgName, notificationsToCheck));
+ @GuardedBy("mAggregatedNotifications")
+ private void regroupNotifications(int userId, String pkgName,
+ ArrayMap<String, NotificationRecord> notificationsToCheck) {
+ // The list of notification operations required after the channel update
+ final ArrayList<NotificationMoveOp> notificationsToMove = new ArrayList<>();
- // Batch move to new section
- if (!notificationsToMove.isEmpty()) {
- moveNotificationsToNewSection(userId, pkgName, notificationsToMove);
- }
+ // Check any already auto-grouped notifications that may need to be re-grouped
+ // after the channel update
+ notificationsToMove.addAll(
+ getAutogroupedNotificationsMoveOps(userId, pkgName,
+ notificationsToCheck));
+
+ // Check any ungrouped notifications that may need to be auto-grouped
+ // after the channel update
+ notificationsToMove.addAll(
+ getUngroupedNotificationsMoveOps(userId, pkgName, notificationsToCheck));
+
+ // Batch move to new section
+ if (!notificationsToMove.isEmpty()) {
+ moveNotificationsToNewSection(userId, pkgName, notificationsToMove);
}
}
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 03fc60c..cd0a2a7 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -106,7 +106,8 @@
protected final String TAG = getClass().getSimpleName().replace('$', '.');
protected final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- private static final int ON_BINDING_DIED_REBIND_DELAY_MS = 10000;
+ protected static final int ON_BINDING_DIED_REBIND_DELAY_MS = 10000;
+ protected static final int ON_BINDING_DIED_REBIND_MSG = 1234;
protected static final String ENABLED_SERVICES_SEPARATOR = ":";
private static final String DB_VERSION_1 = "1";
private static final String DB_VERSION_2 = "2";
@@ -856,7 +857,13 @@
String approvedItem = getApprovedValue(pkgOrComponent);
if (approvedItem != null) {
+ final ComponentName component = ComponentName.unflattenFromString(approvedItem);
if (enabled) {
+ if (component != null && !isValidService(component, userId)) {
+ Log.e(TAG, "Skip allowing " + mConfig.caption + " " + pkgOrComponent
+ + " (userSet: " + userSet + ") for invalid service");
+ return;
+ }
approved.add(approvedItem);
} else {
approved.remove(approvedItem);
@@ -954,7 +961,7 @@
|| isPackageOrComponentAllowed(component.getPackageName(), userId))) {
return false;
}
- return componentHasBindPermission(component, userId);
+ return isValidService(component, userId);
}
private boolean componentHasBindPermission(ComponentName component, int userId) {
@@ -1306,11 +1313,12 @@
if (TextUtils.equals(getPackageName(approvedPackageOrComponent), packageName)) {
final ComponentName component = ComponentName.unflattenFromString(
approvedPackageOrComponent);
- if (component != null && !componentHasBindPermission(component, userId)) {
+ if (component != null && !isValidService(component, userId)) {
approved.removeAt(j);
if (DEBUG) {
Slog.v(TAG, "Removing " + approvedPackageOrComponent
- + " from approved list; no bind permission found "
+ + " from approved list; no bind permission or "
+ + "service interface filter found "
+ mConfig.bindPermission);
}
}
@@ -1329,6 +1337,11 @@
}
}
+ protected boolean isValidService(ComponentName component, int userId) {
+ return componentHasBindPermission(component, userId) && queryPackageForServices(
+ component.getPackageName(), userId).contains(component);
+ }
+
protected boolean isValidEntry(String packageOrComponent, int userId) {
return hasMatchingServices(packageOrComponent, userId);
}
@@ -1486,23 +1499,25 @@
* Called when user switched to unbind all services from other users.
*/
@VisibleForTesting
- void unbindOtherUserServices(int currentUser) {
+ void unbindOtherUserServices(int switchedToUser) {
TimingsTraceAndSlog t = new TimingsTraceAndSlog();
- t.traceBegin("ManagedServices.unbindOtherUserServices_current" + currentUser);
- unbindServicesImpl(currentUser, true /* allExceptUser */);
+ t.traceBegin("ManagedServices.unbindOtherUserServices_current" + switchedToUser);
+ unbindServicesImpl(switchedToUser, true /* allExceptUser */);
t.traceEnd();
}
- void unbindUserServices(int user) {
+ void unbindUserServices(int removedUser) {
TimingsTraceAndSlog t = new TimingsTraceAndSlog();
- t.traceBegin("ManagedServices.unbindUserServices" + user);
- unbindServicesImpl(user, false /* allExceptUser */);
+ t.traceBegin("ManagedServices.unbindUserServices" + removedUser);
+ unbindServicesImpl(removedUser, false /* allExceptUser */);
t.traceEnd();
}
void unbindServicesImpl(int user, boolean allExceptUser) {
final SparseArray<Set<ComponentName>> componentsToUnbind = new SparseArray<>();
synchronized (mMutex) {
+ // Remove enqueued rebinds to avoid rebinding services for a switched user
+ mHandler.removeMessages(ON_BINDING_DIED_REBIND_MSG);
final Set<ManagedServiceInfo> removableBoundServices = getRemovableConnectedServices();
for (ManagedServiceInfo info : removableBoundServices) {
if ((allExceptUser && (info.userid != user))
@@ -1697,6 +1712,7 @@
mServicesRebinding.add(servicesBindingTag);
mHandler.postDelayed(() ->
reregisterService(name, userid),
+ ON_BINDING_DIED_REBIND_MSG,
ON_BINDING_DIED_REBIND_DELAY_MS);
} else {
Slog.v(TAG, getCaption() + " not rebinding in user " + userid
diff --git a/services/core/java/com/android/server/notification/NotificationAdjustmentExtractor.java b/services/core/java/com/android/server/notification/NotificationAdjustmentExtractor.java
index 97bbc23..2dd4f83 100644
--- a/services/core/java/com/android/server/notification/NotificationAdjustmentExtractor.java
+++ b/services/core/java/com/android/server/notification/NotificationAdjustmentExtractor.java
@@ -15,6 +15,9 @@
*/
package com.android.server.notification;
+import static android.service.notification.Adjustment.KEY_TYPE;
+import static android.service.notification.Flags.notificationForceGrouping;
+
import android.content.Context;
import android.util.Slog;
@@ -24,6 +27,7 @@
public class NotificationAdjustmentExtractor implements NotificationSignalExtractor {
private static final String TAG = "AdjustmentExtractor";
private static final boolean DBG = false;
+ private GroupHelper mGroupHelper;
public void initialize(Context ctx, NotificationUsageStats usageStats) {
@@ -35,8 +39,27 @@
if (DBG) Slog.d(TAG, "skipping empty notification");
return null;
}
+
+ final boolean hasAdjustedClassification = record.hasAdjustment(KEY_TYPE);
record.applyAdjustments();
+ if (notificationForceGrouping()
+ && android.service.notification.Flags.notificationClassification()) {
+ // Classification adjustments trigger regrouping
+ if (mGroupHelper != null && hasAdjustedClassification) {
+ return new RankingReconsideration(record.getKey(), 0) {
+ @Override
+ public void work() {
+ }
+
+ @Override
+ public void applyChangesLocked(NotificationRecord record) {
+ mGroupHelper.onChannelUpdated(record);
+ }
+ };
+ }
+ }
+
return null;
}
@@ -49,4 +72,9 @@
public void setZenHelper(ZenModeHelper helper) {
}
+
+ @Override
+ public void setGroupHelper(GroupHelper groupHelper) {
+ mGroupHelper = groupHelper;
+ }
}
diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
index 06f419a..ea4a6db 100644
--- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
+++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
@@ -17,6 +17,7 @@
package com.android.server.notification;
import static android.app.Flags.sortSectionByTime;
+import static android.app.Notification.CATEGORY_MESSAGE;
import static android.app.Notification.FLAG_INSISTENT;
import static android.app.Notification.FLAG_ONLY_ALERT_ONCE;
import static android.app.NotificationManager.IMPORTANCE_MIN;
@@ -42,6 +43,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
+import android.content.pm.ShortcutInfo;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.database.ContentObserver;
@@ -1641,7 +1643,7 @@
}
// recent conversation
- if (record.isConversation()
+ if ((record.isConversation() || isConversationMessage(record))
&& record.getNotification().getWhen() > mLastAvalancheTriggerTimestamp) {
return true;
}
@@ -1656,6 +1658,21 @@
return false;
}
+
+ // Relaxed signals for conversations messages
+ private boolean isConversationMessage(final NotificationRecord record) {
+ if (!CATEGORY_MESSAGE.equals(record.getSbn().getNotification().category)) {
+ return false;
+ }
+ if (record.getChannel().isDemoted()) {
+ return false;
+ }
+ final ShortcutInfo shortcut = record.getShortcutInfo();
+ if (shortcut == null) {
+ return false;
+ }
+ return true;
+ }
}
//====================== Observers =============================
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 56e0a89..6c2d4f7 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -519,6 +519,7 @@
private static final long DELAY_FORCE_REGROUP_TIME = 3000;
+
private static final String ACTION_NOTIFICATION_TIMEOUT =
NotificationManagerService.class.getSimpleName() + ".TIMEOUT";
private static final int REQUEST_CODE_TIMEOUT = 1;
@@ -2583,7 +2584,7 @@
mShowReviewPermissionsNotification,
Clock.systemUTC());
mRankingHelper = new RankingHelper(getContext(), mRankingHandler, mPreferencesHelper,
- mZenModeHelper, mUsageStats, extractorNames, mPlatformCompat);
+ mZenModeHelper, mUsageStats, extractorNames, mPlatformCompat, groupHelper);
mSnoozeHelper = snoozeHelper;
mGroupHelper = groupHelper;
mHistoryManager = historyManager;
@@ -6871,22 +6872,9 @@
}
if (android.service.notification.Flags.notificationClassification()
&& adjustments.containsKey(KEY_TYPE)) {
- NotificationChannel newChannel = null;
- int type = adjustments.getInt(KEY_TYPE);
- if (TYPE_NEWS == type) {
- newChannel = mPreferencesHelper.getNotificationChannel(
- r.getSbn().getPackageName(), r.getUid(), NEWS_ID, false);
- } else if (TYPE_PROMOTION == type) {
- newChannel = mPreferencesHelper.getNotificationChannel(
- r.getSbn().getPackageName(), r.getUid(), PROMOTIONS_ID, false);
- } else if (TYPE_SOCIAL_MEDIA == type) {
- newChannel = mPreferencesHelper.getNotificationChannel(
- r.getSbn().getPackageName(), r.getUid(), SOCIAL_MEDIA_ID, false);
- } else if (TYPE_CONTENT_RECOMMENDATION == type) {
- newChannel = mPreferencesHelper.getNotificationChannel(
- r.getSbn().getPackageName(), r.getUid(), RECS_ID, false);
- }
- if (newChannel == null) {
+ final NotificationChannel newChannel = getClassificationChannelLocked(r,
+ adjustments);
+ if (newChannel == null || newChannel.getId().equals(r.getChannel().getId())) {
adjustments.remove(KEY_TYPE);
} else {
// swap app provided type with the real thing
@@ -6902,6 +6890,27 @@
}
}
+ @GuardedBy("mNotificationLock")
+ @Nullable
+ private NotificationChannel getClassificationChannelLocked(NotificationRecord r,
+ Bundle adjustments) {
+ int type = adjustments.getInt(KEY_TYPE);
+ if (TYPE_NEWS == type) {
+ return mPreferencesHelper.getNotificationChannel(
+ r.getSbn().getPackageName(), r.getUid(), NEWS_ID, false);
+ } else if (TYPE_PROMOTION == type) {
+ return mPreferencesHelper.getNotificationChannel(
+ r.getSbn().getPackageName(), r.getUid(), PROMOTIONS_ID, false);
+ } else if (TYPE_SOCIAL_MEDIA == type) {
+ return mPreferencesHelper.getNotificationChannel(
+ r.getSbn().getPackageName(), r.getUid(), SOCIAL_MEDIA_ID, false);
+ } else if (TYPE_CONTENT_RECOMMENDATION == type) {
+ return mPreferencesHelper.getNotificationChannel(
+ r.getSbn().getPackageName(), r.getUid(), RECS_ID, false);
+ }
+ return null;
+ }
+
@SuppressWarnings("GuardedBy")
@GuardedBy("mNotificationLock")
void addAutogroupKeyLocked(String key, String groupName, boolean requestSort) {
@@ -12009,6 +12018,10 @@
if (record != null && (record.getSbn().getNotification().flags
& FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY) > 0
&& !record.isCanceledAfterLifetimeExtension()) {
+ // Mark that the notification is being updated due to cancelation, so it won't
+ // be updated again if the app cancels multiple times.
+ record.setCanceledAfterLifetimeExtension(true);
+
boolean isAppForeground = pkg != null && packageImportance == IMPORTANCE_FOREGROUND;
// Save the original Record's post silently value, so we can restore it after we send
@@ -12024,9 +12037,6 @@
PostNotificationTracker tracker = mPostNotificationTrackerFactory.newTracker(null);
tracker.addCleanupRunnable(() -> {
synchronized (mNotificationLock) {
- // Mark that the notification has been updated due to cancelation, so it won't
- // be updated again if the app cancels multiple times.
- record.setCanceledAfterLifetimeExtension(true);
// Set the post silently status to the record's previous value.
record.setPostSilently(savedPostSilentlyState);
// Remove FLAG_ONLY_ALERT_ONCE if the notification did not previously have it.
diff --git a/services/core/java/com/android/server/notification/NotificationSignalExtractor.java b/services/core/java/com/android/server/notification/NotificationSignalExtractor.java
index f0358d1..be34bee 100644
--- a/services/core/java/com/android/server/notification/NotificationSignalExtractor.java
+++ b/services/core/java/com/android/server/notification/NotificationSignalExtractor.java
@@ -55,4 +55,9 @@
void setZenHelper(ZenModeHelper helper);
default void setCompatChangeLogger(IPlatformCompat platformCompat){};
+
+ /**
+ * @param groupHelper Helper for auto-grouping notifications
+ */
+ default void setGroupHelper(GroupHelper groupHelper){};
}
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index 03dd935..f06d6405 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -23,7 +23,6 @@
import static android.text.TextUtils.formatSimple;
import android.annotation.NonNull;
-import android.app.NotificationManager;
import android.content.Context;
import android.service.notification.RankingHelperProto;
import android.util.ArrayMap;
@@ -61,7 +60,7 @@
})
public RankingHelper(Context context, RankingHandler rankingHandler, RankingConfig config,
ZenModeHelper zenHelper, NotificationUsageStats usageStats, String[] extractorNames,
- IPlatformCompat platformCompat) {
+ IPlatformCompat platformCompat, GroupHelper groupHelper) {
mContext = context;
mRankingHandler = rankingHandler;
if (sortSectionByTime()) {
@@ -80,6 +79,7 @@
extractor.initialize(mContext, usageStats);
extractor.setConfig(config);
extractor.setZenHelper(zenHelper);
+ extractor.setGroupHelper(groupHelper);
if (restrictAudioAttributesAlarm() || restrictAudioAttributesMedia()
|| restrictAudioAttributesCall()) {
extractor.setCompatChangeLogger(platformCompat);
diff --git a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
index 03a34f2..b0d69e6 100644
--- a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
+++ b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
@@ -19,6 +19,8 @@
import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.DEVICE_CONFIG_UPDATE_BUNDLE_KEY;
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.MODEL_LOADED_BROADCAST_INTENT;
+import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.MODEL_UNLOADED_BROADCAST_INTENT;
import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.REGISTER_MODEL_UPDATE_CALLBACK_BUNDLE_KEY;
import static com.android.server.ondeviceintelligence.BundleUtil.sanitizeInferenceParams;
@@ -154,7 +156,7 @@
@GuardedBy("mLock")
private String[] mTemporaryBroadcastKeys;
@GuardedBy("mLock")
- private String mBroadcastPackageName;
+ private String mBroadcastPackageName = SYSTEM_PACKAGE;
@GuardedBy("mLock")
private String mTemporaryConfigNamespace;
@@ -921,10 +923,7 @@
}
}
- return new String[]{mContext.getResources().getString(
- R.string.config_onDeviceIntelligenceModelLoadedBroadcastKey),
- mContext.getResources().getString(
- R.string.config_onDeviceIntelligenceModelUnloadedBroadcastKey)};
+ return new String[]{ MODEL_LOADED_BROADCAST_INTENT, MODEL_UNLOADED_BROADCAST_INTENT };
}
@RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index b1b1637..34d939b 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -126,6 +126,7 @@
import com.android.server.SystemServiceManager;
import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.utils.RequestThrottle;
+import com.android.server.pm.verify.pkg.VerifierController;
import libcore.io.IoUtils;
@@ -213,6 +214,7 @@
private final StagingManager mStagingManager;
private AppOpsManager mAppOps;
+ private final VerifierController mVerifierController;
private final HandlerThread mInstallThread;
private final Handler mInstallHandler;
@@ -325,6 +327,7 @@
mGentleUpdateHelper = new GentleUpdateHelper(
context, mInstallThread.getLooper(), new AppStateHelper(context));
mPackageArchiver = new PackageArchiver(mContext, mPm);
+ mVerifierController = new VerifierController(mContext, mInstallHandler);
LocalServices.getService(SystemServiceManager.class).startService(
new Lifecycle(context, this));
@@ -521,7 +524,8 @@
try {
session = PackageInstallerSession.readFromXml(in, mInternalCallback,
mContext, mPm, mInstallThread.getLooper(), mStagingManager,
- mSessionsDir, this, mSilentUpdatePolicy);
+ mSessionsDir, this, mSilentUpdatePolicy,
+ mVerifierController);
} catch (Exception e) {
Slog.e(TAG, "Could not read session", e);
continue;
@@ -1037,7 +1041,8 @@
mSilentUpdatePolicy, mInstallThread.getLooper(), mStagingManager, sessionId,
userId, callingUid, installSource, params, createdMillis, 0L, stageDir, stageCid,
null, null, false, false, false, false, null, SessionInfo.INVALID_ID,
- false, false, false, PackageManager.INSTALL_UNKNOWN, "", null);
+ false, false, false, PackageManager.INSTALL_UNKNOWN, "", null,
+ mVerifierController);
synchronized (mSessions) {
mSessions.put(sessionId, session);
@@ -1047,6 +1052,7 @@
mCallbacks.notifySessionCreated(session.sessionId, session.userId);
mSettingsWriteRequest.schedule();
+
if (LOGD) {
Slog.d(TAG, "Created session id=" + sessionId + " staged=" + params.isStaged);
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index ff8a69d..c581622 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -38,6 +38,7 @@
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES;
import static android.content.pm.PackageManager.INSTALL_STAGED;
import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
+import static android.content.pm.verify.pkg.VerificationSession.VERIFICATION_INCOMPLETE_UNKNOWN;
import static android.os.Process.INVALID_UID;
import static android.provider.DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE;
import static android.system.OsConstants.O_CREAT;
@@ -87,6 +88,7 @@
import android.content.pm.DataLoaderParams;
import android.content.pm.DataLoaderParamsParcel;
import android.content.pm.FileSystemControlParcel;
+import android.content.pm.Flags;
import android.content.pm.IDataLoader;
import android.content.pm.IDataLoaderStatusListener;
import android.content.pm.IOnChecksumsReadyListener;
@@ -108,6 +110,7 @@
import android.content.pm.PackageManager.PackageInfoFlags;
import android.content.pm.PackageManagerInternal;
import android.content.pm.SigningDetails;
+import android.content.pm.SigningInfo;
import android.content.pm.dex.DexMetadataHelper;
import android.content.pm.parsing.ApkLite;
import android.content.pm.parsing.ApkLiteParseUtils;
@@ -115,6 +118,7 @@
import android.content.pm.parsing.result.ParseResult;
import android.content.pm.parsing.result.ParseTypeImpl;
import android.content.pm.verify.domain.DomainSet;
+import android.content.pm.verify.pkg.VerificationStatus;
import android.content.res.ApkAssets;
import android.content.res.AssetManager;
import android.content.res.Configuration;
@@ -122,6 +126,7 @@
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.icu.util.ULocale;
+import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
@@ -133,6 +138,7 @@
import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.ParcelableException;
+import android.os.PersistableBundle;
import android.os.Process;
import android.os.RemoteException;
import android.os.RevocableFileDescriptor;
@@ -190,6 +196,7 @@
import com.android.server.pm.dex.DexManager;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.pm.pkg.PackageStateInternal;
+import com.android.server.pm.verify.pkg.VerifierController;
import libcore.io.IoUtils;
import libcore.util.EmptyArray;
@@ -218,6 +225,7 @@
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Predicate;
+import java.util.function.Supplier;
public class PackageInstallerSession extends IPackageInstallerSession.Stub {
private static final String TAG = "PackageInstallerSession";
@@ -404,6 +412,7 @@
* Note all calls must be done outside {@link #mLock} to prevent lock inversion.
*/
private final StagingManager mStagingManager;
+ @NonNull private final VerifierController mVerifierController;
final int sessionId;
final int userId;
@@ -1156,7 +1165,8 @@
boolean prepared, boolean committed, boolean destroyed, boolean sealed,
@Nullable int[] childSessionIds, int parentSessionId, boolean isReady,
boolean isFailed, boolean isApplied, int sessionErrorCode,
- String sessionErrorMessage, DomainSet preVerifiedDomains) {
+ String sessionErrorMessage, DomainSet preVerifiedDomains,
+ @NonNull VerifierController verifierController) {
mCallback = callback;
mContext = context;
mPm = pm;
@@ -1165,6 +1175,7 @@
mSilentUpdatePolicy = silentUpdatePolicy;
mHandler = new Handler(looper, mHandlerCallback);
mStagingManager = stagingManager;
+ mVerifierController = verifierController;
this.sessionId = sessionId;
this.userId = userId;
@@ -1249,6 +1260,14 @@
"Archived installation can only use Streaming System DataLoader.");
}
}
+
+ if (Flags.verificationService()) {
+ // Start binding to the verification service, if not bound already.
+ mVerifierController.bindToVerifierServiceIfNeeded(() -> pm.snapshotComputer(), userId);
+ if (!TextUtils.isEmpty(params.appPackageName)) {
+ mVerifierController.notifyPackageNameAvailable(params.appPackageName);
+ }
+ }
}
PackageInstallerHistoricalSession createHistoricalSession() {
@@ -2821,7 +2840,35 @@
// since installation is in progress.
activate();
}
+ if (Flags.verificationService()) {
+ final Supplier<Computer> snapshotSupplier = mPm::snapshotComputer;
+ if (mVerifierController.isVerifierInstalled(snapshotSupplier, userId)) {
+ // TODO: extract shared library declarations
+ final SigningInfo signingInfo;
+ synchronized (mLock) {
+ signingInfo = new SigningInfo(mSigningDetails);
+ }
+ // Send the request to the verifier and wait for its response before the rest of
+ // the installation can proceed.
+ if (!mVerifierController.startVerificationSession(snapshotSupplier, userId,
+ sessionId, params.appPackageName, Uri.fromFile(stageDir), signingInfo,
+ /* declaredLibraries= */null, /* extensionParams= */ null,
+ new VerifierCallback(), /* retry= */ false)) {
+ // A verifier is installed but cannot be connected. Installation disallowed.
+ onSessionVerificationFailure(INSTALL_FAILED_INTERNAL_ERROR,
+ "A verifier agent is available on device but cannot be connected.");
+ }
+ } else {
+ // Verifier is not installed. Let the installation pass for now.
+ resumeVerify();
+ }
+ } else {
+ // New verification feature is not enabled. Proceed to the rest of the verification.
+ resumeVerify();
+ }
+ }
+ private void resumeVerify() {
if (mVerificationInProgress) {
Slog.w(TAG, "Verification is already in progress for session " + sessionId);
return;
@@ -2856,6 +2903,66 @@
}
}
+ /**
+ * Used for the VerifierController to report status back.
+ */
+ public class VerifierCallback {
+ /**
+ * Called by the VerifierController when the connection has failed.
+ */
+ public void onConnectionFailed() {
+ mHandler.post(() -> {
+ onSessionVerificationFailure(INSTALL_FAILED_VERIFICATION_FAILURE,
+ "A verifier agent is available on device but cannot be connected.");
+ });
+ }
+ /**
+ * Called by the VerifierController when the verification request has timed out.
+ */
+ public void onTimeout() {
+ mHandler.post(() -> {
+ mVerifierController.notifyVerificationTimeout(sessionId);
+ onSessionVerificationFailure(INSTALL_FAILED_VERIFICATION_FAILURE,
+ "Verification timed out; missing a response from the verifier within the"
+ + " time limit");
+ });
+ }
+ /**
+ * Called by the VerifierController when the verification request has received a complete
+ * response.
+ */
+ public void onVerificationCompleteReceived(@NonNull VerificationStatus statusReceived,
+ @Nullable PersistableBundle extensionResponse) {
+ // TODO: handle extension response
+ mHandler.post(() -> {
+ if (statusReceived.isVerified()) {
+ // Continue with the rest of the verification and installation.
+ resumeVerify();
+ } else {
+ StringBuilder sb = new StringBuilder("Verifier rejected the installation");
+ if (!TextUtils.isEmpty(statusReceived.getFailureMessage())) {
+ sb.append(" with message: ").append(statusReceived.getFailureMessage());
+ }
+ onSessionVerificationFailure(INSTALL_FAILED_VERIFICATION_FAILURE,
+ sb.toString());
+ }
+ });
+ }
+ /**
+ * Called by the VerifierController when the verification request has received an incomplete
+ * response.
+ */
+ public void onVerificationIncompleteReceived(int incompleteReason) {
+ mHandler.post(() -> {
+ if (incompleteReason == VERIFICATION_INCOMPLETE_UNKNOWN) {
+ // TODO: change this to a user confirmation and handle other incomplete reasons
+ onSessionVerificationFailure(INSTALL_FAILED_INTERNAL_ERROR,
+ "Verification cannot be completed for unknown reasons.");
+ }
+ });
+ }
+ }
+
private IntentSender getRemoteStatusReceiver() {
synchronized (mLock) {
return mRemoteStatusReceiver;
@@ -5369,6 +5476,14 @@
}
} catch (InstallerException ignored) {
}
+ if (Flags.verificationService()
+ && !TextUtils.isEmpty(params.appPackageName)
+ && !isCommitted()) {
+ // Only notify for the cancellation if the verification request has not
+ // been sent out, which happens right after commit() is called.
+ mVerifierController.notifyVerificationCancelled(
+ params.appPackageName);
+ }
}
void dump(IndentingPrintWriter pw) {
@@ -5768,7 +5883,8 @@
@NonNull PackageManagerService pm, Looper installerThread,
@NonNull StagingManager stagingManager, @NonNull File sessionsDir,
@NonNull PackageSessionProvider sessionProvider,
- @NonNull SilentUpdatePolicy silentUpdatePolicy)
+ @NonNull SilentUpdatePolicy silentUpdatePolicy,
+ @NonNull VerifierController verifierController)
throws IOException, XmlPullParserException {
final int sessionId = in.getAttributeInt(null, ATTR_SESSION_ID);
final int userId = in.getAttributeInt(null, ATTR_USER_ID);
@@ -5972,6 +6088,6 @@
installerUid, installSource, params, createdMillis, committedMillis, stageDir,
stageCid, fileArray, checksumsMap, prepared, committed, destroyed, sealed,
childSessionIdsArray, parentSessionId, isReady, isFailed, isApplied,
- sessionErrorCode, sessionErrorMessage, preVerifiedDomains);
+ sessionErrorCode, sessionErrorMessage, preVerifiedDomains, verifierController);
}
}
diff --git a/services/core/java/com/android/server/pm/verify/pkg/VerificationStatusTracker.java b/services/core/java/com/android/server/pm/verify/pkg/VerificationStatusTracker.java
new file mode 100644
index 0000000..db747f9
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/pkg/VerificationStatusTracker.java
@@ -0,0 +1,101 @@
+/*
+ * 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.pm.verify.pkg;
+
+import android.annotation.CurrentTimeMillisLong;
+import android.annotation.NonNull;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * This class keeps record of the current timeout status of a verification request.
+ */
+public final class VerificationStatusTracker {
+ private final @CurrentTimeMillisLong long mStartTime;
+ private @CurrentTimeMillisLong long mTimeoutTime;
+ private final @CurrentTimeMillisLong long mMaxTimeoutTime;
+ @NonNull
+ private final VerifierController.Injector mInjector;
+ // Record the package name associated with the verification result
+ @NonNull
+ private final String mPackageName;
+
+ /**
+ * By default, the timeout time is the default timeout duration plus the current time (when
+ * the timer starts for a verification request). Both the default timeout time and the max
+ * timeout time cannot be changed after the timer has started, but the actual timeout time
+ * can be extended via {@link #extendTimeRemaining} to the maximum allowed.
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
+ public VerificationStatusTracker(@NonNull String packageName,
+ long defaultTimeoutMillis, long maxExtendedTimeoutMillis,
+ @NonNull VerifierController.Injector injector) {
+ mPackageName = packageName;
+ mStartTime = injector.getCurrentTimeMillis();
+ mTimeoutTime = mStartTime + defaultTimeoutMillis;
+ mMaxTimeoutTime = mStartTime + maxExtendedTimeoutMillis;
+ mInjector = injector;
+ }
+
+ /**
+ * Used by the controller to inform the verifier agent about the timestamp when the verification
+ * request will timeout.
+ */
+ public @CurrentTimeMillisLong long getTimeoutTime() {
+ return mTimeoutTime;
+ }
+
+ /**
+ * Used by the controller to decide when to check for timeout again.
+ * @return 0 if the timeout time has been reached, otherwise the remaining time in milliseconds
+ * before the timeout is reached.
+ */
+ public @CurrentTimeMillisLong long getRemainingTime() {
+ final long remainingTime = mTimeoutTime - mInjector.getCurrentTimeMillis();
+ if (remainingTime < 0) {
+ return 0;
+ }
+ return remainingTime;
+ }
+
+ /**
+ * Used by the controller to extend the timeout duration of the verification request, upon
+ * receiving the callback from the verifier agent.
+ * @return the amount of time in millis that the timeout has been extended, subject to the max
+ * amount allowed.
+ */
+ public long extendTimeRemaining(@CurrentTimeMillisLong long additionalMs) {
+ if (mTimeoutTime + additionalMs > mMaxTimeoutTime) {
+ additionalMs = mMaxTimeoutTime - mTimeoutTime;
+ }
+ mTimeoutTime += additionalMs;
+ return additionalMs;
+ }
+
+ /**
+ * Used by the controller to get the timeout status of the request.
+ * @return False if the request still has some time left before timeout, otherwise return True.
+ */
+ public boolean isTimeout() {
+ return mInjector.getCurrentTimeMillis() >= mTimeoutTime;
+ }
+
+ @NonNull
+ public String getPackageName() {
+ return mPackageName;
+ }
+}
diff --git a/services/core/java/com/android/server/pm/verify/pkg/VerifierController.java b/services/core/java/com/android/server/pm/verify/pkg/VerifierController.java
new file mode 100644
index 0000000..7eac940
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/pkg/VerifierController.java
@@ -0,0 +1,645 @@
+/*
+ * 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.pm.verify.pkg;
+
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+import static android.os.Process.SYSTEM_UID;
+import static android.provider.DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.SharedLibraryInfo;
+import android.content.pm.SigningInfo;
+import android.content.pm.verify.pkg.IVerificationSessionCallback;
+import android.content.pm.verify.pkg.IVerificationSessionInterface;
+import android.content.pm.verify.pkg.IVerifierService;
+import android.content.pm.verify.pkg.VerificationSession;
+import android.content.pm.verify.pkg.VerificationStatus;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Handler;
+import android.os.PersistableBundle;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.provider.DeviceConfig;
+import android.util.Pair;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.infra.AndroidFuture;
+import com.android.internal.infra.ServiceConnector;
+import com.android.server.pm.Computer;
+import com.android.server.pm.PackageInstallerSession;
+import com.android.server.pm.pkg.PackageStateInternal;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
+
+/**
+ * This class manages the bind to the verifier agent installed on the device that implements
+ * {@link android.content.pm.verify.pkg.VerifierService} and handles all its interactions.
+ */
+public class VerifierController {
+ private static final String TAG = "VerifierController";
+ private static final boolean DEBUG = false;
+
+ /**
+ * Configurable maximum amount of time in milliseconds to wait for a verifier to respond to
+ * a verification request.
+ * Flag type: {@code long}
+ * Namespace: NAMESPACE_PACKAGE_MANAGER_SERVICE
+ */
+ private static final String PROPERTY_VERIFICATION_REQUEST_TIMEOUT_MILLIS =
+ "verification_request_timeout_millis";
+ // Default duration to wait for a verifier to respond to a verification request.
+ private static final long DEFAULT_VERIFICATION_REQUEST_TIMEOUT_MILLIS =
+ TimeUnit.MINUTES.toMillis(1);
+ /**
+ * Configurable maximum amount of time in milliseconds that the verifier can request to extend
+ * the verification request timeout duration to. This is the maximum amount of time the system
+ * can wait for a request before it times out.
+ * Flag type: {@code long}
+ * Namespace: NAMESPACE_PACKAGE_MANAGER_SERVICE
+ */
+ private static final String PROPERTY_MAX_VERIFICATION_REQUEST_EXTENDED_TIMEOUT_MILLIS =
+ "max_verification_request_extended_timeout_millis";
+ // Max duration allowed to wait for a verifier to respond to a verification request.
+ private static final long DEFAULT_MAX_VERIFICATION_REQUEST_EXTENDED_TIMEOUT_MILLIS =
+ TimeUnit.MINUTES.toMillis(10);
+ // The maximum amount of time to wait from the moment when the session requires a verification,
+ // till when the request is delivered to the verifier, pending the connection to be established.
+ private static final long CONNECTION_TIMEOUT_SECONDS = 10;
+ // The maximum amount of time to wait before the system unbinds from the verifier.
+ private static final long UNBIND_TIMEOUT_MILLIS = TimeUnit.HOURS.toMillis(6);
+
+ private final Context mContext;
+ private final Handler mHandler;
+ @Nullable
+ private ServiceConnector<IVerifierService> mRemoteService;
+ @Nullable
+ private ComponentName mRemoteServiceComponentName;
+ @NonNull
+ private Injector mInjector;
+
+ // Repository of active verification sessions and their status, mapping from id to status.
+ @NonNull
+ @GuardedBy("mVerificationStatus")
+ private final SparseArray<VerificationStatusTracker> mVerificationStatus = new SparseArray<>();
+
+ public VerifierController(@NonNull Context context, @NonNull Handler handler) {
+ this(context, handler, new Injector());
+ }
+
+ @VisibleForTesting
+ public VerifierController(@NonNull Context context, @NonNull Handler handler,
+ @NonNull Injector injector) {
+ mContext = context;
+ mHandler = handler;
+ mInjector = injector;
+ }
+
+ /**
+ * Used by the installation session to check if a verifier is installed.
+ */
+ public boolean isVerifierInstalled(Supplier<Computer> snapshotSupplier, int userId) {
+ if (isVerifierConnected()) {
+ // Verifier is connected or is being connected, so it must be installed.
+ return true;
+ }
+ // Verifier has been disconnected, or it hasn't been connected. Check if it's installed.
+ return mInjector.isVerifierInstalled(snapshotSupplier.get(), userId);
+ }
+
+ /**
+ * Called to start querying and binding to a qualified verifier agent.
+ *
+ * @return False if a qualified verifier agent doesn't exist on device, so that the system can
+ * handle this situation immediately after the call.
+ * <p>
+ * Notice that since this is an async call, even if this method returns true, it doesn't
+ * necessarily mean that the binding connection was successful. However, the system will only
+ * try to bind once per installation session, so that it doesn't waste resource by repeatedly
+ * trying to bind if the verifier agent isn't available during a short amount of time.
+ * <p>
+ * If the verifier agent exists but cannot be started for some reason, all the notify* methods
+ * in this class will fail asynchronously and quietly. The system will learn about the failure
+ * after receiving the failure from
+ * {@link PackageInstallerSession.VerifierCallback#onConnectionFailed}.
+ */
+ public boolean bindToVerifierServiceIfNeeded(Supplier<Computer> snapshotSupplier, int userId) {
+ if (DEBUG) {
+ Slog.i(TAG, "Requesting to bind to the verifier service.");
+ }
+ if (mRemoteService != null) {
+ // Already connected
+ if (DEBUG) {
+ Slog.i(TAG, "Verifier service is already connected.");
+ }
+ return true;
+ }
+ Pair<ServiceConnector<IVerifierService>, ComponentName> result =
+ mInjector.getRemoteService(snapshotSupplier.get(), mContext, userId, mHandler);
+ if (result == null || result.first == null) {
+ if (DEBUG) {
+ Slog.i(TAG, "Unable to find a qualified verifier.");
+ }
+ return false;
+ }
+ mRemoteService = result.first;
+ mRemoteServiceComponentName = result.second;
+ if (DEBUG) {
+ Slog.i(TAG, "Connecting to a qualified verifier: " + mRemoteServiceComponentName);
+ }
+ mRemoteService.setServiceLifecycleCallbacks(
+ new ServiceConnector.ServiceLifecycleCallbacks<>() {
+ @Override
+ public void onConnected(@NonNull IVerifierService service) {
+ Slog.i(TAG, "Verifier " + mRemoteServiceComponentName + " is connected");
+ }
+
+ @Override
+ public void onDisconnected(@NonNull IVerifierService service) {
+ Slog.w(TAG,
+ "Verifier " + mRemoteServiceComponentName + " is disconnected");
+ destroy();
+ }
+
+ @Override
+ public void onBinderDied() {
+ Slog.w(TAG, "Verifier " + mRemoteServiceComponentName + " has died");
+ destroy();
+ }
+
+ private void destroy() {
+ if (isVerifierConnected()) {
+ mRemoteService.unbind();
+ mRemoteService = null;
+ mRemoteServiceComponentName = null;
+ }
+ }
+ });
+ AndroidFuture<IVerifierService> unusedFuture = mRemoteService.connect();
+ return true;
+ }
+
+ private boolean isVerifierConnected() {
+ return mRemoteService != null && mRemoteServiceComponentName != null;
+ }
+
+ /**
+ * Called to notify the bound verifier agent that a package name is available and will soon be
+ * requested for verification.
+ */
+ public void notifyPackageNameAvailable(@NonNull String packageName) {
+ if (!isVerifierConnected()) {
+ if (DEBUG) {
+ Slog.i(TAG, "Verifier is not connected. Not notifying package name available");
+ }
+ return;
+ }
+ // Best effort. We don't check for the result.
+ mRemoteService.run(service -> {
+ if (DEBUG) {
+ Slog.i(TAG, "Notifying package name available for " + packageName);
+ }
+ service.onPackageNameAvailable(packageName);
+ });
+ }
+
+ /**
+ * Called to notify the bound verifier agent that a package previously notified via
+ * {@link android.content.pm.verify.pkg.VerifierService#onPackageNameAvailable(String)}
+ * will no longer be requested for verification, possibly because the installation is canceled.
+ */
+ public void notifyVerificationCancelled(@NonNull String packageName) {
+ if (!isVerifierConnected()) {
+ if (DEBUG) {
+ Slog.i(TAG, "Verifier is not connected. Not notifying verification cancelled");
+ }
+ return;
+ }
+ // Best effort. We don't check for the result.
+ mRemoteService.run(service -> {
+ if (DEBUG) {
+ Slog.i(TAG, "Notifying verification cancelled for " + packageName);
+ }
+ service.onVerificationCancelled(packageName);
+ });
+ }
+
+ /**
+ * Called to notify the bound verifier agent that a package that's pending installation needs
+ * to be verified right now.
+ * <p>The verification request must be sent to the verifier as soon as the verifier is
+ * connected. If the connection cannot be made within {@link #CONNECTION_TIMEOUT_SECONDS}</p>
+ * of when the request is sent out, we consider the verification to be failed and notify the
+ * installation session.</p>
+ * <p>If a response is not returned from the verifier agent within a timeout duration from the
+ * time the request is sent to the verifier, the verification will be considered a failure.</p>
+ *
+ * @param retry whether this request is for retrying a previously incomplete verification.
+ */
+ public boolean startVerificationSession(Supplier<Computer> snapshotSupplier, int userId,
+ int installationSessionId, String packageName,
+ Uri stagedPackageUri, SigningInfo signingInfo,
+ List<SharedLibraryInfo> declaredLibraries,
+ PersistableBundle extensionParams, PackageInstallerSession.VerifierCallback callback,
+ boolean retry) {
+ // Try connecting to the verifier if not already connected
+ if (!bindToVerifierServiceIfNeeded(snapshotSupplier, userId)) {
+ return false;
+ }
+ if (!isVerifierConnected()) {
+ if (DEBUG) {
+ Slog.i(TAG, "Verifier is not connected. Not notifying verification required");
+ }
+ // Normally this should not happen because we just tried to bind. But if the verifier
+ // just crashed or just became unavailable, we should notify the installation session so
+ // it can finish with a verification failure.
+ return false;
+ }
+ // For now, the verification id is the same as the installation session id.
+ final int verificationId = installationSessionId;
+ final VerificationSession session = new VerificationSession(
+ /* id= */ verificationId,
+ /* installSessionId= */ installationSessionId,
+ packageName, stagedPackageUri, signingInfo, declaredLibraries, extensionParams,
+ new VerificationSessionInterface(),
+ new VerificationSessionCallback(callback));
+ AndroidFuture<Void> unusedFuture = mRemoteService.post(service -> {
+ if (!retry) {
+ if (DEBUG) {
+ Slog.i(TAG, "Notifying verification required for session " + verificationId);
+ }
+ service.onVerificationRequired(session);
+ } else {
+ if (DEBUG) {
+ Slog.i(TAG, "Notifying verification retry for session " + verificationId);
+ }
+ service.onVerificationRetry(session);
+ }
+ }).orTimeout(CONNECTION_TIMEOUT_SECONDS, TimeUnit.SECONDS).whenComplete((res, err) -> {
+ if (err != null) {
+ Slog.e(TAG, "Error notifying verification request for session " + verificationId,
+ err);
+ // Notify the installation session so it can finish with verification failure.
+ callback.onConnectionFailed();
+ }
+ });
+ // Keep track of the session status with the ID. Start counting down the session timeout.
+ final long defaultTimeoutMillis = mInjector.getVerificationRequestTimeoutMillis();
+ final long maxExtendedTimeoutMillis = mInjector.getMaxVerificationExtendedTimeoutMillis();
+ final VerificationStatusTracker tracker = new VerificationStatusTracker(
+ packageName, defaultTimeoutMillis, maxExtendedTimeoutMillis, mInjector);
+ synchronized (mVerificationStatus) {
+ mVerificationStatus.put(verificationId, tracker);
+ }
+ startTimeoutCountdown(verificationId, tracker, callback, defaultTimeoutMillis);
+ return true;
+ }
+
+ private void startTimeoutCountdown(int verificationId, VerificationStatusTracker tracker,
+ PackageInstallerSession.VerifierCallback callback, long delayMillis) {
+ mHandler.postDelayed(() -> {
+ if (DEBUG) {
+ Slog.i(TAG, "Checking request timeout for " + verificationId);
+ }
+ if (!tracker.isTimeout()) {
+ if (DEBUG) {
+ Slog.i(TAG, "Timeout is not met for " + verificationId + "; check later.");
+ }
+ // If the current session is not timed out yet, check again later.
+ startTimeoutCountdown(verificationId, tracker, callback,
+ /* delayMillis= */ tracker.getRemainingTime());
+ } else {
+ if (DEBUG) {
+ Slog.i(TAG, "Request " + verificationId + " has timed out.");
+ }
+ // The request has timed out. Notify the installation session.
+ callback.onTimeout();
+ // Remove status tracking and stop the timeout countdown
+ removeStatusTracker(verificationId);
+ }
+ }, /* token= */ tracker, delayMillis);
+ }
+
+ /**
+ * Called to notify the bound verifier agent that a verification request has timed out.
+ */
+ public void notifyVerificationTimeout(int verificationId) {
+ if (!isVerifierConnected()) {
+ if (DEBUG) {
+ Slog.i(TAG,
+ "Verifier is not connected. Not notifying timeout for " + verificationId);
+ }
+ return;
+ }
+ AndroidFuture<Void> unusedFuture = mRemoteService.post(service -> {
+ if (DEBUG) {
+ Slog.i(TAG, "Notifying timeout for " + verificationId);
+ }
+ service.onVerificationTimeout(verificationId);
+ }).whenComplete((res, err) -> {
+ if (err != null) {
+ Slog.e(TAG, "Error notifying VerificationTimeout for session "
+ + verificationId, (Throwable) err);
+ }
+ });
+ }
+
+ /**
+ * Remove a status tracker after it's no longer needed.
+ */
+ private void removeStatusTracker(int verificationId) {
+ if (DEBUG) {
+ Slog.i(TAG, "Removing status tracking for verification " + verificationId);
+ }
+ synchronized (mVerificationStatus) {
+ VerificationStatusTracker tracker = mVerificationStatus.removeReturnOld(verificationId);
+ // Cancel the timeout counters if there's any
+ if (tracker != null) {
+ mInjector.stopTimeoutCountdown(mHandler, tracker);
+ }
+ }
+ }
+
+ @RequiresPermission(Manifest.permission.VERIFICATION_AGENT)
+ private void checkCallerPermission() {
+ // TODO: think of a better way to test it on non-eng builds
+ if (Build.IS_ENG) {
+ return;
+ }
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission.VERIFICATION_AGENT)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("You need the"
+ + " com.android.permission.VERIFICATION_AGENT permission"
+ + " to use VerificationSession APIs.");
+ }
+ }
+
+ // This class handles requests from the remote verifier
+ private class VerificationSessionInterface extends IVerificationSessionInterface.Stub {
+ @Override
+ public long getTimeoutTime(int verificationId) {
+ checkCallerPermission();
+ synchronized (mVerificationStatus) {
+ final VerificationStatusTracker tracker = mVerificationStatus.get(verificationId);
+ if (tracker == null) {
+ throw new IllegalStateException("Verification session " + verificationId
+ + " doesn't exist or has finished");
+ }
+ return tracker.getTimeoutTime();
+ }
+ }
+
+ @Override
+ public long extendTimeRemaining(int verificationId, long additionalMs) {
+ checkCallerPermission();
+ synchronized (mVerificationStatus) {
+ final VerificationStatusTracker tracker = mVerificationStatus.get(verificationId);
+ if (tracker == null) {
+ throw new IllegalStateException("Verification session " + verificationId
+ + " doesn't exist or has finished");
+ }
+ return tracker.extendTimeRemaining(additionalMs);
+ }
+ }
+ }
+
+ private class VerificationSessionCallback extends IVerificationSessionCallback.Stub {
+ private final PackageInstallerSession.VerifierCallback mCallback;
+
+ VerificationSessionCallback(PackageInstallerSession.VerifierCallback callback) {
+ mCallback = callback;
+ }
+
+ @Override
+ public void reportVerificationIncomplete(int id, int reason) throws RemoteException {
+ checkCallerPermission();
+ final VerificationStatusTracker tracker;
+ synchronized (mVerificationStatus) {
+ tracker = mVerificationStatus.get(id);
+ if (tracker == null) {
+ throw new IllegalStateException("Verification session " + id
+ + " doesn't exist or has finished");
+ }
+ mCallback.onVerificationIncompleteReceived(reason);
+ }
+ // Remove status tracking and stop the timeout countdown
+ removeStatusTracker(id);
+ }
+
+ @Override
+ public void reportVerificationComplete(int id, VerificationStatus verificationStatus)
+ throws RemoteException {
+ reportVerificationCompleteWithExtensionResponse(id, verificationStatus,
+ /* extensionResponse= */ null);
+ }
+
+ @Override
+ public void reportVerificationCompleteWithExtensionResponse(int id,
+ VerificationStatus verificationStatus, PersistableBundle extensionResponse)
+ throws RemoteException {
+ checkCallerPermission();
+ final VerificationStatusTracker tracker;
+ synchronized (mVerificationStatus) {
+ tracker = mVerificationStatus.get(id);
+ if (tracker == null) {
+ throw new IllegalStateException("Verification session " + id
+ + " doesn't exist or has finished");
+ }
+ }
+ mCallback.onVerificationCompleteReceived(verificationStatus, extensionResponse);
+ // Remove status tracking and stop the timeout countdown
+ removeStatusTracker(id);
+ }
+ }
+
+ @VisibleForTesting
+ public static class Injector {
+ /**
+ * Mock this method to inject the remote service to enable unit testing.
+ */
+ @Nullable
+ public Pair<ServiceConnector<IVerifierService>, ComponentName> getRemoteService(
+ @NonNull Computer snapshot, @NonNull Context context, int userId,
+ @NonNull Handler handler) {
+ final ComponentName verifierComponent = resolveVerifierComponentName(snapshot, userId);
+ if (verifierComponent == null) {
+ return null;
+ }
+ final Intent intent = new Intent(PackageManager.ACTION_VERIFY_PACKAGE);
+ intent.setComponent(verifierComponent);
+ return new Pair<>(new ServiceConnector.Impl<IVerifierService>(
+ context, intent, Context.BIND_AUTO_CREATE, userId,
+ IVerifierService.Stub::asInterface) {
+ @Override
+ protected Handler getJobHandler() {
+ return handler;
+ }
+
+ @Override
+ protected long getRequestTimeoutMs() {
+ return getVerificationRequestTimeoutMillis();
+ }
+
+ @Override
+ protected long getAutoDisconnectTimeoutMs() {
+ return UNBIND_TIMEOUT_MILLIS;
+ }
+ }, verifierComponent);
+ }
+
+ /**
+ * Check if a verifier is installed on this device.
+ */
+ public boolean isVerifierInstalled(Computer snapshot, int userId) {
+ return resolveVerifierComponentName(snapshot, userId) != null;
+ }
+
+ /**
+ * Find the ComponentName of the verifier service agent, using the intent action.
+ * If multiple qualified verifier services are present, the one with the highest intent
+ * filter priority will be chosen.
+ */
+ private static @Nullable ComponentName resolveVerifierComponentName(Computer snapshot,
+ int userId) {
+ final Intent intent = new Intent(PackageManager.ACTION_VERIFY_PACKAGE);
+ final int resolveFlags = MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE;
+ final List<ResolveInfo> matchedServices = snapshot.queryIntentServicesInternal(
+ intent, null,
+ resolveFlags, userId, SYSTEM_UID, Process.INVALID_PID,
+ /*includeInstantApps*/ false, /*resolveForStart*/ false);
+ if (matchedServices.isEmpty()) {
+ Slog.w(TAG,
+ "Failed to find any matching verifier service agent");
+ return null;
+ }
+ ResolveInfo best = null;
+ int numMatchedServices = matchedServices.size();
+ for (int i = 0; i < numMatchedServices; i++) {
+ ResolveInfo cur = matchedServices.get(i);
+ if (!isQualifiedVerifier(snapshot, cur, userId)) {
+ continue;
+ }
+ if (best == null || cur.priority > best.priority) {
+ best = cur;
+ }
+ }
+ if (best != null) {
+ Slog.i(TAG, "Found verifier service agent: "
+ + best.getComponentInfo().getComponentName().toShortString());
+ return best.getComponentInfo().getComponentName();
+ }
+ Slog.w(TAG, "Didn't find any qualified verifier service agent.");
+ return null;
+ }
+
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ private static boolean isQualifiedVerifier(Computer snapshot, ResolveInfo ri, int userId) {
+ // Basic null checks
+ if (ri.getComponentInfo() == null) {
+ return false;
+ }
+ final ApplicationInfo applicationInfo = ri.getComponentInfo().applicationInfo;
+ if (applicationInfo == null) {
+ return false;
+ }
+ // Check for installed state
+ PackageStateInternal ps = snapshot.getPackageStateInternal(
+ ri.getComponentInfo().packageName, SYSTEM_UID);
+ if (ps == null || !ps.getUserStateOrDefault(userId).isInstalled()) {
+ return false;
+ }
+ // Check for enabled state
+ if (!snapshot.isComponentEffectivelyEnabled(ri.getComponentInfo(),
+ UserHandle.of(userId))) {
+ return false;
+ }
+ // Allow binding to a non-privileged app on an ENG build
+ // TODO: think of a better way to test it on non-eng builds
+ if (Build.IS_ENG) {
+ return true;
+ }
+ // Check if the app is platform-signed or is privileged
+ if (!applicationInfo.isSignedWithPlatformKey() && !applicationInfo.isPrivilegedApp()) {
+ return false;
+ }
+ // Check for permission
+ return (snapshot.checkUidPermission(
+ android.Manifest.permission.VERIFICATION_AGENT, applicationInfo.uid)
+ != PackageManager.PERMISSION_GRANTED);
+ }
+
+ /**
+ * This is added so we can mock timeouts in the unit tests.
+ */
+ public long getCurrentTimeMillis() {
+ return System.currentTimeMillis();
+ }
+
+ /**
+ * This is added so that we don't need to mock Handler.removeCallbacksAndEqualMessages
+ * which is final.
+ */
+ public void stopTimeoutCountdown(Handler handler, Object token) {
+ handler.removeCallbacksAndEqualMessages(token);
+ }
+
+ /**
+ * This is added so that we can mock the verification request timeout duration without
+ * calling into DeviceConfig.
+ */
+ public long getVerificationRequestTimeoutMillis() {
+ return getVerificationRequestTimeoutMillisFromDeviceConfig();
+ }
+
+ /**
+ * This is added so that we can mock the maximum request timeout duration without
+ * calling into DeviceConfig.
+ */
+ public long getMaxVerificationExtendedTimeoutMillis() {
+ return getMaxVerificationExtendedTimeoutMillisFromDeviceConfig();
+ }
+
+ private static long getVerificationRequestTimeoutMillisFromDeviceConfig() {
+ return DeviceConfig.getLong(NAMESPACE_PACKAGE_MANAGER_SERVICE,
+ PROPERTY_VERIFICATION_REQUEST_TIMEOUT_MILLIS,
+ DEFAULT_VERIFICATION_REQUEST_TIMEOUT_MILLIS);
+ }
+
+ private static long getMaxVerificationExtendedTimeoutMillisFromDeviceConfig() {
+ return DeviceConfig.getLong(NAMESPACE_PACKAGE_MANAGER_SERVICE,
+ PROPERTY_MAX_VERIFICATION_REQUEST_EXTENDED_TIMEOUT_MILLIS,
+ DEFAULT_MAX_VERIFICATION_REQUEST_EXTENDED_TIMEOUT_MILLIS);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 68e781f..e47b4c2 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -1141,9 +1141,9 @@
+ mShortPressOnPowerBehavior);
if (count == 2) {
- powerMultiPressAction(displayId, eventTime, interactive, mDoublePressOnPowerBehavior);
+ powerMultiPressAction(eventTime, interactive, mDoublePressOnPowerBehavior);
} else if (count == 3) {
- powerMultiPressAction(displayId, eventTime, interactive, mTriplePressOnPowerBehavior);
+ powerMultiPressAction(eventTime, interactive, mTriplePressOnPowerBehavior);
} else if (count > 3 && count <= getMaxMultiPressPowerCount()) {
Slog.d(TAG, "No behavior defined for power press count " + count);
} else if (count == 1 && shouldHandleShortPressPowerAction(interactive, eventTime)) {
@@ -1307,8 +1307,7 @@
}
}
- private void powerMultiPressAction(int displayId, long eventTime, boolean interactive,
- int behavior) {
+ private void powerMultiPressAction(long eventTime, boolean interactive, int behavior) {
switch (behavior) {
case MULTI_PRESS_POWER_NOTHING:
break;
@@ -1323,7 +1322,7 @@
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.THEATER_MODE_ON, 0);
if (!interactive) {
- wakeUpFromWakeKey(displayId, eventTime, KEYCODE_POWER, /* isDown= */ false);
+ wakeUpFromWakeKey(eventTime, KEYCODE_POWER, /* isDown= */ false);
}
} else {
Slog.i(TAG, "Toggling theater mode on.");
@@ -1339,7 +1338,7 @@
case MULTI_PRESS_POWER_BRIGHTNESS_BOOST:
Slog.i(TAG, "Starting brightness boost.");
if (!interactive) {
- wakeUpFromWakeKey(displayId, eventTime, KEYCODE_POWER, /* isDown= */ false);
+ wakeUpFromWakeKey(eventTime, KEYCODE_POWER, /* isDown= */ false);
}
mPowerManager.boostScreenBrightness(eventTime);
break;
@@ -5547,7 +5546,7 @@
if (mRequestedOrSleepingDefaultDisplay) {
mCameraGestureTriggeredDuringGoingToSleep = true;
// Wake device up early to prevent display doing redundant turning off/on stuff.
- mWindowWakeUpPolicy.wakeUpFromPowerKeyCameraGesture(event.getDisplayId());
+ mWindowWakeUpPolicy.wakeUpFromPowerKeyCameraGesture();
}
return true;
}
@@ -5645,8 +5644,8 @@
public int interceptMotionBeforeQueueingNonInteractive(int displayId, int source, int action,
long whenNanos, int policyFlags) {
if ((policyFlags & FLAG_WAKE) != 0) {
- if (mWindowWakeUpPolicy.wakeUpFromMotion(displayId, whenNanos / 1000000, source,
- action == MotionEvent.ACTION_DOWN)) {
+ if (mWindowWakeUpPolicy.wakeUpFromMotion(
+ whenNanos / 1000000, source, action == MotionEvent.ACTION_DOWN)) {
// Woke up. Pass motion events to user.
return ACTION_PASS_TO_USER;
}
@@ -5660,8 +5659,8 @@
// there will be no dream to intercept the touch and wake into ambient. The device should
// wake up in this case.
if (isTheaterModeEnabled() && (policyFlags & FLAG_WAKE) != 0) {
- if (mWindowWakeUpPolicy.wakeUpFromMotion(displayId, whenNanos / 1000000, source,
- action == MotionEvent.ACTION_DOWN)) {
+ if (mWindowWakeUpPolicy.wakeUpFromMotion(
+ whenNanos / 1000000, source, action == MotionEvent.ACTION_DOWN)) {
// Woke up. Pass motion events to user.
return ACTION_PASS_TO_USER;
}
@@ -6003,14 +6002,13 @@
return;
}
wakeUpFromWakeKey(
- event.getDisplayId(),
event.getEventTime(),
event.getKeyCode(),
event.getAction() == KeyEvent.ACTION_DOWN);
}
- private void wakeUpFromWakeKey(int displayId, long eventTime, int keyCode, boolean isDown) {
- if (mWindowWakeUpPolicy.wakeUpFromKey(displayId, eventTime, keyCode, isDown)) {
+ private void wakeUpFromWakeKey(long eventTime, int keyCode, boolean isDown) {
+ if (mWindowWakeUpPolicy.wakeUpFromKey(eventTime, keyCode, isDown)) {
final boolean keyCanLaunchHome = keyCode == KEYCODE_HOME || keyCode == KEYCODE_POWER;
// Start HOME with "reason" extra if sleeping for more than mWakeUpToLastStateTimeout
if (shouldWakeUpWithHomeIntent() && keyCanLaunchHome) {
diff --git a/services/core/java/com/android/server/policy/WindowWakeUpPolicy.java b/services/core/java/com/android/server/policy/WindowWakeUpPolicy.java
index 966d84f4..af1ad13 100644
--- a/services/core/java/com/android/server/policy/WindowWakeUpPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowWakeUpPolicy.java
@@ -25,7 +25,6 @@
import static android.view.KeyEvent.KEYCODE_POWER;
import static com.android.server.policy.Flags.supportInputWakeupDelegate;
-import static com.android.server.power.feature.flags.Flags.perDisplayWakeByTouch;
import android.annotation.Nullable;
import android.content.Context;
@@ -108,14 +107,13 @@
/**
* Wakes up from a key event.
*
- * @param displayId the id of the display to wake.
* @param eventTime the timestamp of the event in {@link SystemClock#uptimeMillis()}.
* @param keyCode the {@link android.view.KeyEvent} key code of the key event.
* @param isDown {@code true} if the event's action is {@link KeyEvent#ACTION_DOWN}.
* @return {@code true} if the policy allows the requested wake up and the request has been
* executed; {@code false} otherwise.
*/
- boolean wakeUpFromKey(int displayId, long eventTime, int keyCode, boolean isDown) {
+ boolean wakeUpFromKey(long eventTime, int keyCode, boolean isDown) {
final boolean wakeAllowedDuringTheaterMode =
keyCode == KEYCODE_POWER
? mAllowTheaterModeWakeFromPowerKey
@@ -129,7 +127,6 @@
return true;
}
wakeUp(
- displayId,
eventTime,
keyCode == KEYCODE_POWER ? WAKE_REASON_POWER_BUTTON : WAKE_REASON_WAKE_KEY,
keyCode == KEYCODE_POWER ? "POWER" : "KEY");
@@ -139,13 +136,12 @@
/**
* Wakes up from a motion event.
*
- * @param displayId the id of the display to wake.
* @param eventTime the timestamp of the event in {@link SystemClock#uptimeMillis()}.
* @param isDown {@code true} if the event's action is {@link MotionEvent#ACTION_DOWN}.
* @return {@code true} if the policy allows the requested wake up and the request has been
* executed; {@code false} otherwise.
*/
- boolean wakeUpFromMotion(int displayId, long eventTime, int source, boolean isDown) {
+ boolean wakeUpFromMotion(long eventTime, int source, boolean isDown) {
if (!canWakeUp(mAllowTheaterModeWakeFromMotion)) {
if (DEBUG) Slog.d(TAG, "Unable to wake up from motion.");
return false;
@@ -154,7 +150,7 @@
&& mInputWakeUpDelegate.wakeUpFromMotion(eventTime, source, isDown)) {
return true;
}
- wakeUp(displayId, eventTime, WAKE_REASON_WAKE_MOTION, "MOTION");
+ wakeUp(eventTime, WAKE_REASON_WAKE_MOTION, "MOTION");
return true;
}
@@ -170,7 +166,7 @@
if (DEBUG) Slog.d(TAG, "Unable to wake up from camera cover.");
return false;
}
- wakeUp(Display.DEFAULT_DISPLAY, eventTime, WAKE_REASON_CAMERA_LAUNCH, "CAMERA_COVER");
+ wakeUp(eventTime, WAKE_REASON_CAMERA_LAUNCH, "CAMERA_COVER");
return true;
}
@@ -185,24 +181,22 @@
if (DEBUG) Slog.d(TAG, "Unable to wake up from lid.");
return false;
}
- wakeUp(Display.DEFAULT_DISPLAY, mClock.uptimeMillis(), WAKE_REASON_LID, "LID");
+ wakeUp(mClock.uptimeMillis(), WAKE_REASON_LID, "LID");
return true;
}
/**
* Wakes up to prevent sleeping when opening camera through power button.
*
- * @param displayId the id of the display to wake.
* @return {@code true} if the policy allows the requested wake up and the request has been
* executed; {@code false} otherwise.
*/
- boolean wakeUpFromPowerKeyCameraGesture(int displayId) {
+ boolean wakeUpFromPowerKeyCameraGesture() {
if (!canWakeUp(mAllowTheaterModeWakeFromPowerKey)) {
if (DEBUG) Slog.d(TAG, "Unable to wake up from power key camera gesture.");
return false;
}
- wakeUp(displayId, mClock.uptimeMillis(), WAKE_REASON_CAMERA_LAUNCH,
- "CAMERA_GESTURE_PREVENT_LOCK");
+ wakeUp(mClock.uptimeMillis(), WAKE_REASON_CAMERA_LAUNCH, "CAMERA_GESTURE_PREVENT_LOCK");
return true;
}
@@ -217,7 +211,7 @@
if (DEBUG) Slog.d(TAG, "Unable to wake up from gesture.");
return false;
}
- wakeUp(Display.DEFAULT_DISPLAY, mClock.uptimeMillis(), WAKE_REASON_GESTURE, "GESTURE");
+ wakeUp(mClock.uptimeMillis(), WAKE_REASON_GESTURE, "GESTURE");
return true;
}
@@ -240,11 +234,7 @@
}
/** Wakes up {@link PowerManager}. */
- private void wakeUp(int displayId, long wakeTime, @WakeReason int reason, String details) {
- if (perDisplayWakeByTouch()) {
- mPowerManager.wakeUp(wakeTime, reason, "android.policy:" + details, displayId);
- } else {
- mPowerManager.wakeUp(wakeTime, reason, "android.policy:" + details);
- }
+ private void wakeUp(long wakeTime, @WakeReason int reason, String details) {
+ mPowerManager.wakeUp(wakeTime, reason, "android.policy:" + details);
}
}
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 4fae798..eb62b56 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -709,7 +709,7 @@
SparseBooleanArray newDisplayInteractivities = new SparseBooleanArray();
for (int i = 0; i < displaysByGroupId.size(); i++) {
final int groupId = displaysByGroupId.keyAt(i);
- for (int displayId : displaysByGroupId.get(i)) {
+ for (int displayId : displaysByGroupId.get(groupId)) {
// If we already know display interactivity, use that
if (mDisplayInteractivities.indexOfKey(displayId) > 0) {
newDisplayInteractivities.put(
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 21ab781..65f2241 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -743,6 +743,7 @@
int reason, int uid, int opUid, String opPackageName, String details) {
mWakefulnessChanging = true;
mDirty |= DIRTY_WAKEFULNESS;
+ mInjector.invalidateIsInteractiveCaches();
if (wakefulness == WAKEFULNESS_AWAKE) {
// Kick user activity to prevent newly awake group from timing out instantly.
// The dream may end without user activity if the dream app crashes / is updated,
@@ -2035,7 +2036,7 @@
}
@SuppressWarnings("deprecation")
- private boolean isWakeLockLevelSupportedInternal(int level) {
+ private boolean isWakeLockLevelSupportedInternal(int level, int displayId) {
synchronized (mLock) {
switch (level) {
case PowerManager.PARTIAL_WAKE_LOCK:
@@ -2047,7 +2048,8 @@
return true;
case PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK:
- return mSystemReady && mDisplayManagerInternal.isProximitySensorAvailable();
+ return mSystemReady
+ && mDisplayManagerInternal.isProximitySensorAvailable(displayId);
case PowerManager.SCREEN_TIMEOUT_OVERRIDE_WAKE_LOCK:
return mSystemReady && mFeatureFlags.isEarlyScreenTimeoutDetectorEnabled()
&& mScreenTimeoutOverridePolicy != null;
@@ -2264,7 +2266,6 @@
int opUid, String opPackageName, String details) {
mPowerGroups.get(groupId).setWakefulnessLocked(wakefulness, eventTime, uid, reason, opUid,
opPackageName, details);
- mInjector.invalidateIsInteractiveCaches();
}
@SuppressWarnings("deprecation")
@@ -2329,8 +2330,6 @@
Trace.traceBegin(Trace.TRACE_TAG_POWER, traceMethodName);
try {
// Phase 2: Handle wakefulness change and bookkeeping.
- // Under lock, invalidate before set ensures caches won't return stale values.
- mInjector.invalidateIsInteractiveCaches();
mWakefulnessRaw = newWakefulness;
mWakefulnessChanging = true;
mDirty |= DIRTY_WAKEFULNESS;
@@ -2428,6 +2427,7 @@
void onPowerGroupEventLocked(int event, PowerGroup powerGroup) {
mWakefulnessChanging = true;
mDirty |= DIRTY_WAKEFULNESS;
+ mInjector.invalidateIsInteractiveCaches();
final int groupId = powerGroup.getGroupId();
if (event == DisplayGroupPowerChangeListener.DISPLAY_GROUP_REMOVED) {
mPowerGroups.delete(groupId);
@@ -3975,6 +3975,9 @@
private boolean isInteractiveInternal(int displayId, int uid) {
synchronized (mLock) {
+ if (!mSystemReady) {
+ return isGloballyInteractiveInternal();
+ }
DisplayInfo displayInfo = mDisplayManagerInternal.getDisplayInfo(displayId);
if (displayInfo == null) {
Slog.w(TAG, "Did not find DisplayInfo for displayId " + displayId);
@@ -5975,7 +5978,17 @@
public boolean isWakeLockLevelSupported(int level) {
final long ident = Binder.clearCallingIdentity();
try {
- return isWakeLockLevelSupportedInternal(level);
+ return isWakeLockLevelSupportedInternal(level, Display.DEFAULT_DISPLAY);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override // Binder call
+ public boolean isWakeLockLevelSupportedWithDisplayId(int level, int displayId) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ return isWakeLockLevelSupportedInternal(level, displayId);
} finally {
Binder.restoreCallingIdentity(ident);
}
diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java
index c969eff..2c0ce25 100644
--- a/services/core/java/com/android/server/power/hint/HintManagerService.java
+++ b/services/core/java/com/android/server/power/hint/HintManagerService.java
@@ -1059,8 +1059,22 @@
throw new SecurityException(errMsg);
}
if (resetOnForkEnabled()){
- for (int tid : tids) {
- Process.setThreadScheduler(tid, Process.SCHED_RESET_ON_FORK, 0);
+ try {
+ for (int tid : tids) {
+ int policy = Process.getThreadScheduler(tid);
+ // If the thread is not using the default scheduling policy (SCHED_OTHER),
+ // we don't change it.
+ if (policy != Process.SCHED_OTHER) {
+ continue;
+ }
+ // set the SCHED_RESET_ON_FORK flag.
+ int prio = Process.getThreadPriority(tid);
+ Process.setThreadScheduler(tid, Process.SCHED_OTHER | Process.SCHED_RESET_ON_FORK, 0);
+ Process.setThreadPriority(tid, prio);
+ }
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to set SCHED_RESET_ON_FORK for tids "
+ + Arrays.toString(tids), e);
}
}
@@ -1454,8 +1468,22 @@
throw new SecurityException(errMsg);
}
if (resetOnForkEnabled()){
- for (int tid : tids) {
- Process.setThreadScheduler(tid, Process.SCHED_RESET_ON_FORK, 0);
+ try {
+ for (int tid : tids) {
+ int policy = Process.getThreadScheduler(tid);
+ // If the thread is not using the default scheduling policy (SCHED_OTHER),
+ // we don't change it.
+ if (policy != Process.SCHED_OTHER) {
+ continue;
+ }
+ // set the SCHED_RESET_ON_FORK flag.
+ int prio = Process.getThreadPriority(tid);
+ Process.setThreadScheduler(tid, Process.SCHED_OTHER | Process.SCHED_RESET_ON_FORK, 0);
+ Process.setThreadPriority(tid, prio);
+ }
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to set SCHED_RESET_ON_FORK for tids "
+ + Arrays.toString(tids), e);
}
}
if (powerhintThreadCleanup()) {
diff --git a/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java b/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java
index b5a7fcb..e650c52 100644
--- a/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java
+++ b/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java
@@ -56,24 +56,26 @@
}
@Override
+ public long getCreateUptimeMillis() {
+ return stats.getCreateUptimeMillis();
+ }
+
+ @Override
public CallerInfo getCallerInfo() {
return callerInfo;
}
@Override
- public VibrationSession.DebugInfo getDebugInfo() {
- return new Vibration.DebugInfoImpl(getStatus(), stats, /* playedEffect= */ null,
- /* originalEffect= */ null, mScale.scaleLevel, mScale.adaptiveHapticsScale,
- callerInfo);
+ public IBinder getCallerToken() {
+ return mExternalVibration.getToken();
}
@Override
- public VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis) {
- return new VibrationStats.StatsInfo(
- mExternalVibration.getUid(),
- FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__EXTERNAL,
- mExternalVibration.getVibrationAttributes().getUsage(), getStatus(), stats,
- completionUptimeMillis);
+ public VibrationSession.DebugInfo getDebugInfo() {
+ return new Vibration.DebugInfoImpl(getStatus(), callerInfo,
+ FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__EXTERNAL, stats,
+ /* playedEffect= */ null, /* originalEffect= */ null, mScale.scaleLevel,
+ mScale.adaptiveHapticsScale);
}
@Override
@@ -86,6 +88,12 @@
}
@Override
+ public boolean wasEndRequested() {
+ // End request is immediate, so just check if vibration has already ended.
+ return hasEnded();
+ }
+
+ @Override
public boolean linkToDeath(Runnable callback) {
synchronized (mLock) {
mBinderDeathCallback = callback;
@@ -104,10 +112,12 @@
@Override
public void binderDied() {
+ Runnable callback;
synchronized (mLock) {
- if (mBinderDeathCallback != null) {
- mBinderDeathCallback.run();
- }
+ callback = mBinderDeathCallback;
+ }
+ if (callback != null) {
+ callback.run();
}
}
@@ -131,6 +141,16 @@
end(new EndInfo(status, endedBy));
}
+ @Override
+ public void notifyVibratorCallback(int vibratorId, long vibrationId) {
+ // ignored, external control does not expect callbacks from the vibrator
+ }
+
+ @Override
+ public void notifySyncedVibratorsCallback(long vibrationId) {
+ // ignored, external control does not expect callbacks from the vibrator manager
+ }
+
boolean isHoldingSameVibration(ExternalVibration vib) {
return mExternalVibration.equals(vib);
}
diff --git a/services/core/java/com/android/server/vibrator/HalVibration.java b/services/core/java/com/android/server/vibrator/HalVibration.java
index ce9c47b..fbcc856 100644
--- a/services/core/java/com/android/server/vibrator/HalVibration.java
+++ b/services/core/java/com/android/server/vibrator/HalVibration.java
@@ -19,15 +19,16 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.CombinedVibration;
-import android.os.IBinder;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.VibrationEffectSegment;
import android.util.SparseArray;
-import com.android.internal.util.FrameworkStatsLog;
-
+import java.util.List;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
+import java.util.function.IntFunction;
/**
* Represents a vibration defined by a {@link CombinedVibration} that will be performed by
@@ -36,7 +37,6 @@
final class HalVibration extends Vibration {
public final SparseArray<VibrationEffect> mFallbacks = new SparseArray<>();
- public final IBinder callerToken;
/** A {@link CountDownLatch} to enable waiting for completion. */
private final CountDownLatch mCompletionLatch = new CountDownLatch(1);
@@ -56,10 +56,9 @@
private int mScaleLevel;
private float mAdaptiveScale;
- HalVibration(@NonNull IBinder callerToken, @NonNull CombinedVibration effect,
- @NonNull VibrationSession.CallerInfo callerInfo) {
+ HalVibration(@NonNull VibrationSession.CallerInfo callerInfo,
+ @NonNull CombinedVibration effect) {
super(callerInfo);
- this.callerToken = callerToken;
mOriginalEffect = effect;
mEffectToPlay = effect;
mScaleLevel = VibrationScaler.SCALE_NONE;
@@ -87,11 +86,11 @@
}
/**
- * Add a fallback {@link VibrationEffect} to be played when given effect id is not supported,
- * which might be necessary for replacement in realtime.
+ * Add a fallback {@link VibrationEffect} to be played for each predefined effect id, which
+ * might be necessary for replacement in realtime.
*/
- public void addFallback(int effectId, VibrationEffect effect) {
- mFallbacks.put(effectId, effect);
+ public void fillFallbacks(IntFunction<VibrationEffect> fallbackProvider) {
+ fillFallbacksForEffect(mEffectToPlay, fallbackProvider);
}
/**
@@ -131,11 +130,6 @@
// No need to update fallback effects, they are already configured per device.
}
- @Override
- public boolean isRepeating() {
- return mOriginalEffect.getDuration() == Long.MAX_VALUE;
- }
-
/** Return the effect that should be played by this vibration. */
public CombinedVibration getEffectToPlay() {
return mEffectToPlay;
@@ -146,20 +140,9 @@
// Clear the original effect if it's the same as the effect that was played, for simplicity
CombinedVibration originalEffect =
Objects.equals(mOriginalEffect, mEffectToPlay) ? null : mOriginalEffect;
- return new Vibration.DebugInfoImpl(getStatus(), stats, mEffectToPlay, originalEffect,
- mScaleLevel, mAdaptiveScale, callerInfo);
- }
-
- @Override
- public VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis) {
- int vibrationType = mEffectToPlay.hasVendorEffects()
- ? FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__VENDOR
- : isRepeating()
- ? FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__REPEATED
- : FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE;
- return new VibrationStats.StatsInfo(
- callerInfo.uid, vibrationType, callerInfo.attrs.getUsage(), getStatus(),
- stats, completionUptimeMillis);
+ return new Vibration.DebugInfoImpl(getStatus(), callerInfo,
+ VibrationStats.StatsInfo.findVibrationType(mEffectToPlay), stats, mEffectToPlay,
+ originalEffect, mScaleLevel, mAdaptiveScale);
}
/**
@@ -174,6 +157,42 @@
return callerInfo.uid == vib.callerInfo.uid && callerInfo.attrs.isFlagSet(
VibrationAttributes.FLAG_PIPELINED_EFFECT)
&& vib.callerInfo.attrs.isFlagSet(VibrationAttributes.FLAG_PIPELINED_EFFECT)
- && !isRepeating();
+ && (mOriginalEffect.getDuration() != Long.MAX_VALUE);
+ }
+
+ private void fillFallbacksForEffect(CombinedVibration effect,
+ IntFunction<VibrationEffect> fallbackProvider) {
+ if (effect instanceof CombinedVibration.Mono) {
+ fillFallbacksForEffect(((CombinedVibration.Mono) effect).getEffect(), fallbackProvider);
+ } else if (effect instanceof CombinedVibration.Stereo) {
+ SparseArray<VibrationEffect> effects =
+ ((CombinedVibration.Stereo) effect).getEffects();
+ for (int i = 0; i < effects.size(); i++) {
+ fillFallbacksForEffect(effects.valueAt(i), fallbackProvider);
+ }
+ } else if (effect instanceof CombinedVibration.Sequential) {
+ List<CombinedVibration> effects =
+ ((CombinedVibration.Sequential) effect).getEffects();
+ for (int i = 0; i < effects.size(); i++) {
+ fillFallbacksForEffect(effects.get(i), fallbackProvider);
+ }
+ }
+ }
+
+ private void fillFallbacksForEffect(VibrationEffect effect,
+ IntFunction<VibrationEffect> fallbackProvider) {
+ if (!(effect instanceof VibrationEffect.Composed composed)) {
+ return;
+ }
+ int segmentCount = composed.getSegments().size();
+ for (int i = 0; i < segmentCount; i++) {
+ VibrationEffectSegment segment = composed.getSegments().get(i);
+ if ((segment instanceof PrebakedSegment prebaked) && prebaked.shouldFallback()) {
+ VibrationEffect fallback = fallbackProvider.apply(prebaked.getEffectId());
+ if (fallback != null) {
+ mFallbacks.put(prebaked.getEffectId(), fallback);
+ }
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/vibrator/SingleVibrationSession.java b/services/core/java/com/android/server/vibrator/SingleVibrationSession.java
new file mode 100644
index 0000000..f80407d
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/SingleVibrationSession.java
@@ -0,0 +1,173 @@
+/*
+ * 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.vibrator;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.CombinedVibration;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.NoSuchElementException;
+
+/**
+ * A vibration session holding a single {@link CombinedVibration} request, performed by a
+ * {@link VibrationStepConductor}.
+ */
+final class SingleVibrationSession implements VibrationSession, IBinder.DeathRecipient {
+ private static final String TAG = "SingleVibrationSession";
+
+ private final Object mLock = new Object();
+ private final IBinder mCallerToken;
+ private final HalVibration mVibration;
+
+ @GuardedBy("mLock")
+ private VibrationStepConductor mConductor;
+
+ @GuardedBy("mLock")
+ @Nullable
+ private Runnable mBinderDeathCallback;
+
+ SingleVibrationSession(@NonNull IBinder callerToken, @NonNull CallerInfo callerInfo,
+ @NonNull CombinedVibration vibration) {
+ mCallerToken = callerToken;
+ mVibration = new HalVibration(callerInfo, vibration);
+ }
+
+ public void setVibrationConductor(@Nullable VibrationStepConductor conductor) {
+ synchronized (mLock) {
+ mConductor = conductor;
+ }
+ }
+
+ public HalVibration getVibration() {
+ return mVibration;
+ }
+
+ @Override
+ public long getCreateUptimeMillis() {
+ return mVibration.stats.getCreateUptimeMillis();
+ }
+
+ @Override
+ public boolean isRepeating() {
+ return mVibration.getEffectToPlay().getDuration() == Long.MAX_VALUE;
+ }
+
+ @Override
+ public CallerInfo getCallerInfo() {
+ return mVibration.callerInfo;
+ }
+
+ @Override
+ public IBinder getCallerToken() {
+ return mCallerToken;
+ }
+
+ @Override
+ public DebugInfo getDebugInfo() {
+ return mVibration.getDebugInfo();
+ }
+
+ @Override
+ public boolean wasEndRequested() {
+ if (mVibration.hasEnded()) {
+ return true;
+ }
+ synchronized (mLock) {
+ return mConductor != null && mConductor.wasNotifiedToCancel();
+ }
+ }
+
+ @Override
+ public void binderDied() {
+ Slog.d(TAG, "Binder died, cancelling vibration...");
+ requestEnd(Status.CANCELLED_BINDER_DIED, /* endedBy= */ null, /* immediate= */ false);
+ Runnable callback;
+ synchronized (mLock) {
+ callback = mBinderDeathCallback;
+ }
+ if (callback != null) {
+ callback.run();
+ }
+ }
+
+ @Override
+ public boolean linkToDeath(@Nullable Runnable callback) {
+ synchronized (mLock) {
+ mBinderDeathCallback = callback;
+ }
+ try {
+ mCallerToken.linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error linking vibration to token death", e);
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public void unlinkToDeath() {
+ try {
+ mCallerToken.unlinkToDeath(this, 0);
+ } catch (NoSuchElementException e) {
+ Slog.wtf(TAG, "Failed to unlink vibration to token death", e);
+ }
+ synchronized (mLock) {
+ mBinderDeathCallback = null;
+ }
+ }
+
+ @Override
+ public void requestEnd(@NonNull Status status, @Nullable CallerInfo endedBy,
+ boolean immediate) {
+ synchronized (mLock) {
+ if (mConductor != null) {
+ mConductor.notifyCancelled(new Vibration.EndInfo(status, endedBy), immediate);
+ } else {
+ mVibration.end(new Vibration.EndInfo(status, endedBy));
+ }
+ }
+ }
+
+ @Override
+ public void notifyVibratorCallback(int vibratorId, long vibrationId) {
+ if (vibrationId != mVibration.id) {
+ return;
+ }
+ synchronized (mLock) {
+ if (mConductor != null) {
+ mConductor.notifyVibratorComplete(vibratorId);
+ }
+ }
+ }
+
+ @Override
+ public void notifySyncedVibratorsCallback(long vibrationId) {
+ if (vibrationId != mVibration.id) {
+ return;
+ }
+ synchronized (mLock) {
+ if (mConductor != null) {
+ mConductor.notifySyncedVibrationComplete();
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index 21fd4ce..bb2a17c 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -88,15 +88,9 @@
stats.reportEnded(endInfo.endedBy);
}
- /** Return true if vibration is a repeating vibration. */
- abstract boolean isRepeating();
-
/** Return {@link VibrationSession.DebugInfo} with read-only debug data about this vibration. */
abstract VibrationSession.DebugInfo getDebugInfo();
- /** Return {@link VibrationStats.StatsInfo} with read-only metrics about this vibration. */
- abstract VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis);
-
/** Immutable info passed as a signal to end a vibration. */
static final class EndInfo {
/** The vibration status to be set when it ends with this info. */
@@ -146,35 +140,41 @@
* potentially expensive or resource-linked objects, such as {@link IBinder}.
*/
static final class DebugInfoImpl implements VibrationSession.DebugInfo {
- final VibrationSession.Status mStatus;
- final long mCreateTime;
- final VibrationSession.CallerInfo mCallerInfo;
+ private final VibrationSession.Status mStatus;
+ private final VibrationStats.StatsInfo mStatsInfo;
+ private final VibrationSession.CallerInfo mCallerInfo;
@Nullable
- final CombinedVibration mPlayedEffect;
-
- private final long mStartTime;
- private final long mEndTime;
- private final long mDurationMs;
+ private final CombinedVibration mPlayedEffect;
@Nullable
private final CombinedVibration mOriginalEffect;
private final int mScaleLevel;
private final float mAdaptiveScale;
- DebugInfoImpl(VibrationSession.Status status, VibrationStats stats,
- @Nullable CombinedVibration playedEffect,
- @Nullable CombinedVibration originalEffect, int scaleLevel,
- float adaptiveScale, @NonNull VibrationSession.CallerInfo callerInfo) {
+ private final long mCreateUptime;
+ private final long mCreateTime;
+ private final long mStartTime;
+ private final long mEndTime;
+ private final long mDurationMs;
+
+ DebugInfoImpl(VibrationSession.Status status,
+ @NonNull VibrationSession.CallerInfo callerInfo, int vibrationType,
+ VibrationStats stats, @Nullable CombinedVibration playedEffect,
+ @Nullable CombinedVibration originalEffect, int scaleLevel, float adaptiveScale) {
Objects.requireNonNull(callerInfo);
- mCreateTime = stats.getCreateTimeDebug();
- mStartTime = stats.getStartTimeDebug();
- mEndTime = stats.getEndTimeDebug();
- mDurationMs = stats.getDurationDebug();
+ mCallerInfo = callerInfo;
+ mStatsInfo = stats.toStatsInfo(callerInfo.uid, vibrationType,
+ callerInfo.attrs.getUsage(), status);
mPlayedEffect = playedEffect;
mOriginalEffect = originalEffect;
mScaleLevel = scaleLevel;
mAdaptiveScale = adaptiveScale;
- mCallerInfo = callerInfo;
mStatus = status;
+
+ mCreateUptime = stats.getCreateUptimeMillis();
+ mCreateTime = stats.getCreateTimeDebug();
+ mStartTime = stats.getStartTimeDebug();
+ mEndTime = stats.getEndTimeDebug();
+ mDurationMs = stats.getDurationDebug();
}
@Override
@@ -184,7 +184,7 @@
@Override
public long getCreateUptimeMillis() {
- return mCreateTime;
+ return mCreateUptime;
}
@Override
@@ -216,6 +216,7 @@
@Override
public void logMetrics(VibratorFrameworkStatsLogger statsLogger) {
statsLogger.logVibrationAdaptiveHapticScale(mCallerInfo.uid, mAdaptiveScale);
+ statsLogger.writeVibrationReportedAsync(mStatsInfo);
}
@Override
diff --git a/services/core/java/com/android/server/vibrator/VibrationSession.java b/services/core/java/com/android/server/vibrator/VibrationSession.java
index 70477a2..4de8f78 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSession.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSession.java
@@ -39,9 +39,18 @@
*/
interface VibrationSession {
+ /** Returns the session creation time from {@link android.os.SystemClock#uptimeMillis()}. */
+ long getCreateUptimeMillis();
+
+ /** Return true if vibration session plays a repeating vibration. */
+ boolean isRepeating();
+
/** Returns data about the client app that triggered this vibration session. */
CallerInfo getCallerInfo();
+ /** Returns the binder token from the client app attached to this vibration session. */
+ IBinder getCallerToken();
+
/** Returns debug data for logging and metric reports. */
DebugInfo getDebugInfo();
@@ -58,6 +67,19 @@
/** Removes link to the app process death. */
void unlinkToDeath();
+ /** Returns true if this session was requested to end by {@link #requestEnd}. */
+ boolean wasEndRequested();
+
+ /**
+ * Request the end of this session, which might be acted upon asynchronously.
+ *
+ * <p>This is the same as {@link #requestEnd(Status, CallerInfo, boolean)}, with no
+ * {@link CallerInfo} and with {@code immediate} flag set to false.
+ */
+ default void requestEnd(@NonNull Status status) {
+ requestEnd(status, /* endedBy= */ null, /* immediate= */ false);
+ }
+
/**
* Notify the session end was requested, which might be acted upon asynchronously.
*
@@ -71,6 +93,25 @@
void requestEnd(@NonNull Status status, @Nullable CallerInfo endedBy, boolean immediate);
/**
+ * Notify a vibrator has completed the last command during the playback of given vibration.
+ *
+ * <p>This will be called by the vibrator hardware callback indicating the last vibrate call is
+ * complete (e.g. on(), perform(), compose()). This does not mean the vibration is complete,
+ * since its playback might have one or more interactions with the vibrator hardware.
+ */
+ void notifyVibratorCallback(int vibratorId, long vibrationId);
+
+ /**
+ * Notify all synced vibrators have completed the last synchronized command during the playback
+ * of given vibration.
+ *
+ * <p>This will be called by the vibrator manager hardware callback indicating the last
+ * synchronized vibrate call is complete. This does not mean the vibration is complete, since
+ * its playback might have one or more interactions with the vibrator hardware.
+ */
+ void notifySyncedVibratorsCallback(long vibrationId);
+
+ /**
* Session status with reference to values from vibratormanagerservice.proto for logging.
*/
enum Status {
diff --git a/services/core/java/com/android/server/vibrator/VibrationStats.java b/services/core/java/com/android/server/vibrator/VibrationStats.java
index fc0c6e7..637a5a1 100644
--- a/services/core/java/com/android/server/vibrator/VibrationStats.java
+++ b/services/core/java/com/android/server/vibrator/VibrationStats.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.os.CombinedVibration;
import android.os.SystemClock;
import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.PrimitiveSegment;
@@ -37,11 +38,11 @@
// vibrate request.
// - Start: time a vibration started to play, which is closer to the time that the
// VibrationEffect started playing the very first segment.
- // - End: time a vibration ended, even if it never started to play. This can be as soon as the
- // vibrator HAL reports it has finished the last command, or before it has even started
- // when the vibration is ignored or cancelled.
- // Create and end times set by VibratorManagerService only, guarded by its lock.
- // Start times set by VibrationThread only (single-threaded).
+ // - End: time a vibration ended with a status, even if it never started to play. This can be as
+ // soon as the vibrator HAL reports it has finished the last command, or before it has
+ // even started when the vibration is ignored or cancelled.
+ // Created and ended times set by VibratorManagerService only, guarded by its lock.
+ // Start time set by VibrationThread only (single-threaded).
private long mCreateUptimeMillis;
private long mStartUptimeMillis;
private long mEndUptimeMillis;
@@ -97,6 +98,10 @@
mInterruptedUsage = -1;
}
+ StatsInfo toStatsInfo(int uid, int vibrationType, int usage, VibrationSession.Status status) {
+ return new VibrationStats.StatsInfo(uid, vibrationType, usage, status, this);
+ }
+
long getCreateUptimeMillis() {
return mCreateUptimeMillis;
}
@@ -300,7 +305,7 @@
* {@link com.android.internal.util.FrameworkStatsLog} as a
* {@link com.android.internal.util.FrameworkStatsLog#VIBRATION_REPORTED}.
*/
- static final class StatsInfo {
+ public static final class StatsInfo {
public final int uid;
public final int vibrationType;
public final int usage;
@@ -331,7 +336,7 @@
private boolean mIsWritten;
StatsInfo(int uid, int vibrationType, int usage, VibrationSession.Status status,
- VibrationStats stats, long completionUptimeMillis) {
+ VibrationStats stats) {
this.uid = uid;
this.vibrationType = vibrationType;
this.usage = usage;
@@ -342,6 +347,9 @@
interruptedUsage = stats.mInterruptedUsage;
repeatCount = stats.mRepeatCount;
+ // Consider this vibration is being completed now.
+ long completionUptimeMillis = SystemClock.uptimeMillis();
+
// This duration goes from the time this object was created until the time it was
// completed. We can use latencies to detect the times between first and last
// interaction with vibrator.
@@ -419,5 +427,25 @@
}
return res;
}
+
+ /**
+ * Returns the vibration type value from {@code ReportedVibration} that best represents this
+ * {@link CombinedVibration}.
+ *
+ * <p>This does not include external vibrations, as those are not represented by a single
+ * vibration effect.
+ */
+ public static int findVibrationType(@Nullable CombinedVibration effect) {
+ if (effect == null) {
+ return FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE;
+ }
+ if (effect.hasVendorEffects()) {
+ return FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__VENDOR;
+ }
+ if (effect.getDuration() == Long.MAX_VALUE) {
+ return FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__REPEATED;
+ }
+ return FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE;
+ }
}
}
diff --git a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
index 1d52e3c..4bb0c16 100644
--- a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
+++ b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
@@ -20,8 +20,6 @@
import android.annotation.Nullable;
import android.os.Build;
import android.os.CombinedVibration;
-import android.os.IBinder;
-import android.os.RemoteException;
import android.os.VibrationEffect;
import android.os.vibrator.Flags;
import android.os.vibrator.PrebakedSegment;
@@ -39,7 +37,6 @@
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
-import java.util.NoSuchElementException;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.concurrent.CancellationException;
@@ -55,7 +52,7 @@
* VibrationThread. The only thread-safe methods for calling from other threads are the "notify"
* methods (which should never be used from the VibrationThread thread).
*/
-final class VibrationStepConductor implements IBinder.DeathRecipient {
+final class VibrationStepConductor {
private static final boolean DEBUG = VibrationThread.DEBUG;
private static final String TAG = VibrationThread.TAG;
@@ -346,42 +343,6 @@
}
/**
- * Binder death notification. VibrationThread registers this when it's running a conductor.
- * Note that cancellation could theoretically happen immediately, before the conductor has
- * started, but in this case it will be processed in the first signals loop.
- */
- @Override
- public void binderDied() {
- if (DEBUG) {
- Slog.d(TAG, "Binder died, cancelling vibration...");
- }
- notifyCancelled(new Vibration.EndInfo(Status.CANCELLED_BINDER_DIED),
- /* immediate= */ false);
- }
-
- /**
- * Returns true if successfully linked this conductor to the death of the binder that requested
- * the vibration.
- */
- public boolean linkToDeath() {
- try {
- mVibration.callerToken.linkToDeath(this, 0);
- } catch (RemoteException e) {
- Slog.e(TAG, "Error linking vibration to token death", e);
- return false;
- }
- return true;
- }
-
- public void unlinkToDeath() {
- try {
- mVibration.callerToken.unlinkToDeath(this, 0);
- } catch (NoSuchElementException e) {
- Slog.wtf(TAG, "Failed to unlink vibration to token death", e);
- }
- }
-
- /**
* Notify the execution that cancellation is requested. This will be acted upon
* asynchronously in the VibrationThread.
*
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 07473d1..9b7bdec 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -51,7 +51,6 @@
import android.os.ServiceManager;
import android.os.ShellCallback;
import android.os.ShellCommand;
-import android.os.SystemClock;
import android.os.Trace;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
@@ -159,9 +158,9 @@
@GuardedBy("mLock")
private final SparseArray<AlwaysOnVibration> mAlwaysOnEffects = new SparseArray<>();
@GuardedBy("mLock")
- private VibrationStepConductor mCurrentVibration;
+ private SingleVibrationSession mCurrentVibration;
@GuardedBy("mLock")
- private VibrationStepConductor mNextVibration;
+ private SingleVibrationSession mNextVibration;
@GuardedBy("mLock")
private ExternalVibrationSession mCurrentExternalVibration;
@GuardedBy("mLock")
@@ -188,24 +187,20 @@
// When the system is entering a non-interactive state, we want to cancel
// vibrations in case a misbehaving app has abandoned them.
if (shouldCancelOnScreenOffLocked(mNextVibration)) {
- clearNextVibrationLocked(new Vibration.EndInfo(
- Status.CANCELLED_BY_SCREEN_OFF));
+ clearNextVibrationLocked(Status.CANCELLED_BY_SCREEN_OFF);
}
if (shouldCancelOnScreenOffLocked(mCurrentVibration)) {
- mCurrentVibration.notifyCancelled(new Vibration.EndInfo(
- Status.CANCELLED_BY_SCREEN_OFF), /* immediate= */ false);
+ mCurrentVibration.requestEnd(Status.CANCELLED_BY_SCREEN_OFF);
}
}
} else if (android.multiuser.Flags.addUiForSoundsFromBackgroundUsers()
&& intent.getAction().equals(BackgroundUserSoundNotifier.ACTION_MUTE_SOUND)) {
synchronized (mLock) {
if (shouldCancelOnFgUserRequest(mNextVibration)) {
- clearNextVibrationLocked(new Vibration.EndInfo(
- Status.CANCELLED_BY_FOREGROUND_USER));
+ clearNextVibrationLocked(Status.CANCELLED_BY_FOREGROUND_USER);
}
if (shouldCancelOnFgUserRequest(mCurrentVibration)) {
- mCurrentVibration.notifyCancelled(new Vibration.EndInfo(
- Status.CANCELLED_BY_FOREGROUND_USER), /* immediate= */ false);
+ mCurrentVibration.requestEnd(Status.CANCELLED_BY_FOREGROUND_USER);
}
}
}
@@ -222,12 +217,10 @@
}
synchronized (mLock) {
if (shouldCancelAppOpModeChangedLocked(mNextVibration)) {
- clearNextVibrationLocked(new Vibration.EndInfo(
- Status.CANCELLED_BY_APP_OPS));
+ clearNextVibrationLocked(Status.CANCELLED_BY_APP_OPS);
}
if (shouldCancelAppOpModeChangedLocked(mCurrentVibration)) {
- mCurrentVibration.notifyCancelled(new Vibration.EndInfo(
- Status.CANCELLED_BY_APP_OPS), /* immediate= */ false);
+ mCurrentVibration.requestEnd(Status.CANCELLED_BY_APP_OPS);
}
}
}
@@ -602,8 +595,9 @@
return null;
}
// Create Vibration.Stats as close to the received request as possible, for tracking.
- HalVibration vib = new HalVibration(token, effect, callerInfo);
- fillVibrationFallbacks(vib, effect);
+ SingleVibrationSession session = new SingleVibrationSession(token, callerInfo, effect);
+ HalVibration vib = session.getVibration();
+ vib.fillFallbacks(mVibrationSettings::getFallbackEffect);
if (attrs.isFlagSet(VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE)) {
// Force update of user settings before checking if this vibration effect should
@@ -617,21 +611,26 @@
}
// Check if user settings or DnD is set to ignore this vibration.
- Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationLocked(vib.callerInfo);
+ Status ignoreStatus = shouldIgnoreVibrationLocked(callerInfo);
+ CallerInfo ignoredBy = null;
// Check if ongoing vibration is more important than this vibration.
- if (vibrationEndInfo == null) {
- vibrationEndInfo = shouldIgnoreVibrationForOngoingLocked(vib);
+ if (ignoreStatus == null) {
+ Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationForOngoingLocked(session);
+ if (vibrationEndInfo != null) {
+ ignoreStatus = vibrationEndInfo.status;
+ ignoredBy = vibrationEndInfo.endedBy;
+ }
}
// If not ignored so far then try to start this vibration.
- if (vibrationEndInfo == null) {
+ if (ignoreStatus == null) {
final long ident = Binder.clearCallingIdentity();
try {
if (mCurrentExternalVibration != null) {
vib.stats.reportInterruptedAnotherVibration(
mCurrentExternalVibration.getCallerInfo());
- endExternalVibrateLocked(Status.CANCELLED_SUPERSEDED, vib.callerInfo,
+ endExternalVibrateLocked(Status.CANCELLED_SUPERSEDED, callerInfo,
/* continueExternalControl= */ false);
} else if (mCurrentVibration != null) {
if (mCurrentVibration.getVibration().canPipelineWith(vib)) {
@@ -645,21 +644,19 @@
} else {
vib.stats.reportInterruptedAnotherVibration(
mCurrentVibration.getVibration().callerInfo);
- mCurrentVibration.notifyCancelled(
- new Vibration.EndInfo(Status.CANCELLED_SUPERSEDED,
- vib.callerInfo),
+ mCurrentVibration.requestEnd(Status.CANCELLED_SUPERSEDED, callerInfo,
/* immediate= */ false);
}
}
- vibrationEndInfo = startVibrationLocked(vib);
+ ignoreStatus = startVibrationLocked(session);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
// Ignored or failed to start the vibration, end it and report metrics right away.
- if (vibrationEndInfo != null) {
- endVibrationLocked(vib, vibrationEndInfo);
+ if (ignoreStatus != null) {
+ endVibrationLocked(session, ignoreStatus, ignoredBy);
}
return vib;
}
@@ -677,27 +674,18 @@
if (DEBUG) {
Slog.d(TAG, "Canceling vibration");
}
- Vibration.EndInfo cancelledByUserInfo =
- new Vibration.EndInfo(Status.CANCELLED_BY_USER);
final long ident = Binder.clearCallingIdentity();
try {
- if (mNextVibration != null
- && shouldCancelVibration(mNextVibration.getVibration(),
- usageFilter, token)) {
- clearNextVibrationLocked(cancelledByUserInfo);
+ if (shouldCancelVibration(mNextVibration, usageFilter, token)) {
+ clearNextVibrationLocked(Status.CANCELLED_BY_USER);
}
- if (mCurrentVibration != null
- && shouldCancelVibration(mCurrentVibration.getVibration(),
- usageFilter, token)) {
- mCurrentVibration.notifyCancelled(
- cancelledByUserInfo, /* immediate= */false);
+ if (shouldCancelVibration(mCurrentVibration, usageFilter, token)) {
+ mCurrentVibration.requestEnd(Status.CANCELLED_BY_USER);
}
- if (mCurrentExternalVibration != null
- && shouldCancelVibration(
- mCurrentExternalVibration.getCallerInfo().attrs,
- usageFilter)) {
- endExternalVibrateLocked(cancelledByUserInfo.status,
- cancelledByUserInfo.endedBy, /* continueExternalControl= */ false);
+ // TODO(b/370948466): investigate why token is not checked here and fix it.
+ if (shouldCancelVibration(mCurrentExternalVibration, usageFilter, null)) {
+ endExternalVibrateLocked(Status.CANCELLED_BY_USER,
+ /* endedBy= */ null, /* continueExternalControl= */ false);
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -842,18 +830,13 @@
return;
}
- HalVibration vib = mCurrentVibration.getVibration();
- Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationLocked(vib.callerInfo);
-
- if (inputDevicesChanged || (vibrationEndInfo != null)) {
+ Status ignoreStatus = shouldIgnoreVibrationLocked(mCurrentVibration.getCallerInfo());
+ if (inputDevicesChanged || (ignoreStatus != null)) {
if (DEBUG) {
Slog.d(TAG, "Canceling vibration because settings changed: "
- + (inputDevicesChanged ? "input devices changed"
- : vibrationEndInfo.status));
+ + (inputDevicesChanged ? "input devices changed" : ignoreStatus));
}
- mCurrentVibration.notifyCancelled(
- new Vibration.EndInfo(Status.CANCELLED_BY_SETTINGS_UPDATE),
- /* immediate= */ false);
+ mCurrentVibration.requestEnd(Status.CANCELLED_BY_SETTINGS_UPDATE);
}
}
}
@@ -873,8 +856,8 @@
if (vibrator == null) {
continue;
}
- Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationLocked(vib.callerInfo);
- if (vibrationEndInfo == null) {
+ Status ignoreStatus = shouldIgnoreVibrationLocked(vib.callerInfo);
+ if (ignoreStatus == null) {
effect = mVibrationScaler.scale(effect, vib.callerInfo.attrs.getUsage());
} else {
// Vibration should not run, use null effect to remove registered effect.
@@ -886,25 +869,21 @@
@GuardedBy("mLock")
@Nullable
- private Vibration.EndInfo startVibrationLocked(HalVibration vib) {
+ private Status startVibrationLocked(SingleVibrationSession session) {
Trace.traceBegin(TRACE_TAG_VIBRATOR, "startVibrationLocked");
try {
if (mInputDeviceDelegate.isAvailable()) {
- return startVibrationOnInputDevicesLocked(vib);
+ return startVibrationOnInputDevicesLocked(session.getVibration());
}
-
- VibrationStepConductor conductor = createVibrationStepConductor(vib);
-
if (mCurrentVibration == null) {
- return startVibrationOnThreadLocked(conductor);
+ return startVibrationOnThreadLocked(session);
}
// If there's already a vibration queued (waiting for the previous one to finish
// cancelling), end it cleanly and replace it with the new one.
// Note that we don't consider pipelining here, because new pipelined ones should
// replace pending non-executing pipelined ones anyway.
- clearNextVibrationLocked(
- new Vibration.EndInfo(Status.IGNORED_SUPERSEDED, vib.callerInfo));
- mNextVibration = conductor;
+ clearNextVibrationLocked(Status.IGNORED_SUPERSEDED, session.getCallerInfo());
+ mNextVibration = session;
return null;
} finally {
Trace.traceEnd(TRACE_TAG_VIBRATOR);
@@ -913,50 +892,45 @@
@GuardedBy("mLock")
@Nullable
- private Vibration.EndInfo startVibrationOnThreadLocked(VibrationStepConductor conductor) {
- HalVibration vib = conductor.getVibration();
- int mode = startAppOpModeLocked(vib.callerInfo);
+ private Status startVibrationOnThreadLocked(SingleVibrationSession session) {
+ VibrationStepConductor conductor = createVibrationStepConductor(session.getVibration());
+ session.setVibrationConductor(conductor);
+ int mode = startAppOpModeLocked(session.getCallerInfo());
switch (mode) {
case AppOpsManager.MODE_ALLOWED:
Trace.asyncTraceBegin(TRACE_TAG_VIBRATOR, "vibration", 0);
// Make sure mCurrentVibration is set while triggering the VibrationThread.
- mCurrentVibration = conductor;
- if (!mCurrentVibration.linkToDeath()) {
+ mCurrentVibration = session;
+ if (!mCurrentVibration.linkToDeath(null)) {
// Shouldn't happen. The method call already logs a wtf.
mCurrentVibration = null; // Aborted.
- return new Vibration.EndInfo(Status.IGNORED_ERROR_TOKEN);
+ return Status.IGNORED_ERROR_TOKEN;
}
- if (!mVibrationThread.runVibrationOnVibrationThread(mCurrentVibration)) {
+ if (!mVibrationThread.runVibrationOnVibrationThread(conductor)) {
// Shouldn't happen. The method call already logs a wtf.
+ mCurrentVibration.setVibrationConductor(null);
mCurrentVibration = null; // Aborted.
- return new Vibration.EndInfo(Status.IGNORED_ERROR_SCHEDULING);
+ return Status.IGNORED_ERROR_SCHEDULING;
}
return null;
case AppOpsManager.MODE_ERRORED:
Slog.w(TAG, "Start AppOpsManager operation errored for uid "
- + vib.callerInfo.uid);
- return new Vibration.EndInfo(Status.IGNORED_ERROR_APP_OPS);
+ + session.getCallerInfo().uid);
+ return Status.IGNORED_ERROR_APP_OPS;
default:
- return new Vibration.EndInfo(Status.IGNORED_APP_OPS);
+ return Status.IGNORED_APP_OPS;
}
}
@GuardedBy("mLock")
- private void endVibrationLocked(Vibration vib, Status status) {
- endVibrationLocked(vib, new Vibration.EndInfo(status));
+ private void endVibrationLocked(VibrationSession session, Status status) {
+ endVibrationLocked(session, status, /* endedBy= */ null);
}
@GuardedBy("mLock")
- private void endVibrationLocked(Vibration vib, Vibration.EndInfo vibrationEndInfo) {
- vib.end(vibrationEndInfo);
- reportEndedVibrationLocked(vib);
- }
-
- @GuardedBy("mLock")
- private void reportEndedVibrationLocked(Vibration vib) {
- logAndRecordVibration(vib.getDebugInfo());
- mFrameworkStatsLogger.writeVibrationReportedAsync(
- vib.getStatsInfo(/* completionUptimeMillis= */ SystemClock.uptimeMillis()));
+ private void endVibrationLocked(VibrationSession session, Status status, CallerInfo endedBy) {
+ session.requestEnd(status, endedBy, /* immediate= */ false);
+ logAndRecordVibration(session.getDebugInfo());
}
private VibrationStepConductor createVibrationStepConductor(HalVibration vib) {
@@ -975,12 +949,11 @@
mFrameworkStatsLogger, requestVibrationParamsFuture, mVibrationThreadCallbacks);
}
- private Vibration.EndInfo startVibrationOnInputDevicesLocked(HalVibration vib) {
+ private Status startVibrationOnInputDevicesLocked(HalVibration vib) {
// Scale resolves the default amplitudes from the effect before scaling them.
vib.scaleEffects(mVibrationScaler);
mInputDeviceDelegate.vibrateIfAvailable(vib.callerInfo, vib.getEffectToPlay());
-
- return new Vibration.EndInfo(Status.FORWARDED_TO_INPUT_DEVICES);
+ return Status.FORWARDED_TO_INPUT_DEVICES;
}
private void logAndRecordPerformHapticFeedbackAttempt(int uid, int deviceId, String opPkg,
@@ -994,9 +967,10 @@
private void logAndRecordVibrationAttempt(@Nullable CombinedVibration effect,
CallerInfo callerInfo, Status status) {
logAndRecordVibration(
- new Vibration.DebugInfoImpl(status, new VibrationStats(),
+ new Vibration.DebugInfoImpl(status, callerInfo,
+ VibrationStats.StatsInfo.findVibrationType(effect), new VibrationStats(),
effect, /* originalEffect= */ null, VibrationScaler.SCALE_NONE,
- VibrationScaler.ADAPTIVE_SCALE_NONE, callerInfo));
+ VibrationScaler.ADAPTIVE_SCALE_NONE));
}
private void logAndRecordVibration(DebugInfo info) {
@@ -1050,39 +1024,25 @@
}
}
- @GuardedBy("mLock")
- private void reportFinishedVibrationLocked() {
- Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
- mCurrentVibration.unlinkToDeath();
- HalVibration vib = mCurrentVibration.getVibration();
- if (DEBUG) {
- Slog.d(TAG, "Reporting vibration " + vib.id + " finished with " + vib.getStatus());
- }
- finishAppOpModeLocked(vib.callerInfo);
- reportEndedVibrationLocked(vib);
- }
-
private void onSyncedVibrationComplete(long vibrationId) {
synchronized (mLock) {
- if (mCurrentVibration != null
- && mCurrentVibration.getVibration().id == vibrationId) {
+ if (mCurrentVibration != null) {
if (DEBUG) {
Slog.d(TAG, "Synced vibration " + vibrationId + " complete, notifying thread");
}
- mCurrentVibration.notifySyncedVibrationComplete();
+ mCurrentVibration.notifySyncedVibratorsCallback(vibrationId);
}
}
}
private void onVibrationComplete(int vibratorId, long vibrationId) {
synchronized (mLock) {
- if (mCurrentVibration != null
- && mCurrentVibration.getVibration().id == vibrationId) {
+ if (mCurrentVibration != null) {
if (DEBUG) {
Slog.d(TAG, "Vibration " + vibrationId + " on vibrator " + vibratorId
+ " complete, notifying thread");
}
- mCurrentVibration.notifyVibratorComplete(vibratorId);
+ mCurrentVibration.notifyVibratorCallback(vibratorId, vibrationId);
}
}
}
@@ -1094,14 +1054,14 @@
*/
@GuardedBy("mLock")
@Nullable
- private Vibration.EndInfo shouldIgnoreVibrationForOngoingLocked(Vibration vib) {
+ private Vibration.EndInfo shouldIgnoreVibrationForOngoingLocked(VibrationSession session) {
if (mCurrentExternalVibration != null) {
- return shouldIgnoreVibrationForOngoing(vib, mCurrentExternalVibration);
+ return shouldIgnoreVibrationForOngoing(session, mCurrentExternalVibration);
}
if (mNextVibration != null) {
- Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationForOngoing(vib,
- mNextVibration.getVibration());
+ Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationForOngoing(session,
+ mNextVibration);
if (vibrationEndInfo != null) {
// Next vibration has higher importance than the new one, so the new vibration
// should be ignored.
@@ -1110,14 +1070,12 @@
}
if (mCurrentVibration != null) {
- HalVibration currentVibration = mCurrentVibration.getVibration();
- if (currentVibration.hasEnded() || mCurrentVibration.wasNotifiedToCancel()) {
- // Current vibration has ended or is cancelling, should not block incoming
- // vibrations.
+ if (mCurrentVibration.wasEndRequested()) {
+ // Current session has ended or is cancelling, should not block incoming vibrations.
return null;
}
- return shouldIgnoreVibrationForOngoing(vib, currentVibration);
+ return shouldIgnoreVibrationForOngoing(session, mCurrentVibration);
}
return null;
@@ -1125,32 +1083,33 @@
/**
* Checks if the ongoing vibration has higher importance than the new one. If they have similar
- * importance, then {@link Vibration#isRepeating()} is used as a tiebreaker.
+ * importance, then {@link VibrationSession#isRepeating()} is used as a tiebreaker.
*
* @return a Vibration.EndInfo if the vibration should be ignored, null otherwise.
*/
@Nullable
private static Vibration.EndInfo shouldIgnoreVibrationForOngoing(
- @NonNull Vibration newVibration, @NonNull Vibration ongoingVibration) {
+ @NonNull VibrationSession newSession, @NonNull VibrationSession ongoingSession) {
- int newVibrationImportance = getVibrationImportance(newVibration);
- int ongoingVibrationImportance = getVibrationImportance(ongoingVibration);
+ int newSessionImportance = getVibrationImportance(newSession);
+ int ongoingSessionImportance = getVibrationImportance(ongoingSession);
- if (newVibrationImportance > ongoingVibrationImportance) {
+ if (newSessionImportance > ongoingSessionImportance) {
// New vibration has higher importance and should not be ignored.
return null;
}
- if (ongoingVibrationImportance > newVibrationImportance) {
+ if (ongoingSessionImportance > newSessionImportance) {
// Existing vibration has higher importance and should not be cancelled.
return new Vibration.EndInfo(Status.IGNORED_FOR_HIGHER_IMPORTANCE,
- ongoingVibration.callerInfo);
+ ongoingSession.getCallerInfo());
}
// Same importance, use repeating as a tiebreaker.
- if (ongoingVibration.isRepeating() && !newVibration.isRepeating()) {
+ if (ongoingSession.isRepeating() && !newSession.isRepeating()) {
// Ongoing vibration is repeating and new one is not, give priority to ongoing
- return new Vibration.EndInfo(Status.IGNORED_FOR_ONGOING, ongoingVibration.callerInfo);
+ return new Vibration.EndInfo(Status.IGNORED_FOR_ONGOING,
+ ongoingSession.getCallerInfo());
}
// New vibration is repeating or this is a complete tie between them,
// give priority to new vibration.
@@ -1164,10 +1123,10 @@
* @return a numeric representation for the vibration importance, larger values represent a
* higher importance
*/
- private static int getVibrationImportance(Vibration vibration) {
- int usage = vibration.callerInfo.attrs.getUsage();
+ private static int getVibrationImportance(VibrationSession session) {
+ int usage = session.getCallerInfo().attrs.getUsage();
if (usage == VibrationAttributes.USAGE_UNKNOWN) {
- if (vibration.isRepeating()) {
+ if (session.isRepeating()) {
usage = VibrationAttributes.USAGE_RINGTONE;
} else {
usage = VibrationAttributes.USAGE_TOUCH;
@@ -1201,10 +1160,10 @@
*/
@GuardedBy("mLock")
@Nullable
- private Vibration.EndInfo shouldIgnoreVibrationLocked(CallerInfo callerInfo) {
+ private Status shouldIgnoreVibrationLocked(CallerInfo callerInfo) {
Status statusFromSettings = mVibrationSettings.shouldIgnoreVibration(callerInfo);
if (statusFromSettings != null) {
- return new Vibration.EndInfo(statusFromSettings);
+ return statusFromSettings;
}
int mode = checkAppOpModeLocked(callerInfo);
@@ -1212,9 +1171,9 @@
if (mode == AppOpsManager.MODE_ERRORED) {
// We might be getting calls from within system_server, so we don't actually
// want to throw a SecurityException here.
- return new Vibration.EndInfo(Status.IGNORED_ERROR_APP_OPS);
+ return Status.IGNORED_ERROR_APP_OPS;
} else {
- return new Vibration.EndInfo(Status.IGNORED_APP_OPS);
+ return Status.IGNORED_APP_OPS;
}
}
@@ -1239,32 +1198,29 @@
/**
* Return true if the vibration has the same token and usage belongs to given usage class.
*
- * @param vib The ongoing or pending vibration to be cancelled.
+ * @param session The ongoing or pending vibration session to be cancelled.
* @param usageFilter The vibration usages to be cancelled, any bitwise combination of
* VibrationAttributes.USAGE_* values.
- * @param token The binder token to identify the vibration origin. Only vibrations
+ * @param tokenFilter The binder token to identify the vibration origin. Only vibrations
* started with the same token can be cancelled with it.
*/
- private boolean shouldCancelVibration(HalVibration vib, int usageFilter, IBinder token) {
- return (vib.callerToken == token) && shouldCancelVibration(vib.callerInfo.attrs,
- usageFilter);
- }
-
- /**
- * Return true if the external vibration usage belongs to given usage class.
- *
- * @param attrs The attributes of an ongoing or pending vibration to be cancelled.
- * @param usageFilter The vibration usages to be cancelled, any bitwise combination of
- * VibrationAttributes.USAGE_* values.
- */
- private boolean shouldCancelVibration(VibrationAttributes attrs, int usageFilter) {
- if (attrs.getUsage() == VibrationAttributes.USAGE_UNKNOWN) {
+ private boolean shouldCancelVibration(@Nullable VibrationSession session, int usageFilter,
+ @Nullable IBinder tokenFilter) {
+ if (session == null) {
+ return false;
+ }
+ if ((tokenFilter != null) && (tokenFilter != session.getCallerToken())) {
+ // Vibration from a different app, this should not cancel it.
+ return false;
+ }
+ int usage = session.getCallerInfo().attrs.getUsage();
+ if (usage == VibrationAttributes.USAGE_UNKNOWN) {
// Special case, usage UNKNOWN would match all filters. Instead it should only match if
// it's cancelling that usage specifically, or if cancelling all usages.
return usageFilter == VibrationAttributes.USAGE_UNKNOWN
|| usageFilter == VibrationAttributes.USAGE_FILTER_MATCH_ALL;
}
- return (usageFilter & attrs.getUsage()) == attrs.getUsage();
+ return (usageFilter & usage) == usage;
}
/**
@@ -1340,45 +1296,6 @@
}
/**
- * Sets fallback effects to all prebaked ones in given combination of effects, based on {@link
- * VibrationSettings#getFallbackEffect}.
- */
- private void fillVibrationFallbacks(HalVibration vib, CombinedVibration effect) {
- if (effect instanceof CombinedVibration.Mono) {
- fillVibrationFallbacks(vib, ((CombinedVibration.Mono) effect).getEffect());
- } else if (effect instanceof CombinedVibration.Stereo) {
- SparseArray<VibrationEffect> effects =
- ((CombinedVibration.Stereo) effect).getEffects();
- for (int i = 0; i < effects.size(); i++) {
- fillVibrationFallbacks(vib, effects.valueAt(i));
- }
- } else if (effect instanceof CombinedVibration.Sequential) {
- List<CombinedVibration> effects =
- ((CombinedVibration.Sequential) effect).getEffects();
- for (int i = 0; i < effects.size(); i++) {
- fillVibrationFallbacks(vib, effects.get(i));
- }
- }
- }
-
- private void fillVibrationFallbacks(HalVibration vib, VibrationEffect effect) {
- if (!(effect instanceof VibrationEffect.Composed composed)) {
- return;
- }
- int segmentCount = composed.getSegments().size();
- for (int i = 0; i < segmentCount; i++) {
- VibrationEffectSegment segment = composed.getSegments().get(i);
- if (segment instanceof PrebakedSegment prebaked) {
- VibrationEffect fallback = mVibrationSettings.getFallbackEffect(
- prebaked.getEffectId());
- if (prebaked.shouldFallback() && fallback != null) {
- vib.addFallback(prebaked.getEffectId(), fallback);
- }
- }
- }
- }
-
- /**
* Return new {@link VibrationAttributes} that only applies flags that this user has permissions
* to use.
*/
@@ -1475,30 +1392,28 @@
}
@GuardedBy("mLock")
- private boolean shouldCancelOnScreenOffLocked(@Nullable VibrationStepConductor conductor) {
- if (conductor == null) {
+ private boolean shouldCancelOnScreenOffLocked(@Nullable VibrationSession session) {
+ if (session == null) {
return false;
}
- HalVibration vib = conductor.getVibration();
- return mVibrationSettings.shouldCancelVibrationOnScreenOff(vib.callerInfo,
- vib.stats.getCreateUptimeMillis());
+ return mVibrationSettings.shouldCancelVibrationOnScreenOff(session.getCallerInfo(),
+ session.getCreateUptimeMillis());
}
@GuardedBy("mLock")
- private boolean shouldCancelAppOpModeChangedLocked(@Nullable VibrationStepConductor conductor) {
- if (conductor == null) {
+ private boolean shouldCancelAppOpModeChangedLocked(@Nullable VibrationSession session) {
+ if (session == null) {
return false;
}
- return checkAppOpModeLocked(conductor.getVibration().callerInfo)
- != AppOpsManager.MODE_ALLOWED;
+ return checkAppOpModeLocked(session.getCallerInfo()) != AppOpsManager.MODE_ALLOWED;
}
@GuardedBy("mLock")
- private boolean shouldCancelOnFgUserRequest(@Nullable VibrationStepConductor conductor) {
- if (conductor == null) {
+ private boolean shouldCancelOnFgUserRequest(@Nullable VibrationSession session) {
+ if (session == null) {
return false;
}
- return conductor.getVibration().callerInfo.attrs.getUsageClass() == USAGE_CLASS_ALARM;
+ return session.getCallerInfo().attrs.getUsageClass() == USAGE_CLASS_ALARM;
}
@GuardedBy("mLock")
@@ -1660,17 +1575,21 @@
}
if (mCurrentVibration != null) {
// This is when we consider the current vibration complete, report metrics.
- reportFinishedVibrationLocked();
+ if (DEBUG) {
+ Slog.d(TAG, "Reporting vibration " + vibrationId + " finished.");
+ }
+ mCurrentVibration.unlinkToDeath();
+ finishAppOpModeLocked(mCurrentVibration.getCallerInfo());
+ logAndRecordVibration(mCurrentVibration.getDebugInfo());
+ Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
mCurrentVibration = null;
}
if (mNextVibration != null) {
- VibrationStepConductor nextConductor = mNextVibration;
+ SingleVibrationSession nextVibration = mNextVibration;
mNextVibration = null;
- Vibration.EndInfo vibrationEndInfo = startVibrationOnThreadLocked(
- nextConductor);
- if (vibrationEndInfo != null) {
- // Failed to start the vibration, end it and report metrics right away.
- endVibrationLocked(nextConductor.getVibration(), vibrationEndInfo);
+ Status startErrorStatus = startVibrationOnThreadLocked(nextVibration);
+ if (startErrorStatus != null) {
+ endVibrationLocked(nextVibration, startErrorStatus);
}
}
}
@@ -1892,17 +1811,22 @@
mInfo.dump(proto, fieldId);
}
}
+ /** Clears mNextVibration if set, ending it cleanly */
+ @GuardedBy("mLock")
+ private void clearNextVibrationLocked(Status status) {
+ clearNextVibrationLocked(status, /* endedBy= */ null);
+ }
/** Clears mNextVibration if set, ending it cleanly */
@GuardedBy("mLock")
- private void clearNextVibrationLocked(Vibration.EndInfo vibrationEndInfo) {
+ private void clearNextVibrationLocked(Status status, CallerInfo endedBy) {
if (mNextVibration != null) {
if (DEBUG) {
- Slog.d(TAG, "Dropping pending vibration " + mNextVibration.getVibration().id
- + " with end info: " + vibrationEndInfo);
+ Slog.d(TAG, "Dropping pending vibration from " + mNextVibration.getCallerInfo()
+ + " with status: " + status);
}
// Clearing next vibration before playing it, end it and report metrics right away.
- endVibrationLocked(mNextVibration.getVibration(), vibrationEndInfo);
+ endVibrationLocked(mNextVibration, status, endedBy);
mNextVibration = null;
}
}
@@ -1927,7 +1851,7 @@
setExternalControl(false, mCurrentExternalVibration.stats);
}
// The external control was turned off, end it and report metrics right away.
- reportEndedVibrationLocked(mCurrentExternalVibration);
+ logAndRecordVibration(mCurrentExternalVibration.getDebugInfo());
mCurrentExternalVibration = null;
}
@@ -1987,16 +1911,16 @@
try {
// Create Vibration.Stats as close to the received request as possible, for
// tracking.
- ExternalVibrationSession externalVibration = new ExternalVibrationSession(vib);
+ ExternalVibrationSession session = new ExternalVibrationSession(vib);
// Mute the request until we run all the checks and accept the vibration.
- externalVibration.muteScale();
+ session.muteScale();
boolean alreadyUnderExternalControl = false;
boolean waitForCompletion = false;
synchronized (mLock) {
if (!hasExternalControlCapability()) {
- endVibrationLocked(externalVibration, Status.IGNORED_UNSUPPORTED);
- return externalVibration.getScale();
+ endVibrationLocked(session, Status.IGNORED_UNSUPPORTED);
+ return session.getScale();
}
if (ActivityManager.checkComponentPermission(
@@ -2006,29 +1930,30 @@
Slog.w(TAG, "pkg=" + vib.getPackage() + ", uid=" + vib.getUid()
+ " tried to play externally controlled vibration"
+ " without VIBRATE permission, ignoring.");
- endVibrationLocked(externalVibration, Status.IGNORED_MISSING_PERMISSION);
- return externalVibration.getScale();
+ endVibrationLocked(session, Status.IGNORED_MISSING_PERMISSION);
+ return session.getScale();
}
- Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationLocked(
- externalVibration.callerInfo);
+ Status ignoreStatus = shouldIgnoreVibrationLocked(session.callerInfo);
+ if (ignoreStatus != null) {
+ endVibrationLocked(session, ignoreStatus);
+ return session.getScale();
+ }
- if (vibrationEndInfo == null
- && mCurrentExternalVibration != null
+ if (mCurrentExternalVibration != null
&& mCurrentExternalVibration.isHoldingSameVibration(vib)) {
// We are already playing this external vibration, so we can return the same
// scale calculated in the previous call to this method.
return mCurrentExternalVibration.getScale();
}
- if (vibrationEndInfo == null) {
- // Check if ongoing vibration is more important than this vibration.
- vibrationEndInfo = shouldIgnoreVibrationForOngoingLocked(externalVibration);
- }
-
+ // Check if ongoing vibration is more important than this vibration.
+ Vibration.EndInfo vibrationEndInfo =
+ shouldIgnoreVibrationForOngoingLocked(session);
if (vibrationEndInfo != null) {
- endVibrationLocked(externalVibration, vibrationEndInfo);
- return externalVibration.getScale();
+ endVibrationLocked(session, vibrationEndInfo.status,
+ vibrationEndInfo.endedBy);
+ return session.getScale();
}
if (mCurrentExternalVibration == null) {
@@ -2036,15 +1961,12 @@
// vibration that may be playing and ready the vibrator for external
// control.
if (mCurrentVibration != null) {
- externalVibration.stats.reportInterruptedAnotherVibration(
+ session.stats.reportInterruptedAnotherVibration(
mCurrentVibration.getVibration().callerInfo);
- clearNextVibrationLocked(
- new Vibration.EndInfo(Status.IGNORED_FOR_EXTERNAL,
- externalVibration.callerInfo));
- mCurrentVibration.notifyCancelled(
- new Vibration.EndInfo(Status.CANCELLED_SUPERSEDED,
- externalVibration.callerInfo),
- /* immediate= */ true);
+ clearNextVibrationLocked(Status.IGNORED_FOR_EXTERNAL,
+ session.callerInfo);
+ mCurrentVibration.requestEnd(Status.CANCELLED_SUPERSEDED,
+ session.callerInfo, /* immediate= */ true);
waitForCompletion = true;
}
} else {
@@ -2060,10 +1982,10 @@
// as we would need to mute the old one still if it came from a different
// controller.
alreadyUnderExternalControl = true;
- externalVibration.stats.reportInterruptedAnotherVibration(
+ session.stats.reportInterruptedAnotherVibration(
mCurrentExternalVibration.getCallerInfo());
endExternalVibrateLocked(Status.CANCELLED_SUPERSEDED,
- externalVibration.callerInfo, /* continueExternalControl= */ true);
+ session.callerInfo, /* continueExternalControl= */ true);
}
}
// Wait for lock and interact with HAL to set external control outside main lock.
@@ -2071,8 +1993,8 @@
if (!mVibrationThread.waitForThreadIdle(VIBRATION_CANCEL_WAIT_MILLIS)) {
Slog.e(TAG, "Timed out waiting for vibration to cancel");
synchronized (mLock) {
- endVibrationLocked(externalVibration, Status.IGNORED_ERROR_CANCELLING);
- return externalVibration.getScale();
+ endVibrationLocked(session, Status.IGNORED_ERROR_CANCELLING);
+ return session.getScale();
}
}
}
@@ -2080,7 +2002,7 @@
if (DEBUG) {
Slog.d(TAG, "Vibrator going under external control.");
}
- setExternalControl(true, externalVibration.stats);
+ setExternalControl(true, session.stats);
}
synchronized (mLock) {
if (DEBUG) {
@@ -2095,14 +2017,14 @@
mVibrationSettings.update();
}
- mCurrentExternalVibration = externalVibration;
- externalVibration.linkToDeath(this::onExternalVibrationBinderDied);
- externalVibration.scale(mVibrationScaler, attrs.getUsage());
+ mCurrentExternalVibration = session;
+ session.linkToDeath(this::onExternalVibrationBinderDied);
+ session.scale(mVibrationScaler, attrs.getUsage());
// Vibrator will start receiving data from external channels after this point.
// Report current time as the vibration start time, for debugging.
- externalVibration.stats.reportStarted();
- return externalVibration.getScale();
+ session.stats.reportStarted();
+ return session.getScale();
}
} finally {
Trace.traceEnd(TRACE_TAG_VIBRATOR);
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 517f631..ae30fcd 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -442,6 +442,8 @@
throw new IllegalArgumentException("File descriptors passed in Intent");
}
+ mService.mAmInternal.addCreatorToken(resultData);
+
final ActivityRecord r;
synchronized (mGlobalLock) {
r = ActivityRecord.isInRootTaskLocked(token);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 2972771..0b36c7e 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -6429,11 +6429,7 @@
// and the token could be null.
return;
}
- final AppCompatCameraPolicy cameraPolicy = AppCompatCameraPolicy
- .getAppCompatCameraPolicy(r);
- if (cameraPolicy != null) {
- cameraPolicy.onActivityRefreshed(r);
- }
+ AppCompatCameraPolicy.onActivityRefreshed(r);
}
static void splashScreenAttachedLocked(IBinder token) {
@@ -9476,11 +9472,7 @@
return;
}
- final AppCompatCameraPolicy cameraPolicy = AppCompatCameraPolicy.getAppCompatCameraPolicy(
- this);
- if (cameraPolicy != null) {
- cameraPolicy.onActivityConfigurationChanging(this, newConfig, lastReportedConfig);
- }
+ AppCompatCameraPolicy.onActivityConfigurationChanging(this, newConfig, lastReportedConfig);
}
/** Get process configuration, or global config if the process is not set. */
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index dc960a0..3dfc8f4 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -1228,6 +1228,7 @@
String callingFeatureId, Intent intent, String resolvedType, IBinder resultTo,
String resultWho, int requestCode, int startFlags, ProfilerInfo profilerInfo,
Bundle bOptions) {
+ mAmInternal.addCreatorToken(intent);
return startActivityAsUser(caller, callingPackage, callingFeatureId, intent, resolvedType,
resultTo, resultWho, requestCode, startFlags, profilerInfo, bOptions,
UserHandle.getCallingUserId());
@@ -1240,6 +1241,11 @@
assertPackageMatchesCallingUid(callingPackage);
final String reason = "startActivities";
enforceNotIsolatedCaller(reason);
+ if (intents != null) {
+ for (Intent intent : intents) {
+ mAmInternal.addCreatorToken(intent);
+ }
+ }
userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId, reason);
// TODO: Switch to user app stacks here.
return getActivityStartController().startActivities(caller, -1, 0, -1, callingPackage,
@@ -1269,6 +1275,7 @@
@Nullable String callingFeatureId, Intent intent, String resolvedType,
IBinder resultTo, String resultWho, int requestCode, int startFlags,
ProfilerInfo profilerInfo, Bundle bOptions, int userId, boolean validateIncomingUser) {
+ mAmInternal.addCreatorToken(intent);
final SafeActivityOptions opts = SafeActivityOptions.fromBundle(bOptions);
assertPackageMatchesCallingUid(callingPackage);
@@ -1323,6 +1330,7 @@
}
// Remove existing mismatch flag so it can be properly updated later
fillInIntent.removeExtendedFlags(Intent.EXTENDED_FLAG_FILTER_MISMATCH);
+ mAmInternal.addCreatorToken(fillInIntent);
}
if (!(target instanceof PendingIntentRecord)) {
@@ -1352,6 +1360,9 @@
if (intent != null && intent.hasFileDescriptors()) {
throw new IllegalArgumentException("File descriptors passed in Intent");
}
+
+ mAmInternal.addCreatorToken(intent);
+
SafeActivityOptions options = SafeActivityOptions.fromBundle(bOptions);
synchronized (mGlobalLock) {
diff --git a/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
index 0e66629..d59046f 100644
--- a/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
@@ -255,13 +255,10 @@
mActivityRecord.getOverrideOrientation());
final AppCompatCameraOverrides cameraOverrides =
mActivityRecord.mAppCompatController.getAppCompatCameraOverrides();
- final AppCompatCameraPolicy cameraPolicy = AppCompatCameraPolicy.getAppCompatCameraPolicy(
- mActivityRecord);
// Don't resize to split screen size when in book mode if letterbox position is centered
return (isBookMode && isNotCenteredHorizontally || isTabletopMode && isLandscape)
|| cameraOverrides.isCameraCompatSplitScreenAspectRatioAllowed()
- && (cameraPolicy != null
- && cameraPolicy.isTreatmentEnabledForActivity(mActivityRecord));
+ && AppCompatCameraPolicy.isTreatmentEnabledForActivity(mActivityRecord);
}
/**
diff --git a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
index 3b023fe..548c0a3 100644
--- a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
@@ -74,11 +74,9 @@
@NonNull Rect parentBounds) {
// If in camera compat mode, aspect ratio from the camera compat policy has priority over
// default letterbox aspect ratio.
- final AppCompatCameraPolicy cameraPolicy = AppCompatCameraPolicy.getAppCompatCameraPolicy(
- mActivityRecord);
- if (cameraPolicy != null && cameraPolicy.shouldCameraCompatControlAspectRatio(
+ if (AppCompatCameraPolicy.shouldCameraCompatControlAspectRatio(
mActivityRecord)) {
- return cameraPolicy.getCameraCompatAspectRatio(mActivityRecord);
+ return AppCompatCameraPolicy.getCameraCompatAspectRatio(mActivityRecord);
}
final float letterboxAspectRatioOverride =
@@ -128,12 +126,8 @@
if (aspectRatioOverrides.shouldApplyUserMinAspectRatioOverride()) {
return aspectRatioOverrides.getUserMinAspectRatio();
}
- final AppCompatCameraPolicy cameraPolicy = AppCompatCameraPolicy.getAppCompatCameraPolicy(
- mActivityRecord);
- final boolean shouldOverrideMinAspectRatioForCamera = cameraPolicy != null
- && cameraPolicy.shouldOverrideMinAspectRatioForCamera(mActivityRecord);
if (!aspectRatioOverrides.shouldOverrideMinAspectRatio()
- && !shouldOverrideMinAspectRatioForCamera) {
+ && !AppCompatCameraPolicy.shouldOverrideMinAspectRatioForCamera(mActivityRecord)) {
if (mActivityRecord.isUniversalResizeable()) {
return 0;
}
diff --git a/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java b/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java
index f6090eb..1d00136 100644
--- a/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java
@@ -70,9 +70,10 @@
}
}
- void onActivityRefreshed(@NonNull ActivityRecord activity) {
- if (mActivityRefresher != null) {
- mActivityRefresher.onActivityRefreshed(activity);
+ static void onActivityRefreshed(@NonNull ActivityRecord activity) {
+ final AppCompatCameraPolicy cameraPolicy = getAppCompatCameraPolicy(activity);
+ if (cameraPolicy != null && cameraPolicy.mActivityRefresher != null) {
+ cameraPolicy.mActivityRefresher.onActivityRefreshed(activity);
}
}
@@ -88,10 +89,11 @@
* camera preview and can lead to sideways or stretching issues persisting even after force
* rotation.
*/
- void onActivityConfigurationChanging(@NonNull ActivityRecord activity,
+ static void onActivityConfigurationChanging(@NonNull ActivityRecord activity,
@NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig) {
- if (mActivityRefresher != null) {
- mActivityRefresher.onActivityConfigurationChanging(activity, newConfig,
+ final AppCompatCameraPolicy cameraPolicy = getAppCompatCameraPolicy(activity);
+ if (cameraPolicy != null && cameraPolicy.mActivityRefresher != null) {
+ cameraPolicy.mActivityRefresher.onActivityConfigurationChanging(activity, newConfig,
lastReportedConfig);
}
}
@@ -108,11 +110,11 @@
}
}
- boolean isActivityEligibleForOrientationOverride(@NonNull ActivityRecord activity) {
- if (mDisplayRotationCompatPolicy != null) {
- return mDisplayRotationCompatPolicy.isActivityEligibleForOrientationOverride(activity);
- }
- return false;
+ static boolean isActivityEligibleForOrientationOverride(@NonNull ActivityRecord activity) {
+ final AppCompatCameraPolicy cameraPolicy = getAppCompatCameraPolicy(activity);
+ return cameraPolicy != null && cameraPolicy.mDisplayRotationCompatPolicy != null
+ && cameraPolicy.mDisplayRotationCompatPolicy
+ .isActivityEligibleForOrientationOverride(activity);
}
/**
@@ -125,11 +127,11 @@
* <li>The activity has fixed orientation but not "locked" or "nosensor" one.
* </ul>
*/
- boolean isTreatmentEnabledForActivity(@Nullable ActivityRecord activity) {
- if (mDisplayRotationCompatPolicy != null) {
- return mDisplayRotationCompatPolicy.isTreatmentEnabledForActivity(activity);
- }
- return false;
+ static boolean isTreatmentEnabledForActivity(@NonNull ActivityRecord activity) {
+ final AppCompatCameraPolicy cameraPolicy = getAppCompatCameraPolicy(activity);
+ return cameraPolicy != null && cameraPolicy.mDisplayRotationCompatPolicy != null
+ && cameraPolicy.mDisplayRotationCompatPolicy
+ .isTreatmentEnabledForActivity(activity);
}
void start() {
@@ -176,23 +178,31 @@
}
// TODO(b/369070416): have policies implement the same interface.
- boolean shouldCameraCompatControlOrientation(@NonNull ActivityRecord activity) {
- return (mDisplayRotationCompatPolicy != null
- && mDisplayRotationCompatPolicy.shouldCameraCompatControlOrientation(
- activity))
- || (mCameraCompatFreeformPolicy != null
- && mCameraCompatFreeformPolicy.shouldCameraCompatControlOrientation(
- activity));
+ static boolean shouldCameraCompatControlOrientation(@NonNull ActivityRecord activity) {
+ final AppCompatCameraPolicy cameraPolicy = getAppCompatCameraPolicy(activity);
+ if (cameraPolicy == null) {
+ return false;
+ }
+ return (cameraPolicy.mDisplayRotationCompatPolicy != null
+ && cameraPolicy.mDisplayRotationCompatPolicy
+ .shouldCameraCompatControlOrientation(activity))
+ || (cameraPolicy.mCameraCompatFreeformPolicy != null
+ && cameraPolicy.mCameraCompatFreeformPolicy
+ .shouldCameraCompatControlOrientation(activity));
}
// TODO(b/369070416): have policies implement the same interface.
- boolean shouldCameraCompatControlAspectRatio(@NonNull ActivityRecord activity) {
- return (mDisplayRotationCompatPolicy != null
- && mDisplayRotationCompatPolicy.shouldCameraCompatControlAspectRatio(
- activity))
- || (mCameraCompatFreeformPolicy != null
- && mCameraCompatFreeformPolicy.shouldCameraCompatControlAspectRatio(
- activity));
+ static boolean shouldCameraCompatControlAspectRatio(@NonNull ActivityRecord activity) {
+ final AppCompatCameraPolicy cameraPolicy = getAppCompatCameraPolicy(activity);
+ if (cameraPolicy == null) {
+ return false;
+ }
+ return (cameraPolicy.mDisplayRotationCompatPolicy != null
+ && cameraPolicy.mDisplayRotationCompatPolicy
+ .shouldCameraCompatControlAspectRatio(activity))
+ || (cameraPolicy.mCameraCompatFreeformPolicy != null
+ && cameraPolicy.mCameraCompatFreeformPolicy
+ .shouldCameraCompatControlAspectRatio(activity));
}
// TODO(b/369070416): have policies implement the same interface.
@@ -200,29 +210,41 @@
* @return {@code true} if the Camera is active for the provided {@link ActivityRecord} and
* any camera compat treatment could be triggered for the current windowing mode.
*/
- private boolean isCameraRunningAndWindowingModeEligible(@NonNull ActivityRecord activity) {
- return (mDisplayRotationCompatPolicy != null
- && mDisplayRotationCompatPolicy.isCameraRunningAndWindowingModeEligible(activity,
- /* mustBeFullscreen */ true))
- || (mCameraCompatFreeformPolicy != null && mCameraCompatFreeformPolicy
- .isCameraRunningAndWindowingModeEligible(activity));
+ private static boolean isCameraRunningAndWindowingModeEligible(
+ @NonNull ActivityRecord activity) {
+ final AppCompatCameraPolicy cameraPolicy = getAppCompatCameraPolicy(activity);
+ if (cameraPolicy == null) {
+ return false;
+ }
+ return (cameraPolicy.mDisplayRotationCompatPolicy != null
+ && cameraPolicy.mDisplayRotationCompatPolicy
+ .isCameraRunningAndWindowingModeEligible(activity,
+ /* mustBeFullscreen */ true))
+ || (cameraPolicy.mCameraCompatFreeformPolicy != null
+ && cameraPolicy.mCameraCompatFreeformPolicy
+ .isCameraRunningAndWindowingModeEligible(activity));
}
@Nullable
String getSummaryForDisplayRotationHistoryRecord() {
- if (mDisplayRotationCompatPolicy != null) {
- return mDisplayRotationCompatPolicy.getSummaryForDisplayRotationHistoryRecord();
- }
- return null;
+ return mDisplayRotationCompatPolicy != null
+ ? mDisplayRotationCompatPolicy.getSummaryForDisplayRotationHistoryRecord()
+ : null;
}
// TODO(b/369070416): have policies implement the same interface.
- float getCameraCompatAspectRatio(@NonNull ActivityRecord activity) {
- float displayRotationCompatPolicyAspectRatio = mDisplayRotationCompatPolicy != null
- ? mDisplayRotationCompatPolicy.getCameraCompatAspectRatio(activity)
+ static float getCameraCompatAspectRatio(@NonNull ActivityRecord activity) {
+ final AppCompatCameraPolicy cameraPolicy = getAppCompatCameraPolicy(activity);
+ if (cameraPolicy == null) {
+ return 1.0f;
+ }
+ float displayRotationCompatPolicyAspectRatio =
+ cameraPolicy.mDisplayRotationCompatPolicy != null
+ ? cameraPolicy.mDisplayRotationCompatPolicy.getCameraCompatAspectRatio(activity)
: MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
- float cameraCompatFreeformPolicyAspectRatio = mCameraCompatFreeformPolicy != null
- ? mCameraCompatFreeformPolicy.getCameraCompatAspectRatio(activity)
+ float cameraCompatFreeformPolicyAspectRatio =
+ cameraPolicy.mCameraCompatFreeformPolicy != null
+ ? cameraPolicy.mCameraCompatFreeformPolicy.getCameraCompatAspectRatio(activity)
: MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
return Math.max(displayRotationCompatPolicyAspectRatio,
cameraCompatFreeformPolicyAspectRatio);
@@ -232,8 +254,8 @@
* Whether we should apply the min aspect ratio per-app override only when an app is connected
* to the camera.
*/
- boolean shouldOverrideMinAspectRatioForCamera(@NonNull ActivityRecord activityRecord) {
- return isCameraRunningAndWindowingModeEligible(activityRecord)
+ static boolean shouldOverrideMinAspectRatioForCamera(@NonNull ActivityRecord activityRecord) {
+ return AppCompatCameraPolicy.isCameraRunningAndWindowingModeEligible(activityRecord)
&& activityRecord.mAppCompatController.getAppCompatCameraOverrides()
.isOverrideMinAspectRatioForCameraEnabled();
}
diff --git a/services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java b/services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java
index bd01351..c84711d 100644
--- a/services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.content.pm.ActivityInfo.OVERRIDE_ANY_ORIENTATION;
import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED;
import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION;
@@ -106,6 +107,16 @@
return isChangeEnabled(mActivityRecord, OVERRIDE_RESPECT_REQUESTED_ORIENTATION);
}
+ boolean shouldRespectRequestedOrientationDueToOverride() {
+ // Checking TaskFragment rather than ActivityRecord to ensure that transition
+ // between fullscreen and PiP would work well. Checking TaskFragment rather than
+ // Task to ensure that Activity Embedding is excluded.
+ return mActivityRecord.isVisibleRequested() && mActivityRecord.getTaskFragment() != null
+ && mActivityRecord.getTaskFragment().getWindowingMode() == WINDOWING_MODE_FULLSCREEN
+ && mActivityRecord.mAppCompatController.getAppCompatOrientationOverrides()
+ .isOverrideRespectRequestedOrientationEnabled();
+ }
+
/**
* Whether an app is calling {@link android.app.Activity#setRequestedOrientation}
* in a loop and orientation request should be ignored.
diff --git a/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
index 5bd4aeb..f5d58ea 100644
--- a/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
@@ -58,10 +58,8 @@
&& displayContent.getIgnoreOrientationRequest();
final boolean shouldApplyUserFullscreenOverride = mAppCompatOverrides
.getAppCompatAspectRatioOverrides().shouldApplyUserFullscreenOverride();
- final AppCompatCameraPolicy cameraPolicy = AppCompatCameraPolicy
- .getAppCompatCameraPolicy(mActivityRecord);
- final boolean shouldCameraCompatControlOrientation = cameraPolicy != null
- && cameraPolicy.shouldCameraCompatControlOrientation(mActivityRecord);
+ final boolean shouldCameraCompatControlOrientation =
+ AppCompatCameraPolicy.shouldCameraCompatControlOrientation(mActivityRecord);
if (shouldApplyUserFullscreenOverride && isIgnoreOrientationRequestEnabled
// Do not override orientation to fullscreen for camera activities.
// Fixed-orientation activities are rarely tested in other orientations, and it
@@ -98,7 +96,7 @@
if (displayContent != null
&& mAppCompatOverrides.getAppCompatCameraOverrides()
.isOverrideOrientationOnlyForCameraEnabled()
- && !displayContent.mAppCompatCameraPolicy
+ && !AppCompatCameraPolicy
.isActivityEligibleForOrientationOverride(mActivityRecord)) {
return candidate;
}
@@ -213,5 +211,4 @@
}
return false;
}
-
}
diff --git a/services/core/java/com/android/server/wm/AppTaskImpl.java b/services/core/java/com/android/server/wm/AppTaskImpl.java
index d699af8..4d17ed2 100644
--- a/services/core/java/com/android/server/wm/AppTaskImpl.java
+++ b/services/core/java/com/android/server/wm/AppTaskImpl.java
@@ -160,6 +160,7 @@
Intent intent, String resolvedType, Bundle bOptions) {
checkCallerOrSystemOrRoot();
mService.assertPackageMatchesCallingUid(callingPackage);
+ mService.mAmInternal.addCreatorToken(intent);
int callingUser = UserHandle.getCallingUserId();
Task task;
diff --git a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
index 1073713..264c8be 100644
--- a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
+++ b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
@@ -146,7 +146,7 @@
"process bound by foreground uid");
}
// Allow if the caller has an activity in any foreground task.
- if (checkConfiguration.checkVisibility && hasActivityInVisibleTask
+ if (checkConfiguration.checkOtherExemptions && hasActivityInVisibleTask
&& appSwitchState != APP_SWITCH_DISALLOW) {
return new BalVerdict(BAL_ALLOW_FOREGROUND, /*background*/ false,
"process has activity in foreground task");
diff --git a/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java b/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java
index 3b2f723..c8cb621 100644
--- a/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java
+++ b/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java
@@ -194,12 +194,8 @@
return aspectRatioOverrides.getUserMinAspectRatio();
}
- final AppCompatCameraPolicy cameraPolicy = AppCompatCameraPolicy.getAppCompatCameraPolicy(
- mActivityRecord);
- final boolean shouldOverrideMinAspectRatioForCamera = cameraPolicy != null
- && cameraPolicy.shouldOverrideMinAspectRatioForCamera(mActivityRecord);
if (!aspectRatioOverrides.shouldOverrideMinAspectRatio()
- && !shouldOverrideMinAspectRatioForCamera) {
+ && !AppCompatCameraPolicy.shouldOverrideMinAspectRatioForCamera(mActivityRecord)) {
if (mActivityRecord.isUniversalResizeable()) {
return 0;
}
diff --git a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
index 34b5f6a..1a8f5fc 100644
--- a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
+++ b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
@@ -30,7 +30,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityOptions;
-import android.app.TaskInfo;
import android.content.pm.ActivityInfo.ScreenOrientation;
import android.content.pm.ActivityInfo.WindowLayout;
import android.graphics.Rect;
@@ -98,7 +97,6 @@
private static Rect calculateInitialBounds(@NonNull Task task,
@NonNull ActivityRecord activity, @NonNull Rect stableBounds
) {
- final TaskInfo taskInfo = task.getTaskInfo();
// Display bounds not taking into account insets.
final TaskDisplayArea displayArea = task.getDisplayArea();
final Rect screenBounds = displayArea.getBounds();
@@ -118,14 +116,15 @@
float appAspectRatio = desktopAppCompatAspectRatioPolicy.calculateAspectRatio(task);
final float tdaWidth = stableBounds.width();
final float tdaHeight = stableBounds.height();
+ final int taskConfigOrientation = task.getConfiguration().orientation;
final int activityOrientation = getActivityOrientation(activity, task);
- final Size initialSize = switch (taskInfo.configuration.orientation) {
+ final Size initialSize = switch (taskConfigOrientation) {
case ORIENTATION_LANDSCAPE -> {
// Device in landscape orientation.
if (appAspectRatio == 0) {
appAspectRatio = 1;
}
- if (canChangeAspectRatio(desktopAppCompatAspectRatioPolicy, taskInfo, task)) {
+ if (canChangeAspectRatio(desktopAppCompatAspectRatioPolicy, task)) {
if (isFixedOrientationPortrait(activityOrientation)) {
// For portrait resizeable activities, respect apps fullscreen width but
// apply ideal size height.
@@ -143,7 +142,7 @@
// Device in portrait orientation.
final int customPortraitWidthForLandscapeApp = screenBounds.width()
- (DESKTOP_MODE_LANDSCAPE_APP_PADDING * 2);
- if (canChangeAspectRatio(desktopAppCompatAspectRatioPolicy, taskInfo, task)) {
+ if (canChangeAspectRatio(desktopAppCompatAspectRatioPolicy, task)) {
if (isFixedOrientationLandscape(activityOrientation)) {
if (appAspectRatio == 0) {
appAspectRatio = tdaWidth / (tdaWidth - 1);
@@ -182,8 +181,8 @@
*/
private static boolean canChangeAspectRatio(
@NonNull DesktopAppCompatAspectRatioPolicy desktopAppCompatAspectRatioPolicy,
- @NonNull TaskInfo taskInfo, @NonNull Task task) {
- return taskInfo.isResizeable
+ @NonNull Task task) {
+ return task.isResizeable()
&& !desktopAppCompatAspectRatioPolicy.hasMinAspectRatioOverride(task);
}
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index 0416f74..29ffda7 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -16,7 +16,6 @@
package com.android.server.wm;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
@@ -260,15 +259,14 @@
if (mDisplayContent == null) {
return false;
}
- ActivityRecord activity = mDisplayContent.topRunningActivity(
- /* considerKeyguardState= */ true);
- return activity != null && activity.getTaskFragment() != null
- // Checking TaskFragment rather than ActivityRecord to ensure that transition
- // between fullscreen and PiP would work well. Checking TaskFragment rather than
- // Task to ensure that Activity Embedding is excluded.
- && activity.getTaskFragment().getWindowingMode() == WINDOWING_MODE_FULLSCREEN
- && activity.mAppCompatController.getAppCompatOrientationOverrides()
- .isOverrideRespectRequestedOrientationEnabled();
+
+ // Top running activity can be freeform and ignore orientation request from bottom activity
+ // that should be respected, Check all activities in display to make sure any eligible
+ // activity should be respected.
+ final ActivityRecord activity = mDisplayContent.getActivity((r) ->
+ r.mAppCompatController.getAppCompatOrientationOverrides()
+ .shouldRespectRequestedOrientationDueToOverride());
+ return activity != null;
}
boolean getIgnoreOrientationRequest() {
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index c062f5a..04a625b 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -79,6 +79,7 @@
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.window.flags.Flags.enableFullyImmersiveInDesktop;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -2515,10 +2516,16 @@
defaultTaskDisplayArea.getRootTask(task -> task.isVisible()
&& task.getTopLeafTask().getAdjacentTask() != null)
!= null;
- final boolean freeformRootTaskVisible =
- defaultTaskDisplayArea.isRootTaskVisible(WINDOWING_MODE_FREEFORM);
+ final Task topFreeformTask = defaultTaskDisplayArea
+ .getTopRootTaskInWindowingMode(WINDOWING_MODE_FREEFORM);
+ final boolean freeformRootTaskVisible = topFreeformTask != null
+ && topFreeformTask.isVisible();
+ final boolean inNonFullscreenFreeformMode = freeformRootTaskVisible
+ && !topFreeformTask.getBounds().equals(mDisplayContent.getBounds());
- getInsetsPolicy().updateSystemBars(win, adjacentTasksVisible, freeformRootTaskVisible);
+ getInsetsPolicy().updateSystemBars(win, adjacentTasksVisible,
+ enableFullyImmersiveInDesktop()
+ ? inNonFullscreenFreeformMode : freeformRootTaskVisible);
final boolean topAppHidesStatusBar = topAppHidesSystemBar(Type.statusBars());
if (getStatusBar() != null) {
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index b414a862..24a6f118 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -47,6 +47,7 @@
import android.view.WindowInsetsAnimation;
import android.view.WindowInsetsAnimation.Bounds;
import android.view.WindowManager;
+import android.view.inputmethod.Flags;
import android.view.inputmethod.ImeTracker;
import android.view.inputmethod.InputMethodManager;
@@ -412,6 +413,22 @@
state.addSource(imeSource);
return state;
}
+ } else if (Flags.refactorInsetsController()
+ && (w.mMergedExcludeInsetsTypes & WindowInsets.Type.ime()) != 0) {
+ // In some cases (e.g. split screen from when the IME was requested and the animation
+ // actually starts) the insets should not be send, unless the flag is unset.
+ final InsetsSource originalImeSource = originalState.peekSource(ID_IME);
+ if (originalImeSource != null && originalImeSource.isVisible()) {
+ final InsetsState state = copyState
+ ? new InsetsState(originalState)
+ : originalState;
+ final InsetsSource imeSource = new InsetsSource(originalImeSource);
+ // Setting the height to zero, pretending we're in floating mode
+ imeSource.setFrame(0, 0, 0, 0);
+ imeSource.setVisibleFrame(imeSource.getFrame());
+ state.addSource(imeSource);
+ return state;
+ }
}
return originalState;
}
@@ -611,8 +628,9 @@
return (mForcedShowingTypes & types) == types;
}
- void updateSystemBars(WindowState win, boolean inSplitScreenMode, boolean inFreeformMode) {
- mForcedShowingTypes = (inSplitScreenMode || inFreeformMode)
+ void updateSystemBars(WindowState win, boolean inSplitScreenMode,
+ boolean inNonFullscreenFreeformMode) {
+ mForcedShowingTypes = (inSplitScreenMode || inNonFullscreenFreeformMode)
? (Type.statusBars() | Type.navigationBars())
: forceShowingNavigationBars(win)
? Type.navigationBars()
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index ede587c..5dddf36 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -458,6 +458,12 @@
mDisplayContent.notifyInsetsChanged(mDispatchInsetsChanged);
}
+ void notifyInsetsChanged(ArraySet<WindowState> changedWindows) {
+ for (int i = changedWindows.size() - 1; i >= 0; i--) {
+ mDispatchInsetsChanged.accept(changedWindows.valueAt(i));
+ }
+ }
+
/**
* Checks if the control target has pending controls.
*
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index b6e4c11..9de96f14 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -551,6 +551,12 @@
long currentElapsedTime = SystemClock.elapsedRealtime();
for (int i = 0; i < tasks.size(); i++) {
Task task = tasks.get(i);
+ // Remove the task restored from xml if any existing tasks match.
+ if (findRemoveIndexForAddTask(task) >= 0) {
+ tasks.remove(i);
+ i--;
+ continue;
+ }
task.lastActiveTime = currentElapsedTime - i;
}
@@ -561,6 +567,7 @@
if (existedTaskIds.size() > 0) {
syncPersistentTaskIdsLocked();
}
+ mTaskNotificationController.notifyTaskListUpdated();
}
private boolean isRecentTasksLoaded(int userId) {
@@ -679,27 +686,35 @@
if (isRecentTasksLoaded(userId)) {
Slog.i(TAG, "Unloading recents for user " + userId + " from memory.");
mUsersWithRecentsLoaded.delete(userId);
- removeTasksForUserLocked(userId);
+ removeTasksForUserFromMemoryLocked(userId);
}
mPersistedTaskIds.delete(userId);
mTaskPersister.unloadUserDataFromMemory(userId);
}
/** Remove recent tasks for a user. */
- private void removeTasksForUserLocked(int userId) {
+ private void removeTasksForUserFromMemoryLocked(int userId) {
if (userId <= 0) {
Slog.i(TAG, "Can't remove recent task on user " + userId);
return;
}
+ boolean notifyTaskUpdated = false;
for (int i = mTasks.size() - 1; i >= 0; --i) {
Task task = mTasks.get(i);
if (task.mUserId == userId) {
ProtoLog.i(WM_DEBUG_TASKS, "remove RecentTask %s when finishing user "
+ "%d", task, userId);
- remove(task);
+ mTasks.remove(task);
+ mService.mWindowManager.mSnapshotController.mTaskSnapshotController
+ .removeSnapshotCache(task.mTaskId);
+ // Only notify if list has changed.
+ notifyTaskUpdated = true;
}
}
+ if (notifyTaskUpdated) {
+ mTaskNotificationController.notifyTaskListUpdated();
+ }
}
void onPackagesSuspendedChanged(String[] packages, boolean suspended, int userId) {
diff --git a/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java b/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java
index 8e32813..2428836 100644
--- a/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java
+++ b/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java
@@ -156,7 +156,7 @@
Listeners mRegisteredListeners = new Listeners();
- private InputWindowHandle[] mLastWindowHandles;
+ private Pair<InputWindowHandle[], WindowInfosListener.DisplayInfo[]> mLastWindowHandles;
private void startHandlerThreadIfNeeded() {
synchronized (mHandlerThreadLock) {
@@ -222,10 +222,10 @@
@Override
public void onWindowInfosChanged(InputWindowHandle[] windowHandles,
DisplayInfo[] displayInfos) {
- mHandler.post(() -> computeTpl(windowHandles));
+ mHandler.post(() -> computeTpl(new Pair<>(windowHandles, displayInfos)));
}
};
- mLastWindowHandles = mWindowInfosListener.register().first;
+ mLastWindowHandles = mWindowInfosListener.register();
}
private void unregisterWindowInfosListener() {
@@ -238,28 +238,52 @@
mLastWindowHandles = null;
}
- private void computeTpl(InputWindowHandle[] windowHandles) {
+ private void computeTpl(
+ Pair<InputWindowHandle[], WindowInfosListener.DisplayInfo[]> windowHandles) {
mLastWindowHandles = windowHandles;
- if (mLastWindowHandles == null || mLastWindowHandles.length == 0
+ if (mLastWindowHandles == null || mLastWindowHandles.first.length == 0
|| mRegisteredListeners.isEmpty()) {
return;
}
Rect tmpRect = new Rect();
+ RectF tmpRectF = new RectF();
+ Rect tmpLogicalDisplaySize = new Rect();
Matrix tmpInverseMatrix = new Matrix();
float[] tmpMatrix = new float[9];
Region coveredRegionsAbove = new Region();
long currTimeMs = System.currentTimeMillis();
- ProtoLog.v(WM_DEBUG_TPL, "Checking %d windows", mLastWindowHandles.length);
+ ProtoLog.v(WM_DEBUG_TPL, "Checking %d windows", mLastWindowHandles.first.length);
ArrayMap<ITrustedPresentationListener, Pair<IntArray, IntArray>> listenerUpdates =
new ArrayMap<>();
- for (var windowHandle : mLastWindowHandles) {
+ for (var windowHandle : mLastWindowHandles.first) {
if (!windowHandle.canOccludePresentation) {
ProtoLog.v(WM_DEBUG_TPL, "Skipping %s", windowHandle.name);
continue;
}
- tmpRect.set(windowHandle.frame);
+ var displayFound = false;
+ tmpRectF.set(windowHandle.frame);
+ for (var displayHandle : mLastWindowHandles.second) {
+ if (displayHandle.mDisplayId == windowHandle.displayId) {
+ // Transform the window frame into display logical space and then
+ // crop by the logical display size
+ displayHandle.mTransform.mapRect(tmpRectF);
+ tmpRectF.round(tmpRect);
+ tmpLogicalDisplaySize.set(0, 0, displayHandle.mLogicalSize.getWidth(),
+ displayHandle.mLogicalSize.getHeight());
+ tmpRect.intersect(tmpLogicalDisplaySize);
+ displayFound = true;
+ break;
+ }
+ }
+
+ if (!displayFound) {
+ ProtoLog.v(WM_DEBUG_TPL, "Skipping %s, no associated display %d", windowHandle.name,
+ windowHandle.displayId);
+ continue;
+ }
+
var listeners = mRegisteredListeners.get(windowHandle.getWindowToken());
if (listeners != null) {
Region region = new Region();
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index da64a5f..af57c84 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -32,6 +32,7 @@
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.os.UserHandle.USER_NULL;
import static android.view.SurfaceControl.Transaction;
+import static android.view.WindowInsets.Type.InsetsType;
import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.window.TaskFragmentAnimationParams.DEFAULT_ANIMATION_BACKGROUND_COLOR;
@@ -173,6 +174,13 @@
*/
protected SparseArray<InsetsSourceProvider> mInsetsSourceProviders = null;
+ /**
+ * The combined excluded insets types (combined mExcludeInsetsTypes and the
+ * mMergedExcludeInsetsTypes from its parent)
+ */
+ protected @InsetsType int mMergedExcludeInsetsTypes = 0;
+ private @InsetsType int mExcludeInsetsTypes = 0;
+
@Nullable
private ArrayMap<IBinder, DeathRecipient> mInsetsOwnerDeathRecipientMap;
@@ -555,6 +563,49 @@
return mControllableInsetProvider;
}
+ /**
+ * Sets the excludeInsetsTypes of this window and updates the mMergedExcludeInsetsTypes of
+ * all child nodes in the hierarchy.
+ *
+ * @param excludeInsetsTypes the excluded {@link InsetsType} that should be set on this
+ * WindowContainer
+ */
+ void setExcludeInsetsTypes(@InsetsType int excludeInsetsTypes) {
+ if (excludeInsetsTypes == mExcludeInsetsTypes) {
+ return;
+ }
+ mExcludeInsetsTypes = excludeInsetsTypes;
+ mergeExcludeInsetsTypesAndNotifyInsetsChanged(
+ mParent != null ? mParent.mMergedExcludeInsetsTypes : 0);
+ }
+
+ private void mergeExcludeInsetsTypesAndNotifyInsetsChanged(
+ @InsetsType int excludeInsetsTypesFromParent) {
+ final ArraySet<WindowState> changedWindows = new ArraySet<>();
+ updateMergedExcludeInsetsTypes(excludeInsetsTypesFromParent, changedWindows);
+ if (getDisplayContent() != null) {
+ getDisplayContent().getInsetsStateController().notifyInsetsChanged(changedWindows);
+ }
+ }
+
+ private void updateMergedExcludeInsetsTypes(
+ @InsetsType int excludeInsetsTypesFromParent, ArraySet<WindowState> changedWindows) {
+ final int newMergedExcludeInsetsTypes = mExcludeInsetsTypes | excludeInsetsTypesFromParent;
+ if (newMergedExcludeInsetsTypes == mMergedExcludeInsetsTypes) {
+ return;
+ }
+ mMergedExcludeInsetsTypes = newMergedExcludeInsetsTypes;
+
+ final WindowState win = asWindowState();
+ if (win != null) {
+ changedWindows.add(win);
+ }
+ // Apply to all children
+ for (int i = mChildren.size() - 1; i >= 0; i--) {
+ final WindowContainer<?> child = mChildren.get(i);
+ child.updateMergedExcludeInsetsTypes(mMergedExcludeInsetsTypes, changedWindows);
+ }
+ }
@Override
final protected WindowContainer getParent() {
@@ -653,6 +704,7 @@
void onParentChanged(ConfigurationContainer newParent, ConfigurationContainer oldParent) {
super.onParentChanged(newParent, oldParent);
if (mParent == null) {
+ mergeExcludeInsetsTypesAndNotifyInsetsChanged(0);
return;
}
@@ -667,6 +719,7 @@
// new parent.
reparentSurfaceControl(getSyncTransaction(), mParent.mSurfaceControl);
}
+ mergeExcludeInsetsTypesAndNotifyInsetsChanged(mParent.mMergedExcludeInsetsTypes);
// Either way we need to ask the parent to assign us a Z-order.
mParent.assignChildLayers();
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index e1e4714..ebf645d 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -6810,30 +6810,36 @@
* @param logLevel Determines the amount of data to be written to the Protobuf.
*/
void dumpDebugLocked(ProtoOutputStream proto, @WindowTracingLogLevel int logLevel) {
- mPolicy.dumpDebug(proto, POLICY);
- mRoot.dumpDebug(proto, ROOT_WINDOW_CONTAINER, logLevel);
- final DisplayContent topFocusedDisplayContent = mRoot.getTopFocusedDisplayContent();
- if (topFocusedDisplayContent.mCurrentFocus != null) {
- topFocusedDisplayContent.mCurrentFocus.writeIdentifierToProto(proto, FOCUSED_WINDOW);
- }
- if (topFocusedDisplayContent.mFocusedApp != null) {
- topFocusedDisplayContent.mFocusedApp.writeNameToProto(proto, FOCUSED_APP);
- }
- final WindowState imeWindow = mRoot.getCurrentInputMethodWindow();
- if (imeWindow != null) {
- imeWindow.writeIdentifierToProto(proto, INPUT_METHOD_WINDOW);
- }
- proto.write(DISPLAY_FROZEN, mDisplayFrozen);
- proto.write(FOCUSED_DISPLAY_ID, topFocusedDisplayContent.getDisplayId());
- proto.write(HARD_KEYBOARD_AVAILABLE, mHardKeyboardAvailable);
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "dumpDebugLocked");
+ try {
+ mPolicy.dumpDebug(proto, POLICY);
+ mRoot.dumpDebug(proto, ROOT_WINDOW_CONTAINER, logLevel);
+ final DisplayContent topFocusedDisplayContent = mRoot.getTopFocusedDisplayContent();
+ if (topFocusedDisplayContent.mCurrentFocus != null) {
+ topFocusedDisplayContent.mCurrentFocus
+ .writeIdentifierToProto(proto, FOCUSED_WINDOW);
+ }
+ if (topFocusedDisplayContent.mFocusedApp != null) {
+ topFocusedDisplayContent.mFocusedApp.writeNameToProto(proto, FOCUSED_APP);
+ }
+ final WindowState imeWindow = mRoot.getCurrentInputMethodWindow();
+ if (imeWindow != null) {
+ imeWindow.writeIdentifierToProto(proto, INPUT_METHOD_WINDOW);
+ }
+ proto.write(DISPLAY_FROZEN, mDisplayFrozen);
+ proto.write(FOCUSED_DISPLAY_ID, topFocusedDisplayContent.getDisplayId());
+ proto.write(HARD_KEYBOARD_AVAILABLE, mHardKeyboardAvailable);
- // This is always true for now since we still update the window frames at the server side.
- // Once we move the window layout to the client side, this can be false when we are waiting
- // for the frames.
- proto.write(WINDOW_FRAMES_VALID, true);
+ // This is always true for now since we still update the window frames at the server
+ // side. Once we move the window layout to the client side, this can be false when we
+ // are waiting for the frames.
+ proto.write(WINDOW_FRAMES_VALID, true);
- // Write the BackNavigationController's state into the protocol buffer
- mAtmService.mBackNavigationController.dumpDebug(proto, BACK_NAVIGATION);
+ // Write the BackNavigationController's state into the protocol buffer
+ mAtmService.mBackNavigationController.dumpDebug(proto, BACK_NAVIGATION);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
}
private void dumpWindowsLocked(PrintWriter pw, boolean dumpAll,
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 09a2bf9..f8d0bc2 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -67,6 +67,7 @@
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_RESTORE_TRANSIENT_ORDER;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_EXCLUDE_INSETS_TYPES;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_IS_TRIMMABLE;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT;
@@ -1449,6 +1450,16 @@
}
break;
}
+ case HIERARCHY_OP_TYPE_SET_EXCLUDE_INSETS_TYPES: {
+ final WindowContainer container = WindowContainer.fromBinder(hop.getContainer());
+ if (container == null) {
+ Slog.e(TAG, "Attempt to operate on unknown or detached container: "
+ + container);
+ break;
+ }
+ container.setExcludeInsetsTypes(hop.getExcludeInsetsTypes());
+ break;
+ }
}
return effects;
}
diff --git a/services/core/java/com/android/server/wm/WindowTracing.java b/services/core/java/com/android/server/wm/WindowTracing.java
index fe26726..6883437 100644
--- a/services/core/java/com/android/server/wm/WindowTracing.java
+++ b/services/core/java/com/android/server/wm/WindowTracing.java
@@ -167,12 +167,7 @@
long token = os.start(WINDOW_MANAGER_SERVICE);
synchronized (mGlobalLock) {
- Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "dumpDebugLocked");
- try {
- mService.dumpDebugLocked(os, logLevel);
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
- }
+ mService.dumpDebugLocked(os, logLevel);
}
os.end(token);
} catch (Exception e) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 4e89b85..2be999f 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -8031,8 +8031,7 @@
"DevicePolicyManager.wipeDataWithReason() from %s, organization-owned? %s",
adminName, calledByProfileOwnerOnOrgOwnedDevice);
- wipeDataNoLock(adminComp, flags, internalReason, wipeReasonForUser, userId,
- calledOnParentInstance, factoryReset);
+ wipeDataNoLock(adminComp, flags, internalReason, wipeReasonForUser, userId, factoryReset);
}
private String getGenericWipeReason(
@@ -8188,17 +8187,16 @@
* factory reset
*/
private void wipeDataNoLock(@Nullable ComponentName admin, int flags, String internalReason,
- String wipeReasonForUser, int userId, boolean calledOnParentInstance,
- @Nullable Boolean factoryReset) {
+ String wipeReasonForUser, int userId, @Nullable Boolean factoryReset) {
wtfIfInLock();
final String adminPackage;
if (admin != null) {
adminPackage = admin.getPackageName();
} else {
- int callerId = mInjector.binderGetCallingUid();
- String[] adminPackages = mInjector.getPackageManager().getPackagesForUid(callerId);
+ int callerUid = mInjector.binderGetCallingUid();
+ String[] adminPackages = mInjector.getPackageManager().getPackagesForUid(callerUid);
Preconditions.checkState(adminPackages.length > 0,
- "Caller %s does not have any associated packages", callerId);
+ "Caller %s does not have any associated packages", callerUid);
adminPackage = adminPackages[0];
}
mInjector.binderWithCleanCallingIdentity(() -> {
@@ -8220,32 +8218,22 @@
throw new SecurityException("Cannot wipe data. " + restriction
+ " restriction is set for user " + userId);
}
- });
- boolean isSystemUser = userId == UserHandle.USER_SYSTEM;
- boolean isMainUser = userId == getMainUserId();
- boolean wipeDevice;
- if (factoryReset == null || !mInjector.isChangeEnabled(EXPLICIT_WIPE_BEHAVIOUR,
- adminPackage,
- userId)) {
- // Legacy mode
- wipeDevice = getHeadlessDeviceOwnerModeForDeviceOwner()
- == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER ? isMainUser : isSystemUser;
- } else {
- // Explicit behaviour
- if (factoryReset) {
- EnforcingAdmin enforcingAdmin = enforcePermissionsAndGetEnforcingAdmin(
- /*admin=*/ null,
- /*permission=*/ new String[]{MANAGE_DEVICE_POLICY_WIPE_DATA,
- MASTER_CLEAR},
- USES_POLICY_WIPE_DATA,
- adminPackage,
- factoryReset ? UserHandle.USER_ALL :
- getAffectedUser(calledOnParentInstance));
- wipeDevice = true;
+ boolean isSystemUser = userId == UserHandle.USER_SYSTEM;
+ boolean isMainUser = userId == getMainUserId();
+ boolean wipeDevice;
+ if (factoryReset == null || !mInjector.isChangeEnabled(EXPLICIT_WIPE_BEHAVIOUR,
+ adminPackage,
+ userId)) {
+ // Legacy mode
+ wipeDevice = getHeadlessDeviceOwnerModeForDeviceOwner()
+ == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER ? isMainUser : isSystemUser;
} else {
- mInjector.binderWithCleanCallingIdentity(() -> {
- Preconditions.checkCallAuthorization(!isSystemUser,
+ // Explicit behaviour
+ if (factoryReset) {
+ wipeDevice = true;
+ } else {
+ Preconditions.checkState(!isSystemUser,
"User %s is a system user and cannot be removed", userId);
boolean isLastNonHeadlessUser = getUserInfo(userId).isFull()
&& mUserManager.getAliveUsers().stream()
@@ -8253,13 +8241,11 @@
.noneMatch(UserInfo::isFull);
Preconditions.checkState(!isLastNonHeadlessUser,
"Removing user %s would leave the device without any active users. "
- + "Consider factory resetting the device instead.",
- userId);
- });
- wipeDevice = false;
+ + "Consider factory resetting the device instead.", userId);
+ wipeDevice = false;
+ }
}
- }
- mInjector.binderWithCleanCallingIdentity(() -> {
+
if (wipeDevice) {
forceWipeDeviceNoLock(
(flags & WIPE_EXTERNAL_STORAGE) != 0,
@@ -8600,7 +8586,6 @@
/* reason= */ "reportFailedPasswordAttempt()",
getFailedPasswordAttemptWipeMessage(),
userId,
- /* calledOnParentInstance= */ parent,
// factoryReset=null to enable U- behaviour
/* factoryReset= */ null);
} catch (SecurityException e) {
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.kt b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.kt
index 7aa2ff5..cbca434 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.kt
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.kt
@@ -30,6 +30,7 @@
import android.util.Slog
import android.util.Xml
import com.android.internal.os.BackgroundThread
+import com.android.server.pm.verify.pkg.VerifierController
import com.android.server.testutils.whenever
import com.google.common.truth.Truth.assertThat
import libcore.io.IoUtils
@@ -195,7 +196,8 @@
/* isApplied */ false,
/* stagedSessionErrorCode */ PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE,
/* stagedSessionErrorMessage */ "some error",
- /* preVerifiedDomains */ DomainSet(setOf("com.foo", "com.bar"))
+ /* preVerifiedDomains */ DomainSet(setOf("com.foo", "com.bar")),
+ /* VerifierController */ mock(VerifierController::class.java)
)
}
@@ -249,7 +251,8 @@
mock(StagingManager::class.java),
mTmpDir,
mock(PackageSessionProvider::class.java),
- mock(SilentUpdatePolicy::class.java)
+ mock(SilentUpdatePolicy::class.java),
+ mock(VerifierController::class.java)
)
ret.add(session)
} catch (e: Exception) {
@@ -343,4 +346,4 @@
assertThat(expected.mInitiatingPackageName).isEqualTo(actual.mInitiatingPackageName)
assertThat(expected.mOriginatingPackageName).isEqualTo(actual.mOriginatingPackageName)
}
-}
\ No newline at end of file
+}
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/verify/pkg/VerificationStatusTrackerTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/verify/pkg/VerificationStatusTrackerTest.java
new file mode 100644
index 0000000..fa076db
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/verify/pkg/VerificationStatusTrackerTest.java
@@ -0,0 +1,113 @@
+/*
+ * 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.pm.verify.pkg;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.concurrent.TimeUnit;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class VerificationStatusTrackerTest {
+ private static final String TEST_PACKAGE_NAME = "com.foo";
+ private static final long TEST_REQUEST_START_TIME = 100L;
+ private static final long TEST_TIMEOUT_DURATION_MILLIS = TimeUnit.MINUTES.toMillis(1);
+ private static final long TEST_TIMEOUT_EXTENDED_MILLIS = TimeUnit.MINUTES.toMillis(2);
+ private static final long TEST_MAX_TIMEOUT_DURATION_MILLIS =
+ TimeUnit.MINUTES.toMillis(10);
+
+ @Mock
+ VerifierController.Injector mInjector;
+ private VerificationStatusTracker mTracker;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mInjector.getVerificationRequestTimeoutMillis()).thenReturn(
+ TEST_TIMEOUT_DURATION_MILLIS);
+ when(mInjector.getMaxVerificationExtendedTimeoutMillis()).thenReturn(
+ TEST_MAX_TIMEOUT_DURATION_MILLIS);
+ // Mock time forward as the code continues to check for the current time
+ when(mInjector.getCurrentTimeMillis())
+ .thenReturn(TEST_REQUEST_START_TIME)
+ .thenReturn(TEST_REQUEST_START_TIME + TEST_TIMEOUT_DURATION_MILLIS - 1)
+ .thenReturn(TEST_REQUEST_START_TIME + TEST_TIMEOUT_DURATION_MILLIS)
+ .thenReturn(TEST_REQUEST_START_TIME + TEST_MAX_TIMEOUT_DURATION_MILLIS - 100)
+ .thenReturn(TEST_REQUEST_START_TIME + TEST_MAX_TIMEOUT_DURATION_MILLIS);
+ mTracker = new VerificationStatusTracker(TEST_PACKAGE_NAME, TEST_TIMEOUT_DURATION_MILLIS,
+ TEST_MAX_TIMEOUT_DURATION_MILLIS, mInjector);
+ }
+
+ @Test
+ public void testTimeout() {
+ assertThat(mTracker.getTimeoutTime()).isEqualTo(
+ TEST_REQUEST_START_TIME + TEST_TIMEOUT_DURATION_MILLIS);
+ // It takes two calls to set the timeout, because the timeout time hasn't been reached for
+ // the first calls
+ assertThat(mTracker.isTimeout()).isFalse();
+ assertThat(mTracker.isTimeout()).isTrue();
+ }
+
+ @Test
+ public void testTimeoutExtended() {
+ assertThat(mTracker.getTimeoutTime()).isEqualTo(
+ TEST_REQUEST_START_TIME + TEST_TIMEOUT_DURATION_MILLIS);
+ assertThat(mTracker.extendTimeRemaining(TEST_TIMEOUT_EXTENDED_MILLIS))
+ .isEqualTo(TEST_TIMEOUT_EXTENDED_MILLIS);
+ assertThat(mTracker.getTimeoutTime()).isEqualTo(
+ TEST_REQUEST_START_TIME + TEST_TIMEOUT_DURATION_MILLIS
+ + TEST_TIMEOUT_EXTENDED_MILLIS);
+
+ // It would take 3 calls to set the timeout, because the timeout time hasn't been reached
+ // for the first 2 time checks, but querying the remaining time also does a time check.
+ assertThat(mTracker.isTimeout()).isFalse();
+ assertThat(mTracker.getRemainingTime()).isGreaterThan(0);
+ assertThat(mTracker.isTimeout()).isTrue();
+ assertThat(mTracker.getRemainingTime()).isEqualTo(0);
+ }
+
+ @Test
+ public void testTimeoutExtendedExceedsMax() {
+ assertThat(mTracker.getTimeoutTime()).isEqualTo(
+ TEST_REQUEST_START_TIME + TEST_TIMEOUT_DURATION_MILLIS);
+ assertThat(mTracker.extendTimeRemaining(TEST_MAX_TIMEOUT_DURATION_MILLIS))
+ .isEqualTo(TEST_MAX_TIMEOUT_DURATION_MILLIS - TEST_TIMEOUT_DURATION_MILLIS);
+ assertThat(mTracker.getTimeoutTime()).isEqualTo(
+ TEST_REQUEST_START_TIME + TEST_MAX_TIMEOUT_DURATION_MILLIS);
+ // It takes 4 calls to set the timeout, because the timeout time hasn't been reached for
+ // the first 3 calls
+ assertThat(mTracker.isTimeout()).isFalse();
+ assertThat(mTracker.isTimeout()).isFalse();
+ assertThat(mTracker.isTimeout()).isFalse();
+ assertThat(mTracker.isTimeout()).isTrue();
+ assertThat(mTracker.getRemainingTime()).isEqualTo(0);
+ }
+}
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/verify/pkg/VerifierControllerTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/verify/pkg/VerifierControllerTest.java
new file mode 100644
index 0000000..be094b0
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/verify/pkg/VerifierControllerTest.java
@@ -0,0 +1,502 @@
+/*
+ * 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.pm.verify.pkg;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.expectThrows;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.SharedLibraryInfo;
+import android.content.pm.SigningInfo;
+import android.content.pm.VersionedPackage;
+import android.content.pm.verify.pkg.IVerifierService;
+import android.content.pm.verify.pkg.VerificationSession;
+import android.content.pm.verify.pkg.VerificationStatus;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Message;
+import android.os.PersistableBundle;
+import android.platform.test.annotations.Presubmit;
+import android.util.Pair;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.infra.AndroidFuture;
+import com.android.internal.infra.ServiceConnector;
+import com.android.server.pm.Computer;
+import com.android.server.pm.PackageInstallerSession;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class VerifierControllerTest {
+ private static final int TEST_ID = 100;
+ private static final String TEST_PACKAGE_NAME = "com.foo";
+ private static final ComponentName TEST_VERIFIER_COMPONENT_NAME =
+ new ComponentName("com.verifier", "com.verifier.Service");
+ private static final Uri TEST_PACKAGE_URI = Uri.parse("test://test");
+ private static final SigningInfo TEST_SIGNING_INFO = new SigningInfo();
+ private static final SharedLibraryInfo TEST_SHARED_LIBRARY_INFO1 =
+ new SharedLibraryInfo("sharedLibPath1", TEST_PACKAGE_NAME,
+ Collections.singletonList("path1"), "sharedLib1", 101,
+ SharedLibraryInfo.TYPE_DYNAMIC, new VersionedPackage(TEST_PACKAGE_NAME, 1),
+ null, null, false);
+ private static final SharedLibraryInfo TEST_SHARED_LIBRARY_INFO2 =
+ new SharedLibraryInfo("sharedLibPath2", TEST_PACKAGE_NAME,
+ Collections.singletonList("path2"), "sharedLib2", 102,
+ SharedLibraryInfo.TYPE_DYNAMIC,
+ new VersionedPackage(TEST_PACKAGE_NAME, 2), null, null, false);
+ private static final String TEST_KEY = "test key";
+ private static final String TEST_VALUE = "test value";
+ private static final String TEST_FAILURE_MESSAGE = "verification failed!";
+ private static final long TEST_REQUEST_START_TIME = 0L;
+ private static final long TEST_TIMEOUT_DURATION_MILLIS = TimeUnit.MINUTES.toMillis(1);
+ private static final long TEST_MAX_TIMEOUT_DURATION_MILLIS =
+ TimeUnit.MINUTES.toMillis(10);
+
+ private final ArrayList<SharedLibraryInfo> mTestDeclaredLibraries = new ArrayList<>();
+ private final PersistableBundle mTestExtensionParams = new PersistableBundle();
+ @Mock
+ Context mContext;
+ @Mock
+ Handler mHandler;
+ @Mock
+ VerifierController.Injector mInjector;
+ @Mock
+ ServiceConnector<IVerifierService> mMockServiceConnector;
+ @Mock
+ IVerifierService mMockService;
+ @Mock
+ Computer mSnapshot;
+ Supplier<Computer> mSnapshotSupplier = () -> mSnapshot;
+ @Mock
+ PackageInstallerSession.VerifierCallback mSessionCallback;
+
+ private VerifierController mVerifierController;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mInjector.isVerifierInstalled(any(Computer.class), anyInt())).thenReturn(true);
+ when(mInjector.getRemoteService(
+ any(Computer.class), any(Context.class), anyInt(), any(Handler.class)
+ )).thenReturn(new Pair<>(mMockServiceConnector, TEST_VERIFIER_COMPONENT_NAME));
+ when(mInjector.getVerificationRequestTimeoutMillis()).thenReturn(
+ TEST_TIMEOUT_DURATION_MILLIS);
+ when(mInjector.getMaxVerificationExtendedTimeoutMillis()).thenReturn(
+ TEST_MAX_TIMEOUT_DURATION_MILLIS);
+ // Mock time forward as the code continues to check for the current time
+ when(mInjector.getCurrentTimeMillis())
+ .thenReturn(TEST_REQUEST_START_TIME)
+ .thenReturn(TEST_REQUEST_START_TIME + TEST_TIMEOUT_DURATION_MILLIS + 1);
+ when(mMockServiceConnector.post(any(ServiceConnector.VoidJob.class)))
+ .thenAnswer(
+ i -> {
+ ((ServiceConnector.VoidJob) i.getArguments()[0]).run(mMockService);
+ return new AndroidFuture<>();
+ });
+ when(mMockServiceConnector.run(any(ServiceConnector.VoidJob.class)))
+ .thenAnswer(
+ i -> {
+ ((ServiceConnector.VoidJob) i.getArguments()[0]).run(mMockService);
+ return true;
+ });
+
+ mTestDeclaredLibraries.add(TEST_SHARED_LIBRARY_INFO1);
+ mTestDeclaredLibraries.add(TEST_SHARED_LIBRARY_INFO2);
+ mTestExtensionParams.putString(TEST_KEY, TEST_VALUE);
+
+ mVerifierController = new VerifierController(mContext, mHandler, mInjector);
+ }
+
+ @Test
+ public void testVerifierNotInstalled() {
+ when(mInjector.isVerifierInstalled(any(Computer.class), anyInt())).thenReturn(false);
+ when(mInjector.getRemoteService(
+ any(Computer.class), any(Context.class), anyInt(), any(Handler.class)
+ )).thenReturn(null);
+ assertThat(mVerifierController.isVerifierInstalled(mSnapshotSupplier, 0)).isFalse();
+ assertThat(mVerifierController.bindToVerifierServiceIfNeeded(mSnapshotSupplier, 0))
+ .isFalse();
+ assertThat(mVerifierController.startVerificationSession(
+ mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI,
+ TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback,
+ /* retry= */ false)).isFalse();
+ assertThat(mVerifierController.startVerificationSession(
+ mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI,
+ TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback,
+ /* retry= */ true)).isFalse();
+ verifyZeroInteractions(mSessionCallback);
+ }
+
+ @Test
+ public void testRebindService() {
+ assertThat(mVerifierController.bindToVerifierServiceIfNeeded(mSnapshotSupplier, 0))
+ .isTrue();
+ }
+
+ @Test
+ public void testVerifierAvailableButNotConnected() {
+ assertThat(mVerifierController.isVerifierInstalled(mSnapshotSupplier, 0)).isTrue();
+ when(mInjector.getRemoteService(
+ any(Computer.class), any(Context.class), anyInt(), any(Handler.class)
+ )).thenReturn(null);
+ assertThat(mVerifierController.bindToVerifierServiceIfNeeded(mSnapshotSupplier, 0))
+ .isFalse();
+ // Test that nothing crashes if the verifier is available even though there's no bound
+ mVerifierController.notifyPackageNameAvailable(TEST_PACKAGE_NAME);
+ mVerifierController.notifyVerificationCancelled(TEST_PACKAGE_NAME);
+ mVerifierController.notifyVerificationTimeout(-1);
+ // Since there was no bound, no call is made to the verifier
+ verifyZeroInteractions(mMockService);
+ }
+
+ @Test
+ public void testUnbindService() throws Exception {
+ ArgumentCaptor<ServiceConnector.ServiceLifecycleCallbacks> captor = ArgumentCaptor.forClass(
+ ServiceConnector.ServiceLifecycleCallbacks.class);
+ assertThat(mVerifierController.bindToVerifierServiceIfNeeded(mSnapshotSupplier, 0))
+ .isTrue();
+ verify(mMockServiceConnector).setServiceLifecycleCallbacks(captor.capture());
+ ServiceConnector.ServiceLifecycleCallbacks<IVerifierService> callbacks = captor.getValue();
+ assertThat(mVerifierController.startVerificationSession(
+ mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI,
+ TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback,
+ /* retry= */ false)).isTrue();
+ verify(mMockService, times(1)).onVerificationRequired(any(VerificationSession.class));
+ callbacks.onBinderDied();
+ // Test that nothing crashes if the service connection is lost
+ assertThat(mVerifierController.isVerifierInstalled(mSnapshotSupplier, 0)).isTrue();
+ mVerifierController.notifyPackageNameAvailable(TEST_PACKAGE_NAME);
+ mVerifierController.notifyVerificationCancelled(TEST_PACKAGE_NAME);
+ mVerifierController.notifyVerificationTimeout(TEST_ID);
+ verifyNoMoreInteractions(mMockService);
+ assertThat(mVerifierController.startVerificationSession(
+ mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI,
+ TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback,
+ /* retry= */ false)).isTrue();
+ assertThat(mVerifierController.startVerificationSession(
+ mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI,
+ TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback,
+ /* retry= */ true)).isTrue();
+ mVerifierController.notifyVerificationTimeout(TEST_ID);
+ verify(mMockService, times(1)).onVerificationTimeout(eq(TEST_ID));
+ }
+
+ @Test
+ public void testNotifyPackageNameAvailable() throws Exception {
+ assertThat(mVerifierController.startVerificationSession(
+ mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI,
+ TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback,
+ /* retry= */ false)).isTrue();
+ mVerifierController.notifyPackageNameAvailable(TEST_PACKAGE_NAME);
+ verify(mMockService).onPackageNameAvailable(eq(TEST_PACKAGE_NAME));
+ }
+
+ @Test
+ public void testNotifyVerificationCancelled() throws Exception {
+ assertThat(mVerifierController.startVerificationSession(
+ mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI,
+ TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback,
+ /* retry= */ false)).isTrue();
+ mVerifierController.notifyVerificationCancelled(TEST_PACKAGE_NAME);
+ verify(mMockService).onVerificationCancelled(eq(TEST_PACKAGE_NAME));
+ }
+
+ @Test
+ public void testStartVerificationSession() throws Exception {
+ ArgumentCaptor<VerificationSession> captor =
+ ArgumentCaptor.forClass(VerificationSession.class);
+ assertThat(mVerifierController.startVerificationSession(
+ mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI,
+ TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback,
+ /* retry= */ false)).isTrue();
+ verify(mMockService).onVerificationRequired(captor.capture());
+ VerificationSession session = captor.getValue();
+ assertThat(session.getId()).isEqualTo(TEST_ID);
+ assertThat(session.getInstallSessionId()).isEqualTo(TEST_ID);
+ assertThat(session.getPackageName()).isEqualTo(TEST_PACKAGE_NAME);
+ assertThat(session.getStagedPackageUri()).isEqualTo(TEST_PACKAGE_URI);
+ assertThat(session.getSigningInfo().getSigningDetails())
+ .isEqualTo(TEST_SIGNING_INFO.getSigningDetails());
+ List<SharedLibraryInfo> declaredLibraries = session.getDeclaredLibraries();
+ // SharedLibraryInfo doesn't have a "equals" method, so we have to check it indirectly
+ assertThat(declaredLibraries.getFirst().toString())
+ .isEqualTo(TEST_SHARED_LIBRARY_INFO1.toString());
+ assertThat(declaredLibraries.get(1).toString())
+ .isEqualTo(TEST_SHARED_LIBRARY_INFO2.toString());
+ // We can't directly test with PersistableBundle.equals() because the parceled bundle's
+ // structure is different, but all the key/value pairs should be preserved as before.
+ assertThat(session.getExtensionParams().getString(TEST_KEY))
+ .isEqualTo(mTestExtensionParams.getString(TEST_KEY));
+ }
+
+ @Test
+ public void testNotifyVerificationRetry() throws Exception {
+ ArgumentCaptor<VerificationSession> captor =
+ ArgumentCaptor.forClass(VerificationSession.class);
+ assertThat(mVerifierController.startVerificationSession(
+ mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI,
+ TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback,
+ /* retry= */ true)).isTrue();
+ verify(mMockService).onVerificationRetry(captor.capture());
+ VerificationSession session = captor.getValue();
+ assertThat(session.getId()).isEqualTo(TEST_ID);
+ assertThat(session.getInstallSessionId()).isEqualTo(TEST_ID);
+ assertThat(session.getPackageName()).isEqualTo(TEST_PACKAGE_NAME);
+ assertThat(session.getStagedPackageUri()).isEqualTo(TEST_PACKAGE_URI);
+ assertThat(session.getSigningInfo().getSigningDetails())
+ .isEqualTo(TEST_SIGNING_INFO.getSigningDetails());
+ List<SharedLibraryInfo> declaredLibraries = session.getDeclaredLibraries();
+ // SharedLibraryInfo doesn't have a "equals" method, so we have to check it indirectly
+ assertThat(declaredLibraries.getFirst().toString())
+ .isEqualTo(TEST_SHARED_LIBRARY_INFO1.toString());
+ assertThat(declaredLibraries.get(1).toString())
+ .isEqualTo(TEST_SHARED_LIBRARY_INFO2.toString());
+ // We can't directly test with PersistableBundle.equals() because the parceled bundle's
+ // structure is different, but all the key/value pairs should be preserved as before.
+ assertThat(session.getExtensionParams().getString(TEST_KEY))
+ .isEqualTo(mTestExtensionParams.getString(TEST_KEY));
+ }
+
+ @Test
+ public void testNotifyVerificationTimeout() throws Exception {
+ assertThat(mVerifierController.startVerificationSession(
+ mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI,
+ TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback,
+ /* retry= */ true)).isTrue();
+ mVerifierController.notifyVerificationTimeout(TEST_ID);
+ verify(mMockService).onVerificationTimeout(eq(TEST_ID));
+ }
+
+ @Test
+ public void testRequestTimeout() {
+ // Let the mock handler set request to TIMEOUT, immediately after the request is sent.
+ // We can't mock postDelayed because it's final, but we can mock the method it calls.
+ when(mHandler.sendMessageAtTime(any(Message.class), anyLong())).thenAnswer(
+ i -> {
+ ((Message) i.getArguments()[0]).getCallback().run();
+ return true;
+ });
+ ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+ assertThat(mVerifierController.startVerificationSession(
+ mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI,
+ TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback,
+ /* retry= */ false)).isTrue();
+ verify(mHandler, times(1)).sendMessageAtTime(any(Message.class), anyLong());
+ verify(mSessionCallback, times(1)).onTimeout();
+ verify(mInjector, times(2)).getCurrentTimeMillis();
+ verify(mInjector, times(1)).stopTimeoutCountdown(eq(mHandler), any());
+ }
+
+ @Test
+ public void testRequestTimeoutWithRetryPass() throws Exception {
+ // Only let the first request timeout and let the second one pass
+ when(mHandler.sendMessageAtTime(any(Message.class), anyLong())).thenAnswer(
+ i -> {
+ ((Message) i.getArguments()[0]).getCallback().run();
+ return true;
+ })
+ .thenAnswer(i -> true);
+ assertThat(mVerifierController.startVerificationSession(
+ mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI,
+ TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback,
+ /* retry= */ false)).isTrue();
+ verify(mHandler, times(1)).sendMessageAtTime(any(Message.class), anyLong());
+ verify(mSessionCallback, times(1)).onTimeout();
+ verify(mInjector, times(2)).getCurrentTimeMillis();
+ verify(mInjector, times(1)).stopTimeoutCountdown(eq(mHandler), any());
+ // Then retry
+ ArgumentCaptor<VerificationSession> captor =
+ ArgumentCaptor.forClass(VerificationSession.class);
+ assertThat(mVerifierController.startVerificationSession(
+ mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI,
+ TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback,
+ /* retry= */ true)).isTrue();
+ verify(mMockService).onVerificationRetry(captor.capture());
+ VerificationSession session = captor.getValue();
+ VerificationStatus status = new VerificationStatus.Builder().setVerified(true).build();
+ session.reportVerificationComplete(status);
+ verify(mSessionCallback, times(1)).onVerificationCompleteReceived(
+ eq(status), eq(null));
+ verify(mInjector, times(2)).stopTimeoutCountdown(eq(mHandler), any());
+ }
+
+ @Test
+ public void testRequestIncomplete() throws Exception {
+ ArgumentCaptor<VerificationSession> captor =
+ ArgumentCaptor.forClass(VerificationSession.class);
+ assertThat(mVerifierController.startVerificationSession(
+ mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI,
+ TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback,
+ /* retry= */ false)).isTrue();
+ verify(mMockService).onVerificationRequired(captor.capture());
+ VerificationSession session = captor.getValue();
+ session.reportVerificationIncomplete(VerificationSession.VERIFICATION_INCOMPLETE_UNKNOWN);
+ verify(mSessionCallback, times(1)).onVerificationIncompleteReceived(
+ eq(VerificationSession.VERIFICATION_INCOMPLETE_UNKNOWN));
+ verify(mInjector, times(1)).stopTimeoutCountdown(eq(mHandler), any());
+ }
+
+ @Test
+ public void testRequestCompleteWithSuccessWithExtensionResponse() throws Exception {
+ ArgumentCaptor<VerificationSession> captor =
+ ArgumentCaptor.forClass(VerificationSession.class);
+ assertThat(mVerifierController.startVerificationSession(
+ mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI,
+ TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback,
+ /* retry= */ false)).isTrue();
+ verify(mMockService).onVerificationRequired(captor.capture());
+ VerificationSession session = captor.getValue();
+ VerificationStatus status = new VerificationStatus.Builder().setVerified(true).build();
+ PersistableBundle bundle = new PersistableBundle();
+ session.reportVerificationComplete(status, bundle);
+ verify(mSessionCallback, times(1)).onVerificationCompleteReceived(
+ eq(status), eq(bundle));
+ verify(mInjector, times(1)).stopTimeoutCountdown(eq(mHandler), any());
+ }
+
+ @Test
+ public void testRequestCompleteWithFailure() throws Exception {
+ ArgumentCaptor<VerificationSession> captor =
+ ArgumentCaptor.forClass(VerificationSession.class);
+ assertThat(mVerifierController.startVerificationSession(
+ mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI,
+ TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback,
+ /* retry= */ false)).isTrue();
+ verify(mMockService).onVerificationRequired(captor.capture());
+ VerificationSession session = captor.getValue();
+ VerificationStatus status = new VerificationStatus.Builder()
+ .setVerified(false)
+ .setFailureMessage(TEST_FAILURE_MESSAGE)
+ .build();
+ session.reportVerificationComplete(status);
+ verify(mSessionCallback, times(1)).onVerificationCompleteReceived(
+ eq(status), eq(null));
+ verify(mInjector, times(1)).stopTimeoutCountdown(eq(mHandler), any());
+ }
+
+ @Test
+ public void testRepeatedRequestCompleteShouldThrow() throws Exception {
+ ArgumentCaptor<VerificationSession> captor =
+ ArgumentCaptor.forClass(VerificationSession.class);
+ assertThat(mVerifierController.startVerificationSession(
+ mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI,
+ TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback,
+ /* retry= */ false)).isTrue();
+ verify(mMockService).onVerificationRequired(captor.capture());
+ VerificationSession session = captor.getValue();
+ VerificationStatus status = new VerificationStatus.Builder().setVerified(true).build();
+ session.reportVerificationComplete(status);
+ // getters should throw after the report
+ expectThrows(IllegalStateException.class, () -> session.getTimeoutTime());
+ // Report again should fail with exception
+ expectThrows(IllegalStateException.class, () -> session.reportVerificationComplete(status));
+ }
+
+ @Test
+ public void testExtendTimeRemaining() throws Exception {
+ ArgumentCaptor<VerificationSession> captor =
+ ArgumentCaptor.forClass(VerificationSession.class);
+ mVerifierController.startVerificationSession(
+ mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI,
+ TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback,
+ /* retry= */ false);
+ verify(mMockService).onVerificationRequired(captor.capture());
+ VerificationSession session = captor.getValue();
+ final long initialTimeoutTime = TEST_REQUEST_START_TIME + TEST_TIMEOUT_DURATION_MILLIS;
+ assertThat(session.getTimeoutTime()).isEqualTo(initialTimeoutTime);
+ final long extendTimeMillis = TEST_TIMEOUT_DURATION_MILLIS;
+ assertThat(session.extendTimeRemaining(extendTimeMillis)).isEqualTo(extendTimeMillis);
+ assertThat(session.getTimeoutTime()).isEqualTo(initialTimeoutTime + extendTimeMillis);
+ }
+
+ @Test
+ public void testExtendTimeExceedsMax() throws Exception {
+ ArgumentCaptor<VerificationSession> captor =
+ ArgumentCaptor.forClass(VerificationSession.class);
+ mVerifierController.startVerificationSession(
+ mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI,
+ TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback,
+ /* retry= */ false);
+ verify(mMockService).onVerificationRequired(captor.capture());
+ VerificationSession session = captor.getValue();
+ final long initialTimeoutTime = TEST_REQUEST_START_TIME + TEST_TIMEOUT_DURATION_MILLIS;
+ final long maxTimeoutTime = TEST_REQUEST_START_TIME + TEST_MAX_TIMEOUT_DURATION_MILLIS;
+ assertThat(session.getTimeoutTime()).isEqualTo(initialTimeoutTime);
+ final long extendTimeMillis = TEST_MAX_TIMEOUT_DURATION_MILLIS;
+ assertThat(session.extendTimeRemaining(extendTimeMillis)).isEqualTo(
+ TEST_MAX_TIMEOUT_DURATION_MILLIS - TEST_TIMEOUT_DURATION_MILLIS);
+ assertThat(session.getTimeoutTime()).isEqualTo(maxTimeoutTime);
+ }
+
+ @Test
+ public void testTimeoutChecksMultipleTimes() {
+ // Mock message handling
+ when(mHandler.sendMessageAtTime(any(Message.class), anyLong())).thenAnswer(
+ i -> {
+ ((Message) i.getArguments()[0]).getCallback().run();
+ return true;
+ });
+ // Mock time forward as the code continues to check for the current time
+ when(mInjector.getCurrentTimeMillis())
+ // First called when the tracker is created
+ .thenReturn(TEST_REQUEST_START_TIME)
+ // Then mock the first timeout check when the timeout time isn't reached yet
+ .thenReturn(TEST_REQUEST_START_TIME + TEST_TIMEOUT_DURATION_MILLIS - 1000)
+ // Then mock the same time used to check the remaining time
+ .thenReturn(TEST_REQUEST_START_TIME + TEST_TIMEOUT_DURATION_MILLIS - 1000)
+ // Then mock the second timeout check when the timeout time isn't reached yet
+ .thenReturn(TEST_REQUEST_START_TIME + TEST_TIMEOUT_DURATION_MILLIS - 100)
+ // Then mock the same time used to check the remaining time
+ .thenReturn(TEST_REQUEST_START_TIME + TEST_TIMEOUT_DURATION_MILLIS - 100)
+ // Then mock the third timeout check when the timeout time has been reached
+ .thenReturn(TEST_REQUEST_START_TIME + TEST_TIMEOUT_DURATION_MILLIS + 1);
+ mVerifierController.startVerificationSession(
+ mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI,
+ TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback,
+ /* retry= */ false);
+ verify(mHandler, times(3)).sendMessageAtTime(any(Message.class), anyLong());
+ verify(mInjector, times(6)).getCurrentTimeMillis();
+ verify(mSessionCallback, times(1)).onTimeout();
+ }
+}
diff --git a/services/tests/appfunctions/Android.bp b/services/tests/appfunctions/Android.bp
index 9560ec9..c841643 100644
--- a/services/tests/appfunctions/Android.bp
+++ b/services/tests/appfunctions/Android.bp
@@ -36,6 +36,7 @@
"androidx.test.core",
"androidx.test.runner",
"androidx.test.ext.truth",
+ "kotlin-test",
"platform-test-annotations",
"services.appfunctions",
"servicestests-core-utils",
diff --git a/services/tests/appfunctions/src/android/app/appfunctions/AppFunctionRuntimeMetadataTest.kt b/services/tests/appfunctions/src/android/app/appfunctions/AppFunctionRuntimeMetadataTest.kt
index da3e94f..d326204 100644
--- a/services/tests/appfunctions/src/android/app/appfunctions/AppFunctionRuntimeMetadataTest.kt
+++ b/services/tests/appfunctions/src/android/app/appfunctions/AppFunctionRuntimeMetadataTest.kt
@@ -15,8 +15,12 @@
*/
package android.app.appfunctions
+import android.app.appfunctions.AppFunctionManager.APP_FUNCTION_STATE_DEFAULT
+import android.app.appfunctions.AppFunctionManager.APP_FUNCTION_STATE_DISABLED
+import android.app.appfunctions.AppFunctionManager.APP_FUNCTION_STATE_ENABLED
import android.app.appsearch.AppSearchSchema
import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertFailsWith
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@@ -108,32 +112,43 @@
assertThat(runtimeMetadata.packageName).isEqualTo("com.pkg")
assertThat(runtimeMetadata.functionId).isEqualTo("funcId")
- assertThat(runtimeMetadata.enabled).isNull()
+ assertThat(runtimeMetadata.enabled).isEqualTo(APP_FUNCTION_STATE_DEFAULT)
assertThat(runtimeMetadata.appFunctionStaticMetadataQualifiedId)
.isEqualTo("android\$apps-db/app_functions#com.pkg/funcId")
}
@Test
- fun setEnabled_true() {
+ fun setEnabled_enabled() {
val runtimeMetadata =
- AppFunctionRuntimeMetadata.Builder("com.pkg", "funcId").setEnabled(true).build()
+ AppFunctionRuntimeMetadata.Builder("com.pkg", "funcId").setEnabled(APP_FUNCTION_STATE_ENABLED).build()
- assertThat(runtimeMetadata.enabled).isTrue()
+ assertThat(runtimeMetadata.enabled).isEqualTo(APP_FUNCTION_STATE_ENABLED)
}
@Test
- fun setEnabled_false() {
+ fun setEnabled_disabled() {
val runtimeMetadata =
- AppFunctionRuntimeMetadata.Builder("com.pkg", "funcId").setEnabled(false).build()
+ AppFunctionRuntimeMetadata.Builder("com.pkg", "funcId").setEnabled(
+ APP_FUNCTION_STATE_DISABLED).build()
- assertThat(runtimeMetadata.enabled).isFalse()
+ assertThat(runtimeMetadata.enabled).isEqualTo(APP_FUNCTION_STATE_DISABLED)
}
@Test
- fun setEnabled_null() {
+ fun setEnabled_default() {
val runtimeMetadata =
- AppFunctionRuntimeMetadata.Builder("com.pkg", "funcId").setEnabled(null).build()
+ AppFunctionRuntimeMetadata.Builder("com.pkg", "funcId").setEnabled(
+ APP_FUNCTION_STATE_DEFAULT).build()
- assertThat(runtimeMetadata.enabled).isNull()
+ assertThat(runtimeMetadata.enabled).isEqualTo(APP_FUNCTION_STATE_DEFAULT)
+ }
+
+ @Test
+ fun setEnabled_illegalArgument() {
+ val runtimeMetadataBuilder =
+ AppFunctionRuntimeMetadata.Builder("com.pkg", "funcId")
+ assertFailsWith<IllegalArgumentException>("Value of EnabledState is unsupported.") {
+ runtimeMetadataBuilder.setEnabled(-1)
+ }
}
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyTest.kt b/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyTest.kt
index 1fad14b..f3a8d841 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyTest.kt
@@ -16,6 +16,7 @@
package com.android.server.display
+import android.view.Display
import com.google.common.truth.Truth.assertThat
import org.junit.Test
@@ -23,7 +24,7 @@
private val topology = DisplayTopology()
@Test
- fun oneDisplay() {
+ fun addOneDisplay() {
val displayId = 1
val width = 800.0
val height = 600.0
@@ -40,7 +41,7 @@
}
@Test
- fun twoDisplays() {
+ fun addTwoDisplays() {
val displayId1 = 1
val width1 = 800.0
val height1 = 600.0
@@ -71,7 +72,7 @@
}
@Test
- fun manyDisplays() {
+ fun addManyDisplays() {
val displayId1 = 1
val width1 = 800.0
val height1 = 600.0
@@ -118,4 +119,130 @@
assertThat(display.mOffset).isEqualTo(0)
}
}
+
+ @Test
+ fun removeDisplays() {
+ val displayId1 = 1
+ val width1 = 800.0
+ val height1 = 600.0
+
+ val displayId2 = 2
+ val width2 = 1000.0
+ val height2 = 1500.0
+
+ topology.addDisplay(displayId1, width1, height1)
+ topology.addDisplay(displayId2, width2, height2)
+
+ val noOfDisplays = 30
+ for (i in 3..noOfDisplays) {
+ topology.addDisplay(/* displayId= */ i, width1, height1)
+ }
+
+ var removedDisplays = arrayOf(20)
+ topology.removeDisplay(20)
+
+ assertThat(topology.mPrimaryDisplayId).isEqualTo(displayId1)
+
+ var display1 = topology.mRoot!!
+ assertThat(display1.mDisplayId).isEqualTo(displayId1)
+ assertThat(display1.mWidth).isEqualTo(width1)
+ assertThat(display1.mHeight).isEqualTo(height1)
+ assertThat(display1.mChildren).hasSize(1)
+
+ var display2 = display1.mChildren[0]
+ assertThat(display2.mDisplayId).isEqualTo(displayId2)
+ assertThat(display2.mWidth).isEqualTo(width2)
+ assertThat(display2.mHeight).isEqualTo(height2)
+ assertThat(display2.mChildren).hasSize(1)
+ assertThat(display2.mPosition).isEqualTo(
+ DisplayTopology.TreeNode.Position.POSITION_TOP)
+ assertThat(display2.mOffset).isEqualTo(width1 / 2 - width2 / 2)
+
+ var display = display2
+ for (i in 3..noOfDisplays) {
+ if (i in removedDisplays) {
+ continue
+ }
+ display = display.mChildren[0]
+ assertThat(display.mDisplayId).isEqualTo(i)
+ assertThat(display.mWidth).isEqualTo(width1)
+ assertThat(display.mHeight).isEqualTo(height1)
+ // The last display should have no children
+ assertThat(display.mChildren).hasSize(if (i < noOfDisplays) 1 else 0)
+ assertThat(display.mPosition).isEqualTo(
+ DisplayTopology.TreeNode.Position.POSITION_RIGHT)
+ assertThat(display.mOffset).isEqualTo(0)
+ }
+
+ topology.removeDisplay(22)
+ removedDisplays += 22
+ topology.removeDisplay(23)
+ removedDisplays += 23
+ topology.removeDisplay(25)
+ removedDisplays += 25
+
+ assertThat(topology.mPrimaryDisplayId).isEqualTo(displayId1)
+
+ display1 = topology.mRoot!!
+ assertThat(display1.mDisplayId).isEqualTo(displayId1)
+ assertThat(display1.mWidth).isEqualTo(width1)
+ assertThat(display1.mHeight).isEqualTo(height1)
+ assertThat(display1.mChildren).hasSize(1)
+
+ display2 = display1.mChildren[0]
+ assertThat(display2.mDisplayId).isEqualTo(displayId2)
+ assertThat(display2.mWidth).isEqualTo(width2)
+ assertThat(display2.mHeight).isEqualTo(height2)
+ assertThat(display2.mChildren).hasSize(1)
+ assertThat(display2.mPosition).isEqualTo(
+ DisplayTopology.TreeNode.Position.POSITION_TOP)
+ assertThat(display2.mOffset).isEqualTo(width1 / 2 - width2 / 2)
+
+ display = display2
+ for (i in 3..noOfDisplays) {
+ if (i in removedDisplays) {
+ continue
+ }
+ display = display.mChildren[0]
+ assertThat(display.mDisplayId).isEqualTo(i)
+ assertThat(display.mWidth).isEqualTo(width1)
+ assertThat(display.mHeight).isEqualTo(height1)
+ // The last display should have no children
+ assertThat(display.mChildren).hasSize(if (i < noOfDisplays) 1 else 0)
+ assertThat(display.mPosition).isEqualTo(
+ DisplayTopology.TreeNode.Position.POSITION_RIGHT)
+ assertThat(display.mOffset).isEqualTo(0)
+ }
+ }
+
+ @Test
+ fun removeAllDisplays() {
+ val displayId = 1
+ val width = 800.0
+ val height = 600.0
+
+ topology.addDisplay(displayId, width, height)
+ topology.removeDisplay(displayId)
+
+ assertThat(topology.mPrimaryDisplayId).isEqualTo(Display.INVALID_DISPLAY)
+ assertThat(topology.mRoot).isNull()
+ }
+
+ @Test
+ fun removeDisplayThatDoesNotExist() {
+ val displayId = 1
+ val width = 800.0
+ val height = 600.0
+
+ topology.addDisplay(displayId, width, height)
+ topology.removeDisplay(3)
+
+ assertThat(topology.mPrimaryDisplayId).isEqualTo(displayId)
+
+ val display = topology.mRoot!!
+ assertThat(display.mDisplayId).isEqualTo(displayId)
+ assertThat(display.mWidth).isEqualTo(width)
+ assertThat(display.mHeight).isEqualTo(height)
+ assertThat(display.mChildren).isEmpty()
+ }
}
\ No newline at end of file
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index 809e13c..6ccc037 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -29,6 +29,7 @@
import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN;
import static android.content.ContentResolver.SCHEME_CONTENT;
+import static android.content.Intent.FILL_IN_ACTION;
import static android.os.PowerExemptionManager.REASON_DENIED;
import static android.os.UserHandle.USER_ALL;
import static android.util.DebugUtils.valueToString;
@@ -1300,6 +1301,45 @@
.containsExactly(new Pair<>(USER_ID, 42));
}
+ @Test
+ @RequiresFlagsEnabled(android.security.Flags.FLAG_PREVENT_INTENT_REDIRECT)
+ public void testAddCreatorToken() {
+ Intent intent = new Intent();
+ Intent extraIntent = new Intent("EXTRA_INTENT_ACTION");
+ intent.putExtra("EXTRA_INTENT0", extraIntent);
+
+ intent.collectExtraIntentKeys();
+ mAms.addCreatorToken(intent);
+
+ ActivityManagerService.IntentCreatorToken token =
+ (ActivityManagerService.IntentCreatorToken) extraIntent.getCreatorToken();
+ assertThat(token).isNotNull();
+ assertThat(token.getCreatorUid()).isEqualTo(mInjector.getCallingUid());
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.security.Flags.FLAG_PREVENT_INTENT_REDIRECT)
+ public void testAddCreatorTokenForFillingIntent() {
+ Intent intent = new Intent();
+ Intent extraIntent = new Intent("EXTRA_INTENT_ACTION");
+ intent.putExtra("EXTRA_INTENT0", extraIntent);
+ Intent fillinIntent = new Intent();
+ Intent fillinExtraIntent = new Intent("FILLIN_EXTRA_INTENT_ACTION");
+ fillinIntent.putExtra("FILLIN_EXTRA_INTENT0", fillinExtraIntent);
+
+ fillinIntent.collectExtraIntentKeys();
+ intent.fillIn(fillinIntent, FILL_IN_ACTION);
+
+ mAms.addCreatorToken(fillinIntent);
+
+ fillinExtraIntent = intent.getParcelableExtra("FILLIN_EXTRA_INTENT0", Intent.class);
+
+ ActivityManagerService.IntentCreatorToken token =
+ (ActivityManagerService.IntentCreatorToken) fillinExtraIntent.getCreatorToken();
+ assertThat(token).isNotNull();
+ assertThat(token.getCreatorUid()).isEqualTo(mInjector.getCallingUid());
+ }
+
private void verifyWaitingForNetworkStateUpdate(long curProcStateSeq,
long lastNetworkUpdatedProcStateSeq,
final long procStateSeqToWait, boolean expectWait) throws Exception {
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/OWNERS b/services/tests/mockingservicestests/src/com/android/server/pm/OWNERS
index 5181af1..aa22790 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/OWNERS
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/OWNERS
@@ -2,3 +2,4 @@
per-file BackgroundDexOptServiceUnitTest.java = file:/services/core/java/com/android/server/pm/dex/OWNERS
per-file StagingManagerTest.java = dariofreni@google.com, ioffe@google.com, olilan@google.com
+per-file ApexManagerTest.java = dariofreni@google.com, ioffe@google.com, olilan@google.com
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
index 6f9b8df..39acd8d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
@@ -756,7 +756,8 @@
/* isApplied */false,
/* stagedSessionErrorCode */ PackageManager.INSTALL_UNKNOWN,
/* stagedSessionErrorMessage */ "no error",
- /* preVerifiedDomains */ null);
+ /* preVerifiedDomains */ null,
+ /* verifierController */ null);
StagingManager.StagedSession stagedSession = spy(session.mStagedSession);
doReturn(packageName).when(stagedSession).getPackageName();
diff --git a/services/tests/powerservicetests/src/com/android/server/power/LowPowerStandbyControllerTest.java b/services/tests/powerservicetests/src/com/android/server/power/LowPowerStandbyControllerTest.java
index 0e815d0..3e731a3 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/LowPowerStandbyControllerTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/LowPowerStandbyControllerTest.java
@@ -163,6 +163,7 @@
addLocalServiceMock(ActivityManagerInternal.class, mActivityManagerInternalMock);
when(mIPowerManagerMock.isInteractive()).thenReturn(true);
+ when(mIPowerManagerMock.isDisplayInteractive(anyInt())).thenReturn(true);
when(mDeviceConfigWrapperMock.enableCustomPolicy()).thenReturn(true);
when(mDeviceConfigWrapperMock.enableStandbyPorts()).thenReturn(true);
@@ -899,11 +900,13 @@
private void setInteractive() throws Exception {
when(mIPowerManagerMock.isInteractive()).thenReturn(true);
+ when(mIPowerManagerMock.isDisplayInteractive(anyInt())).thenReturn(true);
mContextSpy.sendBroadcast(new Intent(Intent.ACTION_SCREEN_ON));
}
private void setNonInteractive() throws Exception {
when(mIPowerManagerMock.isInteractive()).thenReturn(false);
+ when(mIPowerManagerMock.isDisplayInteractive(anyInt())).thenReturn(false);
mContextSpy.sendBroadcast(new Intent(Intent.ACTION_SCREEN_OFF));
}
diff --git a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
index 54a02cf..e9e21de 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -1659,6 +1659,64 @@
}
@Test
+ public void testIsWakeLockLevelSupported_returnsCorrectValue() {
+ createService();
+ startSystem();
+ PowerManagerService.BinderService service = mService.getBinderServiceInstance();
+ assertThat(service.isWakeLockLevelSupported(PowerManager.PARTIAL_WAKE_LOCK)).isTrue();
+ assertThat(service.isWakeLockLevelSupported(PowerManager.SCREEN_BRIGHT_WAKE_LOCK)).isTrue();
+ assertThat(service.isWakeLockLevelSupported(PowerManager.FULL_WAKE_LOCK)).isTrue();
+ assertThat(service.isWakeLockLevelSupported(PowerManager.DOZE_WAKE_LOCK)).isTrue();
+ assertThat(service.isWakeLockLevelSupported(PowerManager.DRAW_WAKE_LOCK)).isTrue();
+
+ when(mDisplayManagerInternalMock.isProximitySensorAvailable(eq(Display.DEFAULT_DISPLAY)))
+ .thenReturn(true);
+ assertThat(service.isWakeLockLevelSupported(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK))
+ .isTrue();
+
+ when(mDisplayManagerInternalMock.isProximitySensorAvailable(eq(Display.DEFAULT_DISPLAY)))
+ .thenReturn(false);
+ assertThat(service.isWakeLockLevelSupported(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK))
+ .isFalse();
+ }
+
+ @Test
+ public void testIsWakeLockLevelSupportedWithDisplayId_nonDefaultDisplay_returnsCorrectValue() {
+ createService();
+ startSystem();
+ int displayId = Display.DEFAULT_DISPLAY + 1;
+ PowerManagerService.BinderService service = mService.getBinderServiceInstance();
+ assertThat(service.isWakeLockLevelSupportedWithDisplayId(
+ PowerManager.PARTIAL_WAKE_LOCK, displayId))
+ .isTrue();
+ assertThat(service.isWakeLockLevelSupportedWithDisplayId(
+ PowerManager.SCREEN_BRIGHT_WAKE_LOCK, displayId))
+ .isTrue();
+ assertThat(service.isWakeLockLevelSupportedWithDisplayId(
+ PowerManager.FULL_WAKE_LOCK, displayId))
+ .isTrue();
+ assertThat(service.isWakeLockLevelSupportedWithDisplayId(
+ PowerManager.DOZE_WAKE_LOCK, displayId))
+ .isTrue();
+ assertThat(service.isWakeLockLevelSupportedWithDisplayId(
+ PowerManager.DRAW_WAKE_LOCK, displayId))
+ .isTrue();
+
+ when(mDisplayManagerInternalMock.isProximitySensorAvailable(eq(displayId)))
+ .thenReturn(true);
+ assertThat(service.isWakeLockLevelSupportedWithDisplayId(
+ PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, displayId))
+ .isTrue();
+
+ when(mDisplayManagerInternalMock.isProximitySensorAvailable(eq(displayId)))
+ .thenReturn(false);
+ assertThat(service.isWakeLockLevelSupportedWithDisplayId(
+ PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, displayId))
+ .isFalse();
+ }
+
+
+ @Test
public void testWakeLock_affectsProperDisplayGroup() {
final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
@@ -1699,6 +1757,47 @@
}
@Test
+ public void testWakeLock_nonDefaultDisplay_affectsProperDisplayGroup() {
+ final int nonDefaultDisplayId = Display.DEFAULT_DISPLAY + 1;
+ final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+ final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
+ new AtomicReference<>();
+ doAnswer((Answer<Void>) invocation -> {
+ listener.set(invocation.getArgument(0));
+ return null;
+ }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
+ final DisplayInfo info = new DisplayInfo();
+ info.displayGroupId = nonDefaultDisplayGroupId;
+ when(mDisplayManagerInternalMock.getDisplayInfo(nonDefaultDisplayId)).thenReturn(info);
+
+ final String pkg = mContextSpy.getOpPackageName();
+ final Binder token = new Binder();
+ final String tag = "testWakeLock_nonDefaultDisplay_affectsProperDisplayGroup";
+
+ setMinimumScreenOffTimeoutConfig(5);
+ createService();
+ startSystem();
+ listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
+
+ mService.getBinderServiceInstance().acquireWakeLock(token,
+ PowerManager.SCREEN_BRIGHT_WAKE_LOCK, tag, pkg,
+ null /* workSource */, null /* historyTag */, nonDefaultDisplayId, null);
+
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+ assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+ WAKEFULNESS_AWAKE);
+ assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
+ WAKEFULNESS_AWAKE);
+
+ advanceTime(15000);
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+ assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+ WAKEFULNESS_ASLEEP);
+ assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
+ WAKEFULNESS_AWAKE);
+ }
+
+ @Test
public void testInvalidDisplayGroupWakeLock_affectsAllDisplayGroups() {
final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
@@ -2590,14 +2689,15 @@
when(mDisplayManagerInternalMock.getDisplayInfo(nonDefaultDisplay)).thenReturn(info);
createService();
startSystem();
- listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
-
verify(mInvalidateInteractiveCachesMock).call();
+ listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
+ verify(mInvalidateInteractiveCachesMock, times(2)).call();
+
mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_ASLEEP,
mClock.now(), 0, PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0, null, null);
- verify(mInvalidateInteractiveCachesMock, times(2)).call();
+ verify(mInvalidateInteractiveCachesMock, times(3)).call();
}
@Test
@@ -2616,14 +2716,15 @@
when(mDisplayManagerInternalMock.getDisplayInfo(nonDefaultDisplay)).thenReturn(info);
createService();
startSystem();
- listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
-
verify(mInvalidateInteractiveCachesMock).call();
+ listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
+ verify(mInvalidateInteractiveCachesMock, times(2)).call();
+
mService.setWakefulnessLocked(nonDefaultDisplayGroupId, WAKEFULNESS_ASLEEP, mClock.now(),
0, PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0, null, null);
- verify(mInvalidateInteractiveCachesMock, times(2)).call();
+ verify(mInvalidateInteractiveCachesMock, times(3)).call();
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 4d067f6..e6c34ca 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -150,6 +150,7 @@
private static final String NONBLOCKED_APP_PACKAGE_NAME = "com.someapp";
private static final String PERMISSION_CONTROLLER_PACKAGE_NAME =
"com.android.permissioncontroller";
+ private static final String VIRTUAL_DEVICE_OWNER_PACKAGE = "com.android.virtualdevice.test";
private static final String SETTINGS_PACKAGE_NAME = "com.android.settings";
private static final String VENDING_PACKAGE_NAME = "com.android.vending";
private static final String GOOGLE_DIALER_PACKAGE_NAME = "com.google.android.dialer";
@@ -296,11 +297,10 @@
private Intent createRestrictedActivityBlockedIntent(Set<String> displayCategories,
String targetDisplayCategory) {
when(mDisplayManagerInternalMock.createVirtualDisplay(any(), any(), any(), any(),
- eq(NONBLOCKED_APP_PACKAGE_NAME))).thenReturn(DISPLAY_ID_1);
+ eq(VIRTUAL_DEVICE_OWNER_PACKAGE))).thenReturn(DISPLAY_ID_1);
VirtualDisplayConfig config = new VirtualDisplayConfig.Builder("display", 640, 480,
420).setDisplayCategories(displayCategories).build();
- mDeviceImpl.createVirtualDisplay(config, mVirtualDisplayCallback,
- NONBLOCKED_APP_PACKAGE_NAME);
+ mDeviceImpl.createVirtualDisplay(config, mVirtualDisplayCallback);
GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
DISPLAY_ID_1);
doNothing().when(mContext).startActivityAsUser(any(), any(), any());
@@ -1950,7 +1950,7 @@
mVirtualDeviceLog,
new Binder(),
new AttributionSource(
- ownerUid, "com.android.virtualdevice.test", "virtualdevice"),
+ ownerUid, VIRTUAL_DEVICE_OWNER_PACKAGE, "virtualdevice"),
virtualDeviceId,
mInputController,
mCameraAccessController,
@@ -1971,8 +1971,7 @@
private void addVirtualDisplay(VirtualDeviceImpl virtualDevice, int displayId) {
when(mDisplayManagerInternalMock.createVirtualDisplay(any(), eq(mVirtualDisplayCallback),
eq(virtualDevice), any(), any())).thenReturn(displayId);
- virtualDevice.createVirtualDisplay(VIRTUAL_DISPLAY_CONFIG, mVirtualDisplayCallback,
- NONBLOCKED_APP_PACKAGE_NAME);
+ virtualDevice.createVirtualDisplay(VIRTUAL_DISPLAY_CONFIG, mVirtualDisplayCallback);
final String uniqueId = UNIQUE_ID + displayId;
doAnswer(inv -> {
final DisplayInfo displayInfo = new DisplayInfo();
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java
index 50f3a88..5bcddc4 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java
@@ -1,6 +1,10 @@
package com.android.server.locksettings;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
import android.platform.test.annotations.Presubmit;
@@ -56,4 +60,44 @@
mService.initializeSyntheticPassword(userId); // This should allocate a Weaver slot.
assertEquals(Sets.newHashSet(0), mPasswordSlotManager.getUsedSlots());
}
+
+ private int getNumUsedWeaverSlots() {
+ return mPasswordSlotManager.getUsedSlots().size();
+ }
+
+ @Test
+ public void testDisableWeaverOnUnsecuredUsers_false() {
+ final int userId = PRIMARY_USER_ID;
+ when(mResources.getBoolean(eq(
+ com.android.internal.R.bool.config_disableWeaverOnUnsecuredUsers)))
+ .thenReturn(false);
+ assertEquals(0, getNumUsedWeaverSlots());
+ mService.initializeSyntheticPassword(userId);
+ assertEquals(1, getNumUsedWeaverSlots());
+ assertTrue(mService.setLockCredential(newPassword("password"), nonePassword(), userId));
+ assertEquals(1, getNumUsedWeaverSlots());
+ assertTrue(mService.setLockCredential(nonePassword(), newPassword("password"), userId));
+ assertEquals(1, getNumUsedWeaverSlots());
+ }
+
+ @Test
+ public void testDisableWeaverOnUnsecuredUsers_true() {
+ final int userId = PRIMARY_USER_ID;
+ when(mResources.getBoolean(eq(
+ com.android.internal.R.bool.config_disableWeaverOnUnsecuredUsers)))
+ .thenReturn(true);
+ assertEquals(0, getNumUsedWeaverSlots());
+ mService.initializeSyntheticPassword(userId);
+ assertEquals(0, getNumUsedWeaverSlots());
+ assertTrue(mService.setLockCredential(newPassword("password"), nonePassword(), userId));
+ assertEquals(1, getNumUsedWeaverSlots());
+ assertTrue(mService.setLockCredential(nonePassword(), newPassword("password"), userId));
+ assertEquals(0, getNumUsedWeaverSlots());
+ }
+
+ @Test
+ public void testDisableWeaverOnUnsecuredUsers_defaultsToFalse() {
+ assertFalse(mResources.getBoolean(
+ com.android.internal.R.bool.config_disableWeaverOnUnsecuredUsers));
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
index 48bc9d7..3bbc6b2 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
@@ -63,11 +63,13 @@
import android.os.Bundle;
import android.os.IBinder;
import android.os.IInterface;
+import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.platform.test.annotations.EnableFlags;
import android.provider.Settings;
+import android.testing.TestableLooper;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -82,6 +84,7 @@
import com.google.android.collect.Lists;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
@@ -103,6 +106,7 @@
import java.util.Set;
import java.util.concurrent.CountDownLatch;
+
public class ManagedServicesTest extends UiServiceTestCase {
@Mock
@@ -115,6 +119,7 @@
private ManagedServices.UserProfiles mUserProfiles;
@Mock private DevicePolicyManager mDpm;
Object mLock = new Object();
+ private TestableLooper mTestableLooper;
UserInfo mZero = new UserInfo(0, "zero", 0);
UserInfo mTen = new UserInfo(10, "ten", 0);
@@ -142,6 +147,7 @@
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
+ mTestableLooper = new TestableLooper(Looper.getMainLooper());
mContext.setMockPackageManager(mPm);
mContext.addMockSystemService(Context.USER_SERVICE, mUm);
@@ -199,6 +205,11 @@
mIpm, APPROVAL_BY_COMPONENT);
}
+ @After
+ public void tearDown() throws Exception {
+ mTestableLooper.destroy();
+ }
+
@Test
public void testBackupAndRestore_migration() throws Exception {
for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
@@ -888,7 +899,7 @@
return true;
});
- mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>());
+ mockServiceInfoWithMetaData(List.of(cn), service, pm, new ArrayMap<>());
service.addApprovedList("a", 0, true);
service.reregisterService(cn, 0);
@@ -919,7 +930,7 @@
return true;
});
- mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>());
+ mockServiceInfoWithMetaData(List.of(cn), service, pm, new ArrayMap<>());
service.addApprovedList("a", 0, false);
service.reregisterService(cn, 0);
@@ -950,7 +961,7 @@
return true;
});
- mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>());
+ mockServiceInfoWithMetaData(List.of(cn), service, pm, new ArrayMap<>());
service.addApprovedList("a/a", 0, true);
service.reregisterService(cn, 0);
@@ -981,7 +992,7 @@
return true;
});
- mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>());
+ mockServiceInfoWithMetaData(List.of(cn), service, pm, new ArrayMap<>());
service.addApprovedList("a/a", 0, false);
service.reregisterService(cn, 0);
@@ -1053,6 +1064,77 @@
}
@Test
+ public void registerService_bindingDied_rebindIsClearedOnUserSwitch() throws Exception {
+ Context context = mock(Context.class);
+ PackageManager pm = mock(PackageManager.class);
+ ApplicationInfo ai = new ApplicationInfo();
+ ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
+
+ when(context.getPackageName()).thenReturn(mPkg);
+ when(context.getUserId()).thenReturn(mUser.getIdentifier());
+ when(context.getPackageManager()).thenReturn(pm);
+ when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai);
+
+ ManagedServices service = new TestManagedServices(context, mLock, mUserProfiles, mIpm,
+ APPROVAL_BY_PACKAGE);
+ service = spy(service);
+ ComponentName cn = ComponentName.unflattenFromString("a/a");
+
+ // Trigger onBindingDied for component when registering
+ // => will schedule a rebind in 10 seconds
+ when(context.bindServiceAsUser(any(), any(), anyInt(), any())).thenAnswer(invocation -> {
+ Object[] args = invocation.getArguments();
+ ServiceConnection sc = (ServiceConnection) args[1];
+ sc.onBindingDied(cn);
+ return true;
+ });
+ service.registerService(cn, 0);
+ assertThat(service.isBound(cn, 0)).isFalse();
+
+ // Switch to user 10
+ service.onUserSwitched(10);
+
+ // Check that the scheduled rebind for user 0 was cleared
+ mTestableLooper.moveTimeForward(ManagedServices.ON_BINDING_DIED_REBIND_DELAY_MS);
+ mTestableLooper.processAllMessages();
+ verify(service, never()).reregisterService(any(), anyInt());
+ }
+
+ @Test
+ public void registerService_bindingDied_rebindIsExecutedAfterTimeout() throws Exception {
+ Context context = mock(Context.class);
+ PackageManager pm = mock(PackageManager.class);
+ ApplicationInfo ai = new ApplicationInfo();
+ ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
+
+ when(context.getPackageName()).thenReturn(mPkg);
+ when(context.getUserId()).thenReturn(mUser.getIdentifier());
+ when(context.getPackageManager()).thenReturn(pm);
+ when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai);
+
+ ManagedServices service = new TestManagedServices(context, mLock, mUserProfiles, mIpm,
+ APPROVAL_BY_PACKAGE);
+ service = spy(service);
+ ComponentName cn = ComponentName.unflattenFromString("a/a");
+
+ // Trigger onBindingDied for component when registering
+ // => will schedule a rebind in 10 seconds
+ when(context.bindServiceAsUser(any(), any(), anyInt(), any())).thenAnswer(invocation -> {
+ Object[] args = invocation.getArguments();
+ ServiceConnection sc = (ServiceConnection) args[1];
+ sc.onBindingDied(cn);
+ return true;
+ });
+ service.registerService(cn, 0);
+ assertThat(service.isBound(cn, 0)).isFalse();
+
+ // Check that the scheduled rebind is run
+ mTestableLooper.moveTimeForward(ManagedServices.ON_BINDING_DIED_REBIND_DELAY_MS);
+ mTestableLooper.processAllMessages();
+ verify(service, times(1)).reregisterService(eq(cn), eq(0));
+ }
+
+ @Test
public void testPackageUninstall_packageNoLongerInApprovedList() throws Exception {
for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
@@ -1211,6 +1293,64 @@
}
@Test
+ public void testUpgradeAppNoIntentFilterNoRebind() throws Exception {
+ Context context = spy(getContext());
+ doReturn(true).when(context).bindServiceAsUser(any(), any(), anyInt(), any());
+
+ ManagedServices service = new TestManagedServices(context, mLock, mUserProfiles,
+ mIpm, APPROVAL_BY_COMPONENT);
+
+ List<String> packages = new ArrayList<>();
+ packages.add("package");
+ addExpectedServices(service, packages, 0);
+
+ final ComponentName unapprovedComponent = ComponentName.unflattenFromString("package/C1");
+ final ComponentName approvedComponent = ComponentName.unflattenFromString("package/C2");
+
+ // Both components are approved initially
+ mExpectedPrimaryComponentNames.clear();
+ mExpectedPrimaryPackages.clear();
+ mExpectedPrimaryComponentNames.put(0, "package/C1:package/C2");
+ mExpectedSecondaryComponentNames.clear();
+ mExpectedSecondaryPackages.clear();
+
+ loadXml(service);
+
+ //Component package/C1 loses serviceInterface intent filter
+ ManagedServices.Config config = service.getConfig();
+ when(mPm.queryIntentServicesAsUser(any(), anyInt(), anyInt()))
+ .thenAnswer(new Answer<List<ResolveInfo>>() {
+ @Override
+ public List<ResolveInfo> answer(InvocationOnMock invocationOnMock)
+ throws Throwable {
+ Object[] args = invocationOnMock.getArguments();
+ Intent invocationIntent = (Intent) args[0];
+ if (invocationIntent != null) {
+ if (invocationIntent.getAction().equals(config.serviceInterface)
+ && packages.contains(invocationIntent.getPackage())) {
+ List<ResolveInfo> dummyServices = new ArrayList<>();
+ ResolveInfo resolveInfo = new ResolveInfo();
+ ServiceInfo serviceInfo = new ServiceInfo();
+ serviceInfo.packageName = invocationIntent.getPackage();
+ serviceInfo.name = approvedComponent.getClassName();
+ serviceInfo.permission = service.getConfig().bindPermission;
+ resolveInfo.serviceInfo = serviceInfo;
+ dummyServices.add(resolveInfo);
+ return dummyServices;
+ }
+ }
+ return new ArrayList<>();
+ }
+ });
+
+ // Trigger package update
+ service.onPackagesChanged(false, new String[]{"package"}, new int[]{0});
+
+ assertFalse(service.isComponentEnabledForCurrentProfiles(unapprovedComponent));
+ assertTrue(service.isComponentEnabledForCurrentProfiles(approvedComponent));
+ }
+
+ @Test
public void testSetPackageOrComponentEnabled() throws Exception {
for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
@@ -1223,6 +1363,21 @@
"user10package1/K", "user10.3/Component", "user10package2/L",
"user10.4/Component"}));
+ // mock permissions for services
+ PackageManager pm = mock(PackageManager.class);
+ when(getContext().getPackageManager()).thenReturn(pm);
+ List<ComponentName> enabledComponents = List.of(
+ ComponentName.unflattenFromString("package/Comp"),
+ ComponentName.unflattenFromString("package/C2"),
+ ComponentName.unflattenFromString("again/M4"),
+ ComponentName.unflattenFromString("user10package/B"),
+ ComponentName.unflattenFromString("user10/Component"),
+ ComponentName.unflattenFromString("user10package1/K"),
+ ComponentName.unflattenFromString("user10.3/Component"),
+ ComponentName.unflattenFromString("user10package2/L"),
+ ComponentName.unflattenFromString("user10.4/Component"));
+ mockServiceInfoWithMetaData(enabledComponents, service, pm, new ArrayMap<>());
+
for (int userId : expectedEnabled.keySet()) {
ArrayList<String> expectedForUser = expectedEnabled.get(userId);
for (int i = 0; i < expectedForUser.size(); i++) {
@@ -1944,7 +2099,7 @@
metaDataAutobindAllow.putBoolean(META_DATA_DEFAULT_AUTOBIND, true);
metaDatas.put(cn_allowed, metaDataAutobindAllow);
- mockServiceInfoWithMetaData(componentNames, service, metaDatas);
+ mockServiceInfoWithMetaData(componentNames, service, pm, metaDatas);
service.addApprovedList(cn_allowed.flattenToString(), 0, true);
service.addApprovedList(cn_disallowed.flattenToString(), 0, true);
@@ -1989,7 +2144,7 @@
metaDataAutobindDisallow.putBoolean(META_DATA_DEFAULT_AUTOBIND, false);
metaDatas.put(cn_disallowed, metaDataAutobindDisallow);
- mockServiceInfoWithMetaData(componentNames, service, metaDatas);
+ mockServiceInfoWithMetaData(componentNames, service, pm, metaDatas);
service.addApprovedList(cn_disallowed.flattenToString(), 0, true);
@@ -2028,7 +2183,7 @@
metaDataAutobindDisallow.putBoolean(META_DATA_DEFAULT_AUTOBIND, false);
metaDatas.put(cn_disallowed, metaDataAutobindDisallow);
- mockServiceInfoWithMetaData(componentNames, service, metaDatas);
+ mockServiceInfoWithMetaData(componentNames, service, pm, metaDatas);
service.addApprovedList(cn_disallowed.flattenToString(), 0, true);
@@ -2099,8 +2254,8 @@
}
private void mockServiceInfoWithMetaData(List<ComponentName> componentNames,
- ManagedServices service, ArrayMap<ComponentName, Bundle> metaDatas)
- throws RemoteException {
+ ManagedServices service, PackageManager packageManager,
+ ArrayMap<ComponentName, Bundle> metaDatas) throws RemoteException {
when(mIpm.getServiceInfo(any(), anyLong(), anyInt())).thenAnswer(
(Answer<ServiceInfo>) invocation -> {
ComponentName invocationCn = invocation.getArgument(0);
@@ -2115,6 +2270,39 @@
return null;
}
);
+
+ // add components to queryIntentServicesAsUser response
+ final List<String> packages = new ArrayList<>();
+ for (ComponentName cn: componentNames) {
+ packages.add(cn.getPackageName());
+ }
+ ManagedServices.Config config = service.getConfig();
+ when(packageManager.queryIntentServicesAsUser(any(), anyInt(), anyInt())).
+ thenAnswer(new Answer<List<ResolveInfo>>() {
+ @Override
+ public List<ResolveInfo> answer(InvocationOnMock invocationOnMock)
+ throws Throwable {
+ Object[] args = invocationOnMock.getArguments();
+ Intent invocationIntent = (Intent) args[0];
+ if (invocationIntent != null) {
+ if (invocationIntent.getAction().equals(config.serviceInterface)
+ && packages.contains(invocationIntent.getPackage())) {
+ List<ResolveInfo> dummyServices = new ArrayList<>();
+ for (ComponentName cn: componentNames) {
+ ResolveInfo resolveInfo = new ResolveInfo();
+ ServiceInfo serviceInfo = new ServiceInfo();
+ serviceInfo.packageName = invocationIntent.getPackage();
+ serviceInfo.name = cn.getClassName();
+ serviceInfo.permission = service.getConfig().bindPermission;
+ resolveInfo.serviceInfo = serviceInfo;
+ dummyServices.add(resolveInfo);
+ }
+ return dummyServices;
+ }
+ }
+ return new ArrayList<>();
+ }
+ });
}
private void resetComponentsAndPackages() {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java
index 18ca09b..bf0586c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java
@@ -18,11 +18,21 @@
import static android.app.NotificationManager.IMPORTANCE_LOW;
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
+import static android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION;
+import static android.service.notification.Flags.FLAG_NOTIFICATION_FORCE_GROUPING;
+
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.PendingIntent;
@@ -30,12 +40,16 @@
import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.Adjustment;
import android.service.notification.SnoozeCriterion;
import android.service.notification.StatusBarNotification;
import com.android.server.UiServiceTestCase;
+import org.junit.Rule;
import org.junit.Test;
import java.util.ArrayList;
@@ -43,6 +57,9 @@
public class NotificationAdjustmentExtractorTest extends UiServiceTestCase {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
+
@Test
public void testExtractsAdjustment() {
NotificationAdjustmentExtractor extractor = new NotificationAdjustmentExtractor();
@@ -111,6 +128,44 @@
assertEquals(snoozeCriteria, r.getSnoozeCriteria());
}
+ @Test
+ @EnableFlags({FLAG_NOTIFICATION_CLASSIFICATION, FLAG_NOTIFICATION_FORCE_GROUPING})
+ public void testClassificationAdjustments_triggerRegrouping() {
+ GroupHelper groupHelper = mock(GroupHelper.class);
+ NotificationAdjustmentExtractor extractor = new NotificationAdjustmentExtractor();
+ extractor.setGroupHelper(groupHelper);
+
+ NotificationRecord r = generateRecord();
+
+ Bundle classificationAdj = new Bundle();
+ classificationAdj.putParcelable(Adjustment.KEY_TYPE, mock(NotificationChannel.class));
+ Adjustment adjustment = new Adjustment("pkg", r.getKey(), classificationAdj, "", 0);
+ r.addAdjustment(adjustment);
+
+ RankingReconsideration regroupingTask = extractor.process(r);
+ assertThat(regroupingTask).isNotNull();
+ regroupingTask.applyChangesLocked(r);
+ verify(groupHelper, times(1)).onChannelUpdated(r);
+ }
+
+ @Test
+ @DisableFlags({FLAG_NOTIFICATION_CLASSIFICATION, FLAG_NOTIFICATION_FORCE_GROUPING})
+ public void testClassificationAdjustments_notTriggerRegrouping_flagsDisabled() {
+ GroupHelper groupHelper = mock(GroupHelper.class);
+ NotificationAdjustmentExtractor extractor = new NotificationAdjustmentExtractor();
+ extractor.setGroupHelper(groupHelper);
+
+ NotificationRecord r = generateRecord();
+
+ Bundle classificationAdj = new Bundle();
+ classificationAdj.putParcelable(Adjustment.KEY_TYPE, mock(NotificationChannel.class));
+ Adjustment adjustment = new Adjustment("pkg", r.getKey(), classificationAdj, "", 0);
+ r.addAdjustment(adjustment);
+
+ RankingReconsideration regroupingTask = extractor.process(r);
+ assertThat(regroupingTask).isNull();
+ }
+
private NotificationRecord generateRecord() {
NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_LOW);
final Notification.Builder builder = new Notification.Builder(getContext())
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
index 797b95b5..7e4ae67 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
@@ -25,6 +25,7 @@
import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertNull;
+
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Matchers.any;
@@ -193,6 +194,8 @@
public void testWriteXml_userTurnedOffNAS() throws Exception {
int userId = ActivityManager.getCurrentUser();
+ doReturn(true).when(mAssistants).isValidService(eq(mCn), eq(userId));
+
mAssistants.loadDefaultsFromConfig(true);
mAssistants.setPackageOrComponentEnabled(mCn.flattenToString(), userId, true,
@@ -398,6 +401,10 @@
public void testSetPackageOrComponentEnabled_onlyOnePackage() throws Exception {
ComponentName component1 = ComponentName.unflattenFromString("package/Component1");
ComponentName component2 = ComponentName.unflattenFromString("package/Component2");
+
+ doReturn(true).when(mAssistants).isValidService(eq(component1), eq(mZero.id));
+ doReturn(true).when(mAssistants).isValidService(eq(component2), eq(mZero.id));
+
mAssistants.setPackageOrComponentEnabled(component1.flattenToString(), mZero.id, true,
true, true);
verify(mNm, never()).setNotificationAssistantAccessGrantedForUserInternal(
@@ -543,6 +550,7 @@
public void testSetAdjustmentTypeSupportedState() throws Exception {
int userId = ActivityManager.getCurrentUser();
+ doReturn(true).when(mAssistants).isValidService(eq(mCn), eq(userId));
mAssistants.loadDefaultsFromConfig(true);
mAssistants.setPackageOrComponentEnabled(mCn.flattenToString(), userId, true,
true, true);
@@ -566,6 +574,7 @@
public void testSetAdjustmentTypeSupportedState_readWriteXml_entries() throws Exception {
int userId = ActivityManager.getCurrentUser();
+ doReturn(true).when(mAssistants).isValidService(eq(mCn), eq(userId));
mAssistants.loadDefaultsFromConfig(true);
mAssistants.setPackageOrComponentEnabled(mCn.flattenToString(), userId, true,
true, true);
@@ -589,6 +598,7 @@
public void testSetAdjustmentTypeSupportedState_readWriteXml_empty() throws Exception {
int userId = ActivityManager.getCurrentUser();
+ doReturn(true).when(mAssistants).isValidService(eq(mCn), eq(userId));
mAssistants.loadDefaultsFromConfig(true);
mAssistants.setPackageOrComponentEnabled(mCn.flattenToString(), userId, true,
true, true);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
index 45cd571..592eec5 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
@@ -2291,10 +2291,7 @@
flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
initAttentionHelper(flagResolver);
- // Trigger avalanche trigger intent
- final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
- intent.putExtra("state", false);
- mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+ triggerAvalancheEvent();
NotificationRecord r = getBeepyNotification();
@@ -2338,10 +2335,7 @@
flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
initAttentionHelper(flagResolver);
- // Trigger avalanche trigger intent
- final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
- intent.putExtra("state", false);
- mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+ triggerAvalancheEvent();
NotificationRecord r = getBeepyNotification();
@@ -2379,10 +2373,7 @@
flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
initAttentionHelper(flagResolver);
- // Trigger avalanche trigger intent
- final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
- intent.putExtra("state", false);
- mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+ triggerAvalancheEvent();
NotificationRecord r = getBeepyNotification();
@@ -2428,10 +2419,7 @@
flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
initAttentionHelper(flagResolver);
- // Trigger avalanche trigger intent
- final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
- intent.putExtra("state", false);
- mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+ triggerAvalancheEvent();
NotificationRecord r = getBeepyNotification();
r.getNotification().category = Notification.CATEGORY_EVENT;
@@ -2504,10 +2492,7 @@
flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
initAttentionHelper(flagResolver);
- // Trigger avalanche trigger intent
- final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
- intent.putExtra("state", false);
- mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+ triggerAvalancheEvent();
// Regular notification: should beep at 0% volume
NotificationRecord r = getBeepyNotification();
@@ -2574,10 +2559,7 @@
flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
initAttentionHelper(flagResolver);
- // Trigger avalanche trigger intent
- final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
- intent.putExtra("state", false);
- mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+ triggerAvalancheEvent();
NotificationRecord r = getBeepyNotification();
@@ -2602,10 +2584,7 @@
flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
initAttentionHelper(flagResolver);
- // Trigger avalanche trigger intent
- final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
- intent.putExtra("state", false);
- mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+ triggerAvalancheEvent();
// CATEGORY_ALARM is exempted
NotificationRecord r = getBeepyNotification();
@@ -2637,6 +2616,70 @@
}
@Test
+ public void testBeepVolume_politeNotif_AvalancheStrategy_exempt_msgCategory() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+ mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
+ mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS_ATTN_UPDATE);
+ TestableFlagResolver flagResolver = new TestableFlagResolver();
+ flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
+ flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
+ initAttentionHelper(flagResolver);
+
+ triggerAvalancheEvent();
+
+ // Create a conversation group with GROUP_ALERT_SUMMARY behavior
+ // Where the summary is not MessagingStyle
+ final String groupKey = "grup_name";
+ final String shortcutId = "shortcut";
+ NotificationRecord summary = getBeepyNotificationRecord(groupKey, GROUP_ALERT_SUMMARY);
+ summary.getNotification().flags |= Notification.FLAG_GROUP_SUMMARY;
+ summary.getNotification().category = Notification.CATEGORY_MESSAGE;
+ ShortcutInfo.Builder sb = new ShortcutInfo.Builder(getContext());
+ summary.setShortcutInfo(sb.setId(shortcutId).build());
+
+ // Should beep at 100% volume
+ mAttentionHelper.buzzBeepBlinkLocked(summary, DEFAULT_SIGNALS);
+ verifyBeepVolume(1.0f);
+ assertNotEquals(-1, summary.getLastAudiblyAlertedMs());
+ Mockito.reset(mRingtonePlayer);
+
+ // Post child notifications with GROUP_ALERT_SUMMARY
+ NotificationRecord child = getConversationNotificationRecord(mId, false /* insistent */,
+ false /* once */, true /* noisy */, false /* buzzy*/, false /* lights */, true,
+ true, false, groupKey, Notification.GROUP_ALERT_SUMMARY, false, mUser, mPkg,
+ shortcutId);
+
+ // Should not beep
+ mAttentionHelper.buzzBeepBlinkLocked(child, DEFAULT_SIGNALS);
+ verifyNeverBeep();
+ assertFalse(child.isInterruptive());
+ assertEquals(-1, child.getLastAudiblyAlertedMs());
+ Mockito.reset(mRingtonePlayer);
+
+ // 2nd update for summary should beep at 50% volume
+ mAttentionHelper.buzzBeepBlinkLocked(summary, DEFAULT_SIGNALS);
+ verifyBeepVolume(0.5f);
+ assertNotEquals(-1, summary.getLastAudiblyAlertedMs());
+ Mockito.reset(mRingtonePlayer);
+
+ // 3rd update for summary should not beep
+ mAttentionHelper.buzzBeepBlinkLocked(summary, DEFAULT_SIGNALS);
+ verifyNeverBeep();
+ assertFalse(summary.isInterruptive());
+ assertEquals(-1, summary.getLastAudiblyAlertedMs());
+ }
+
+ private void triggerAvalancheEvent() throws Exception {
+ // Trigger avalanche trigger intent
+ final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ intent.putExtra("state", false);
+ mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+ // Wait after avalanche trigger before posting notifications
+ // so that notification#getWhen() is not the same value
+ Thread.sleep(100);
+ }
+
+ @Test
public void testBeepVolume_politeNotif_exemptEmergency() throws Exception {
mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
mSetFlagsRule.disableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
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 3c120e1..1349ee0 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -3206,7 +3206,6 @@
// Send two cancelations.
mBinderService.cancelNotificationWithTag(mPkg, mPkg, sbn.getTag(), sbn.getId(),
sbn.getUserId());
- waitForIdle();
mBinderService.cancelNotificationWithTag(mPkg, mPkg, sbn.getTag(), sbn.getId(),
sbn.getUserId());
waitForIdle();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
index 9a6e818..5d4382a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
@@ -92,6 +92,7 @@
@Mock ZenModeHelper mMockZenModeHelper;
@Mock RankingConfig mConfig;
@Mock Vibrator mVibrator;
+ @Mock GroupHelper mGroupHelper;
private NotificationManager.Policy mTestNotificationPolicy;
private Notification mNotiGroupGSortA;
@@ -157,7 +158,7 @@
when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
mHelper = new RankingHelper(getContext(), mHandler, mConfig, mMockZenModeHelper,
mUsageStats, new String[] {ImportanceExtractor.class.getName()},
- mock(IPlatformCompat.class));
+ mock(IPlatformCompat.class), mGroupHelper);
mNotiGroupGSortA = new Notification.Builder(mContext, TEST_CHANNEL_ID)
.setContentTitle("A")
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
index e83a4b2..7536f5f 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -47,7 +47,6 @@
import android.hardware.vibrator.IVibratorManager;
import android.os.CombinedVibration;
import android.os.Handler;
-import android.os.IBinder;
import android.os.PersistableBundle;
import android.os.PowerManager;
import android.os.Process;
@@ -119,7 +118,6 @@
@Mock private PackageManagerInternal mPackageManagerInternalMock;
@Mock private VibrationThread.VibratorManagerHooks mManagerHooks;
@Mock private VibratorController.OnVibrationCompleteListener mControllerCallbacks;
- @Mock private IBinder mVibrationToken;
@Mock private VibrationConfig mVibrationConfigMock;
@Mock private VibratorFrameworkStatsLogger mStatsLoggerMock;
@@ -668,7 +666,7 @@
VibrationEffect fallback = VibrationEffect.createOneShot(10, 100);
HalVibration vibration = createVibration(CombinedVibration.createParallel(
VibrationEffect.get(VibrationEffect.EFFECT_CLICK)));
- vibration.addFallback(VibrationEffect.EFFECT_CLICK, fallback);
+ vibration.fillFallbacks(unused -> fallback);
startThreadAndDispatcher(vibration);
waitForCompletion();
@@ -848,7 +846,7 @@
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
.compose();
HalVibration vibration = createVibration(CombinedVibration.createParallel(effect));
- vibration.addFallback(VibrationEffect.EFFECT_TICK, fallback);
+ vibration.fillFallbacks(unused -> fallback);
startThreadAndDispatcher(vibration);
waitForCompletion();
@@ -954,7 +952,8 @@
assertTrue(mThread.isRunningVibrationId(vibration.id));
assertTrue(mControllers.get(VIBRATOR_ID).isVibrating());
- mVibrationConductor.binderDied();
+ mVibrationConductor.notifyCancelled(
+ new Vibration.EndInfo(Status.CANCELLED_BINDER_DIED), /* immediate= */ false);
waitForCompletion();
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
@@ -1575,7 +1574,8 @@
TEST_TIMEOUT_MILLIS));
assertTrue(mThread.isRunningVibrationId(vibration.id));
- mVibrationConductor.binderDied();
+ mVibrationConductor.notifyCancelled(
+ new Vibration.EndInfo(Status.CANCELLED_BINDER_DIED), /* immediate= */ false);
waitForCompletion();
verifyCallbacksTriggered(vibration, Status.CANCELLED_BINDER_DIED);
@@ -1865,9 +1865,9 @@
VibrationAttributes attrs = new VibrationAttributes.Builder()
.setUsage(usage)
.build();
- HalVibration vib = new HalVibration(mVibrationToken,
- CombinedVibration.createParallel(effect),
- new CallerInfo(attrs, UID, DEVICE_ID, PACKAGE_NAME, "reason"));
+ HalVibration vib = new HalVibration(
+ new CallerInfo(attrs, UID, DEVICE_ID, PACKAGE_NAME, "reason"),
+ CombinedVibration.createParallel(effect));
return startThreadAndDispatcher(vib, requestVibrationParamsFuture);
}
@@ -1903,8 +1903,8 @@
}
private HalVibration createVibration(CombinedVibration effect) {
- return new HalVibration(mVibrationToken, effect,
- new CallerInfo(ATTRS, UID, DEVICE_ID, PACKAGE_NAME, "reason"));
+ return new HalVibration(new CallerInfo(ATTRS, UID, DEVICE_ID, PACKAGE_NAME, "reason"),
+ effect);
}
private SparseArray<VibratorController> createVibratorControllers() {
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java
index cd057b6..1d1b4e2 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java
@@ -115,6 +115,6 @@
}
private static VibrationStats.StatsInfo newEmptyStatsInfo() {
- return new VibrationStats.StatsInfo(0, 0, 0, Status.FINISHED, new VibrationStats(), 0L);
+ return new VibrationStats.StatsInfo(0, 0, 0, Status.FINISHED, new VibrationStats());
}
}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index 538c3fc..b782162 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -1791,28 +1791,6 @@
}
@Test
- public void performHapticFeedback_usesServiceAsToken() throws Exception {
- VibratorManagerService service = createSystemReadyService();
-
- HalVibration vibration =
- performHapticFeedbackAndWaitUntilFinished(
- service, HapticFeedbackConstants.SCROLL_TICK, /* always= */ true);
-
- assertTrue(vibration.callerToken == service);
- }
-
- @Test
- public void performHapticFeedbackForInputDevice_usesServiceAsToken() throws Exception {
- VibratorManagerService service = createSystemReadyService();
-
- HalVibration vibration = performHapticFeedbackForInputDeviceAndWaitUntilFinished(
- service, HapticFeedbackConstants.SCROLL_TICK, /* inputDeviceId= */ 0,
- InputDevice.SOURCE_ROTARY_ENCODER, /* always= */ true);
-
- assertTrue(vibration.callerToken == service);
- }
-
- @Test
@RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
public void vibrate_vendorEffectsWithoutPermission_doesNotVibrate() throws Exception {
// Deny permission to vibrate with vendor effects
@@ -2147,6 +2125,27 @@
}
@Test
+ public void cancelVibrate_externalVibration_cancelWithDifferentToken() {
+ mockVibrators(1);
+ mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+ createSystemReadyService();
+
+ IBinder vibrationBinderToken = mock(IBinder.class);
+ ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
+ AUDIO_ALARM_ATTRS,
+ mock(IExternalVibrationController.class), vibrationBinderToken);
+ ExternalVibrationScale scale = mExternalVibratorService.onExternalVibrationStart(
+ externalVibration);
+
+ IBinder cancelBinderToken = mock(IBinder.class);
+ mService.cancelVibrate(VibrationAttributes.USAGE_FILTER_MATCH_ALL, cancelBinderToken);
+
+ assertNotEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel);
+ assertEquals(Arrays.asList(false, true, false),
+ mVibratorProviders.get(1).getExternalControlStates());
+ }
+
+ @Test
public void onExternalVibration_ignoreVibrationFromVirtualDevices() {
mockVibrators(1);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index 3dc893a..a85f866 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -700,7 +700,7 @@
void assertPowerWakeUp() {
mTestLooper.dispatchAll();
verify(mWindowWakeUpPolicy)
- .wakeUpFromKey(anyInt(), anyLong(), eq(KeyEvent.KEYCODE_POWER), anyBoolean());
+ .wakeUpFromKey(anyLong(), eq(KeyEvent.KEYCODE_POWER), anyBoolean());
}
void assertNoPowerSleep() {
diff --git a/services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java b/services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java
index 4119ad3..7322e5a 100644
--- a/services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java
@@ -35,7 +35,6 @@
import static com.android.internal.R.bool.config_allowTheaterModeWakeFromLidSwitch;
import static com.android.internal.R.bool.config_allowTheaterModeWakeFromGesture;
import static com.android.server.policy.Flags.FLAG_SUPPORT_INPUT_WAKEUP_DELEGATE;
-import static com.android.server.power.feature.flags.Flags.FLAG_PER_DISPLAY_WAKE_BY_TOUCH;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -44,7 +43,6 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -54,8 +52,6 @@
import android.content.ContextWrapper;
import android.content.res.Resources;
import android.os.PowerManager;
-import android.platform.test.annotations.DisableFlags;
-import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.view.Display;
@@ -129,7 +125,6 @@
}
@Test
- @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
public void testMotionWakeUpDelegation_wakePowerManagerIfDelegateDoesNotHandleWake() {
setTheaterModeEnabled(false);
mSetFlagsRule.enableFlags(FLAG_SUPPORT_INPUT_WAKEUP_DELEGATE);
@@ -141,8 +136,7 @@
// Verify the policy wake up call succeeds because of the call on the delegate, and not
// because of a PowerManager wake up.
- assertThat(mPolicy.wakeUpFromMotion(
- mDefaultDisplay.getDisplayId(), 200, SOURCE_TOUCHSCREEN, true)).isTrue();
+ assertThat(mPolicy.wakeUpFromMotion(200, SOURCE_TOUCHSCREEN, true)).isTrue();
verify(mInputWakeUpDelegate).wakeUpFromMotion(200, SOURCE_TOUCHSCREEN, true);
verifyNoPowerManagerWakeUp();
@@ -150,14 +144,12 @@
// Verify the policy wake up call succeeds because of the PowerManager wake up, since the
// delegate would not handle the wake up request.
- assertThat(mPolicy.wakeUpFromMotion(
- mDefaultDisplay.getDisplayId(), 300, SOURCE_ROTARY_ENCODER, false)).isTrue();
+ assertThat(mPolicy.wakeUpFromMotion(300, SOURCE_ROTARY_ENCODER, false)).isTrue();
verify(mInputWakeUpDelegate).wakeUpFromMotion(300, SOURCE_ROTARY_ENCODER, false);
verify(mPowerManager).wakeUp(300, WAKE_REASON_WAKE_MOTION, "android.policy:MOTION");
}
@Test
- @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
public void testKeyWakeUpDelegation_wakePowerManagerIfDelegateDoesNotHandleWake() {
setTheaterModeEnabled(false);
mSetFlagsRule.enableFlags(FLAG_SUPPORT_INPUT_WAKEUP_DELEGATE);
@@ -169,8 +161,7 @@
// Verify the policy wake up call succeeds because of the call on the delegate, and not
// because of a PowerManager wake up.
- assertThat(mPolicy.wakeUpFromKey(
- mDefaultDisplay.getDisplayId(), 200, KEYCODE_POWER, true)).isTrue();
+ assertThat(mPolicy.wakeUpFromKey(200, KEYCODE_POWER, true)).isTrue();
verify(mInputWakeUpDelegate).wakeUpFromKey(200, KEYCODE_POWER, true);
verifyNoPowerManagerWakeUp();
@@ -178,8 +169,7 @@
// Verify the policy wake up call succeeds because of the PowerManager wake up, since the
// delegate would not handle the wake up request.
- assertThat(mPolicy.wakeUpFromKey(
- mDefaultDisplay.getDisplayId(), 300, KEYCODE_STEM_PRIMARY, false)).isTrue();
+ assertThat(mPolicy.wakeUpFromKey(300, KEYCODE_STEM_PRIMARY, false)).isTrue();
verify(mInputWakeUpDelegate).wakeUpFromKey(300, KEYCODE_STEM_PRIMARY, false);
verify(mPowerManager).wakeUp(300, WAKE_REASON_WAKE_KEY, "android.policy:KEY");
}
@@ -196,8 +186,7 @@
.setInputWakeUpDelegate(mInputWakeUpDelegate);
// Check that the wake up does not happen because the theater mode policy check fails.
- assertThat(mPolicy.wakeUpFromKey(
- mDefaultDisplay.getDisplayId(), 200, KEYCODE_POWER, true)).isFalse();
+ assertThat(mPolicy.wakeUpFromKey(200, KEYCODE_POWER, true)).isFalse();
verify(mInputWakeUpDelegate, never()).wakeUpFromKey(anyLong(), anyInt(), anyBoolean());
}
@@ -212,13 +201,11 @@
.setInputWakeUpDelegate(mInputWakeUpDelegate);
// Check that the wake up does not happen because the theater mode policy check fails.
- assertThat(mPolicy.wakeUpFromMotion(
- mDefaultDisplay.getDisplayId(), 200, SOURCE_TOUCHSCREEN, true)).isFalse();
+ assertThat(mPolicy.wakeUpFromMotion(200, SOURCE_TOUCHSCREEN, true)).isFalse();
verify(mInputWakeUpDelegate, never()).wakeUpFromMotion(anyLong(), anyInt(), anyBoolean());
}
@Test
- @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
public void testTheaterModeChecksNotAppliedWhenScreenIsOn() {
mSetFlagsRule.enableFlags(FLAG_SUPPORT_INPUT_WAKEUP_DELEGATE);
setDefaultDisplayState(Display.STATE_ON);
@@ -226,69 +213,30 @@
setBooleanRes(config_allowTheaterModeWakeFromMotion, false);
mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
- mPolicy.wakeUpFromMotion(mDefaultDisplay.getDisplayId(), 200L, SOURCE_TOUCHSCREEN, true);
+ mPolicy.wakeUpFromMotion(200L, SOURCE_TOUCHSCREEN, true);
verify(mPowerManager).wakeUp(200L, WAKE_REASON_WAKE_MOTION, "android.policy:MOTION");
}
@Test
- @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
public void testWakeUpFromMotion() {
runPowerManagerUpChecks(
- () -> mPolicy.wakeUpFromMotion(mDefaultDisplay.getDisplayId(),
- mClock.uptimeMillis(), SOURCE_TOUCHSCREEN, true),
+ () -> mPolicy.wakeUpFromMotion(mClock.uptimeMillis(), SOURCE_TOUCHSCREEN, true),
config_allowTheaterModeWakeFromMotion,
WAKE_REASON_WAKE_MOTION,
"android.policy:MOTION");
}
@Test
- @EnableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
- public void testWakeUpFromMotion_perDisplayWakeByTouchEnabled() {
- setTheaterModeEnabled(false);
- final int displayId = 555;
- mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
-
- boolean displayWokeUp = mPolicy.wakeUpFromMotion(
- displayId, mClock.uptimeMillis(), SOURCE_TOUCHSCREEN, /* isDown= */ true);
-
- // Verify that display is woken up
- assertThat(displayWokeUp).isTrue();
- verify(mPowerManager).wakeUp(anyLong(), eq(WAKE_REASON_WAKE_MOTION),
- eq("android.policy:MOTION"), eq(displayId));
- }
-
- @Test
- @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
- public void testWakeUpFromMotion_perDisplayWakeByTouchDisabled() {
- setTheaterModeEnabled(false);
- final int displayId = 555;
- mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
-
- boolean displayWokeUp = mPolicy.wakeUpFromMotion(
- displayId, mClock.uptimeMillis(), SOURCE_TOUCHSCREEN, /* isDown= */ true);
-
- // Verify that power is woken up and display isn't woken up individually
- assertThat(displayWokeUp).isTrue();
- verify(mPowerManager).wakeUp(
- anyLong(), eq(WAKE_REASON_WAKE_MOTION), eq("android.policy:MOTION"));
- verify(mPowerManager, never()).wakeUp(anyLong(), eq(WAKE_REASON_WAKE_MOTION),
- eq("android.policy:MOTION"), eq(displayId));
- }
-
- @Test
- @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
public void testWakeUpFromKey_nonPowerKey() {
runPowerManagerUpChecks(
- () -> mPolicy.wakeUpFromKey(
- mDefaultDisplay.getDisplayId(), mClock.uptimeMillis(), KEYCODE_HOME, true),
+ () -> mPolicy.wakeUpFromKey(mClock.uptimeMillis(), KEYCODE_HOME, true),
config_allowTheaterModeWakeFromKey,
WAKE_REASON_WAKE_KEY,
"android.policy:KEY");
}
@Test
- @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
public void testWakeUpFromKey_powerKey() {
// Disable the resource affecting all wake keys because it affects power key as well.
// That way, power key wake during theater mode will solely be controlled by
@@ -297,8 +245,7 @@
// Test with power key
runPowerManagerUpChecks(
- () -> mPolicy.wakeUpFromKey(
- mDefaultDisplay.getDisplayId(), mClock.uptimeMillis(), KEYCODE_POWER, true),
+ () -> mPolicy.wakeUpFromKey(mClock.uptimeMillis(), KEYCODE_POWER, true),
config_allowTheaterModeWakeFromPowerKey,
WAKE_REASON_POWER_BUTTON,
"android.policy:POWER");
@@ -307,15 +254,13 @@
// even if the power-key specific theater mode config is disabled.
setBooleanRes(config_allowTheaterModeWakeFromPowerKey, false);
runPowerManagerUpChecks(
- () -> mPolicy.wakeUpFromKey(mDefaultDisplay.getDisplayId(), mClock.uptimeMillis(),
- KEYCODE_POWER, false),
+ () -> mPolicy.wakeUpFromKey(mClock.uptimeMillis(), KEYCODE_POWER, false),
config_allowTheaterModeWakeFromKey,
WAKE_REASON_POWER_BUTTON,
"android.policy:POWER");
}
@Test
- @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
public void testWakeUpFromLid() {
runPowerManagerUpChecks(
() -> mPolicy.wakeUpFromLid(),
@@ -325,7 +270,6 @@
}
@Test
- @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
public void testWakeUpFromWakeGesture() {
runPowerManagerUpChecks(
() -> mPolicy.wakeUpFromWakeGesture(),
@@ -335,7 +279,6 @@
}
@Test
- @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
public void testwakeUpFromCameraCover() {
runPowerManagerUpChecks(
() -> mPolicy.wakeUpFromCameraCover(mClock.uptimeMillis()),
@@ -345,7 +288,6 @@
}
@Test
- @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
public void testWakeUpFromPowerKeyCameraGesture() {
// Disable the resource affecting all wake keys because it affects power key as well.
// That way, power key wake during theater mode will solely be controlled by
@@ -353,7 +295,7 @@
setBooleanRes(config_allowTheaterModeWakeFromKey, false);
runPowerManagerUpChecks(
- () -> mPolicy.wakeUpFromPowerKeyCameraGesture(mDefaultDisplay.getDisplayId()),
+ () -> mPolicy.wakeUpFromPowerKeyCameraGesture(),
config_allowTheaterModeWakeFromPowerKey,
WAKE_REASON_CAMERA_LAUNCH,
"android.policy:CAMERA_GESTURE_PREVENT_LOCK");
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
index 65736cb..c8a3559 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
@@ -179,17 +179,25 @@
.getAppCompatAspectRatioPolicy()).isLetterboxedForFixedOrientationAndAspectRatio();
}
- void enableTreatmentForTopActivity(boolean enabled) {
- doReturn(enabled).when(mDisplayContent.mAppCompatCameraPolicy)
- .isTreatmentEnabledForActivity(eq(mActivityStack.top()));
+ void enableFullscreenCameraCompatTreatmentForTopActivity(boolean enabled) {
+ if (mDisplayContent.mAppCompatCameraPolicy.hasDisplayRotationCompatPolicy()) {
+ doReturn(enabled).when(
+ mDisplayContent.mAppCompatCameraPolicy.mDisplayRotationCompatPolicy)
+ .isTreatmentEnabledForActivity(eq(mActivityStack.top()));
+ }
}
- void setTopActivityCameraActive(boolean enabled) {
+ void setIsCameraRunningAndWindowingModeEligibleFullscreen(boolean enabled) {
doReturn(enabled).when(getTopDisplayRotationCompatPolicy())
.isCameraRunningAndWindowingModeEligible(eq(mActivityStack.top()),
/* mustBeFullscreen= */ eq(true));
}
+ void setIsCameraRunningAndWindowingModeEligibleFreeform(boolean enabled) {
+ doReturn(enabled).when(getTopCameraCompatFreeformPolicy())
+ .isCameraRunningAndWindowingModeEligible(eq(mActivityStack.top()));
+ }
+
void setTopActivityEligibleForOrientationOverride(boolean enabled) {
doReturn(enabled).when(getTopDisplayRotationCompatPolicy())
.isActivityEligibleForOrientationOverride(eq(mActivityStack.top()));
@@ -508,8 +516,13 @@
}
private DisplayRotationCompatPolicy getTopDisplayRotationCompatPolicy() {
- return mActivityStack.top().mDisplayContent
- .mAppCompatCameraPolicy.mDisplayRotationCompatPolicy;
+ return mActivityStack.top().mDisplayContent.mAppCompatCameraPolicy
+ .mDisplayRotationCompatPolicy;
+ }
+
+ private CameraCompatFreeformPolicy getTopCameraCompatFreeformPolicy() {
+ return mActivityStack.top().mDisplayContent.mAppCompatCameraPolicy
+ .mCameraCompatFreeformPolicy;
}
// We add the activity to the stack and spyOn() on its properties.
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java
index 1e40aa0..b839113 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java
@@ -282,7 +282,8 @@
robot.activity().createActivityWithComponentInNewTaskAndDisplay();
robot.checkFixedOrientationLetterboxAspectRatioForTopParent(/* expected */ 1.5f);
- robot.activity().enableTreatmentForTopActivity(/* enabled */ true);
+ robot.activity().enableFullscreenCameraCompatTreatmentForTopActivity(
+ /* enabled */ true);
robot.checkAspectRatioForTopParentIsSplitScreenRatio(/* expected */ true);
});
}
@@ -308,6 +309,12 @@
void onPostDisplayContentCreation(@NonNull DisplayContent displayContent) {
super.onPostDisplayContentCreation(displayContent);
spyOn(displayContent.mAppCompatCameraPolicy);
+ if (displayContent.mAppCompatCameraPolicy.hasDisplayRotationCompatPolicy()) {
+ spyOn(displayContent.mAppCompatCameraPolicy.mDisplayRotationCompatPolicy);
+ }
+ if (displayContent.mAppCompatCameraPolicy.hasCameraCompatFreeformPolicy()) {
+ spyOn(displayContent.mAppCompatCameraPolicy.mCameraCompatFreeformPolicy);
+ }
}
@Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java
index 41102d6..cb5afd8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java
@@ -18,13 +18,13 @@
import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.server.wm.AppCompatCameraPolicy.isTreatmentEnabledForActivity;
+import static com.android.server.wm.AppCompatCameraPolicy.shouldOverrideMinAspectRatioForCamera;
import static com.android.window.flags.Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
import android.compat.testing.PlatformCompatChangeRule;
import android.platform.test.annotations.DisableFlags;
@@ -88,7 +88,7 @@
@EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
public void testCameraCompatFreeformPolicy_presentWhenEnabledAndDW() {
runTestScenario((robot) -> {
- robot.allowEnterDesktopMode(/* isAllowed= */ true);
+ robot.dw().allowEnterDesktopMode(/* isAllowed= */ true);
robot.activity().createActivityWithComponentInNewTaskAndDisplay();
robot.checkTopActivityHasCameraCompatFreeformPolicy(/* exists= */ true);
});
@@ -98,7 +98,7 @@
@EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
public void testCameraCompatFreeformPolicy_notPresentWhenNoDW() {
runTestScenario((robot) -> {
- robot.allowEnterDesktopMode(/* isAllowed= */ false);
+ robot.dw().allowEnterDesktopMode(/* isAllowed= */ false);
robot.activity().createActivityWithComponentInNewTaskAndDisplay();
robot.checkTopActivityHasCameraCompatFreeformPolicy(/* exists= */ false);
});
@@ -108,7 +108,7 @@
@DisableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
public void testCameraCompatFreeformPolicy_notPresentWhenNoFlag() {
runTestScenario((robot) -> {
- robot.allowEnterDesktopMode(/* isAllowed= */ true);
+ robot.dw().allowEnterDesktopMode(/* isAllowed= */ true);
robot.activity().createActivityWithComponentInNewTaskAndDisplay();
robot.checkTopActivityHasCameraCompatFreeformPolicy(/* exists= */ false);
});
@@ -118,7 +118,7 @@
@EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
public void testCameraCompatFreeformPolicy_notPresentWhenNoFlagAndNoDW() {
runTestScenario((robot) -> {
- robot.allowEnterDesktopMode(/* isAllowed= */ false);
+ robot.dw().allowEnterDesktopMode(/* isAllowed= */ false);
robot.activity().createActivityWithComponentInNewTaskAndDisplay();
robot.checkTopActivityHasCameraCompatFreeformPolicy(/* exists= */ false);
});
@@ -128,7 +128,7 @@
@EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
public void testCameraCompatFreeformPolicy_startedWhenEnabledAndDW() {
runTestScenario((robot) -> {
- robot.allowEnterDesktopMode(/* isAllowed= */ true);
+ robot.dw().allowEnterDesktopMode(/* isAllowed= */ true);
robot.activity().createActivityWithComponentInNewTaskAndDisplay();
robot.checkTopActivityHasCameraCompatFreeformPolicy(/* exists= */ true);
robot.checkTopActivityCameraCompatFreeformPolicyIsRunning();
@@ -139,7 +139,7 @@
@EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
public void testCameraStateManager_existsWhenCameraCompatFreeformExists() {
runTestScenario((robot) -> {
- robot.allowEnterDesktopMode(true);
+ robot.dw().allowEnterDesktopMode(true);
robot.activity().createActivityWithComponentInNewTaskAndDisplay();
robot.checkTopActivityHasCameraCompatFreeformPolicy(/* exists= */ true);
robot.checkTopActivityHasCameraStateMonitor(/* exists= */ true);
@@ -150,7 +150,7 @@
@EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
public void testCameraStateManager_startedWhenCameraCompatFreeformExists() {
runTestScenario((robot) -> {
- robot.allowEnterDesktopMode(true);
+ robot.dw().allowEnterDesktopMode(true);
robot.activity().createActivityWithComponentInNewTaskAndDisplay();
robot.checkTopActivityHasCameraCompatFreeformPolicy(/* exists= */ true);
robot.checkTopActivityHasCameraStateMonitor(/* exists= */ true);
@@ -194,9 +194,10 @@
@Test
public void testIsCameraCompatTreatmentActive_whenTreatmentForTopActivityIsEnabled() {
runTestScenario((robot) -> {
+ robot.conf().enableCameraCompatTreatmentAtBuildTime(/* enabled= */ true);
robot.applyOnActivity((a)-> {
- a.createActivityWithComponent();
- a.enableTreatmentForTopActivity(/* enabled */ true);
+ a.createActivityWithComponentInNewTaskAndDisplay();
+ a.enableFullscreenCameraCompatTreatmentForTopActivity(/* enabled */ true);
});
robot.checkIsCameraCompatTreatmentActiveForTopActivity(/* active */ true);
@@ -206,9 +207,10 @@
@Test
public void testIsCameraCompatTreatmentNotActive_whenTreatmentForTopActivityIsDisabled() {
runTestScenario((robot) -> {
+ robot.conf().enableCameraCompatTreatmentAtBuildTime(/* enabled= */ true);
robot.applyOnActivity((a)-> {
a.createActivityWithComponent();
- a.enableTreatmentForTopActivity(/* enabled */ false);
+ a.enableFullscreenCameraCompatTreatmentForTopActivity(/* enabled */ false);
});
robot.checkIsCameraCompatTreatmentActiveForTopActivity(/* active */ false);
@@ -220,9 +222,10 @@
public void testShouldOverrideMinAspectRatioForCamera_whenCameraIsNotRunning() {
runTestScenario((robot) -> {
robot.applyOnActivity((a)-> {
+ robot.dw().allowEnterDesktopMode(true);
robot.conf().enableCameraCompatTreatmentAtBuildTime(/* enabled= */ true);
a.createActivityWithComponentInNewTaskAndDisplay();
- a.setTopActivityCameraActive(/* active */ false);
+ a.setIsCameraRunningAndWindowingModeEligibleFullscreen(/* enabled */ false);
});
robot.checkShouldOverrideMinAspectRatioForCamera(/* active */ false);
@@ -234,9 +237,10 @@
public void testShouldOverrideMinAspectRatioForCamera_whenCameraIsRunning_overrideDisabled() {
runTestScenario((robot) -> {
robot.applyOnActivity((a)-> {
+ robot.dw().allowEnterDesktopMode(true);
robot.conf().enableCameraCompatTreatmentAtBuildTime(/* enabled= */ true);
a.createActivityWithComponentInNewTaskAndDisplay();
- a.setTopActivityCameraActive(/* active */ true);
+ a.setIsCameraRunningAndWindowingModeEligibleFullscreen(/* active */ true);
});
robot.checkShouldOverrideMinAspectRatioForCamera(/* active */ false);
@@ -245,12 +249,28 @@
@Test
@EnableCompatChanges(OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA)
- public void testShouldOverrideMinAspectRatioForCamera_whenCameraIsRunning_overrideEnabled() {
+ public void testShouldOverrideMinAspectRatioForCameraFullscr_cameraIsRunning_overrideEnabled() {
runTestScenario((robot) -> {
robot.applyOnActivity((a)-> {
robot.conf().enableCameraCompatTreatmentAtBuildTime(/* enabled= */ true);
a.createActivityWithComponentInNewTaskAndDisplay();
- a.setTopActivityCameraActive(/* active */ true);
+ a.setIsCameraRunningAndWindowingModeEligibleFullscreen(/* active */ true);
+ });
+
+ robot.checkShouldOverrideMinAspectRatioForCamera(/* active */ true);
+ });
+ }
+
+
+ @Test
+ @EnableCompatChanges(OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA)
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ public void testShouldOverrideMinAspectRatioForCameraFreeform_cameraRunning_overrideEnabled() {
+ runTestScenario((robot) -> {
+ robot.applyOnActivity((a)-> {
+ robot.dw().allowEnterDesktopMode(true);
+ a.createActivityWithComponentInNewTaskAndDisplay();
+ a.setIsCameraRunningAndWindowingModeEligibleFreeform(/* active */ true);
});
robot.checkShouldOverrideMinAspectRatioForCamera(/* active */ true);
@@ -318,23 +338,11 @@
}
void checkIsCameraCompatTreatmentActiveForTopActivity(boolean active) {
- assertEquals(getTopAppCompatCameraPolicy()
- .isTreatmentEnabledForActivity(activity().top()), active);
+ assertEquals(active, isTreatmentEnabledForActivity(activity().top()));
}
void checkShouldOverrideMinAspectRatioForCamera(boolean expected) {
- assertEquals(getTopAppCompatCameraPolicy()
- .shouldOverrideMinAspectRatioForCamera(activity().top()), expected);
- }
-
- // TODO(b/350460645): Create Desktop Windowing Robot to reuse common functionalities.
- void allowEnterDesktopMode(boolean isAllowed) {
- doReturn(isAllowed).when(() ->
- DesktopModeHelper.canEnterDesktopMode(any()));
- }
-
- private AppCompatCameraPolicy getTopAppCompatCameraPolicy() {
- return activity().top().mDisplayContent.mAppCompatCameraPolicy;
+ assertEquals(expected, shouldOverrideMinAspectRatioForCamera(activity().top()));
}
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationOverridesTest.java
index d9b5f37..8747cfa 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationOverridesTest.java
@@ -17,11 +17,13 @@
import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED;
import static android.content.pm.ActivityInfo.OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_IGNORING_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.server.wm.AppCompatOrientationOverrides.OrientationOverridesState.MIN_COUNT_TO_IGNORE_REQUEST_IN_LOOP;
import static com.android.server.wm.AppCompatOrientationOverrides.OrientationOverridesState.SET_ORIENTATION_REQUEST_COUNTER_TIMEOUT_MS;
@@ -31,6 +33,7 @@
import static org.junit.Assert.assertTrue;
import android.compat.testing.PlatformCompatChangeRule;
+import android.content.pm.ActivityInfo.ScreenOrientation;
import android.platform.test.annotations.Presubmit;
import androidx.annotation.NonNull;
@@ -228,6 +231,25 @@
});
}
+ @Test
+ public void testOverrideRespectRequestedOrientationIsEnabled_bottomOrientationIsRespected() {
+ runTestScenario((robot) -> {
+ robot.applyOnActivity((a) -> {
+ a.setIgnoreOrientationRequest(true);
+ a.createActivityWithComponentInNewTask();
+ robot.setOverrideRespectRequestedOrientationEnabled(true);
+ a.configureUnresizableTopActivity(SCREEN_ORIENTATION_LANDSCAPE);
+ robot.checkDisplayShouldIgnoreOrientationRequest(SCREEN_ORIENTATION_LANDSCAPE,
+ /* expected */ false);
+
+ a.createActivityWithComponentInNewTask();
+ a.setTopActivityInFreeformWindowingMode(true);
+ });
+ robot.checkDisplayShouldIgnoreOrientationRequest(SCREEN_ORIENTATION_LANDSCAPE,
+ /* expected */ false);
+ });
+ }
+
/**
* Runs a test scenario providing a Robot.
*/
@@ -291,6 +313,22 @@
}
}
+ void setOverrideRespectRequestedOrientationEnabled(boolean override) {
+ spyOn(getTopOrientationOverrides());
+ doReturn(override).when(getTopOrientationOverrides())
+ .isOverrideRespectRequestedOrientationEnabled();
+ }
+
+ void checkDisplayShouldIgnoreOrientationRequest(@ScreenOrientation int candidate,
+ boolean expected) {
+ assertEquals(expected, activity().displayContent()
+ .shouldIgnoreOrientationRequest(candidate));
+ }
+
+ void checkExpectedDisplayOrientation(@ScreenOrientation int expected) {
+ assertEquals(expected, activity().displayContent().getOrientation());
+ }
+
void checkShouldUseDisplayLandscapeNaturalOrientation(boolean expected) {
assertEquals(expected,
getTopOrientationOverrides().shouldUseDisplayLandscapeNaturalOrientation());
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java
index 9057b6c..09ed9ba 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java
@@ -38,6 +38,7 @@
import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.window.flags.Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.verify;
@@ -45,6 +46,7 @@
import android.compat.testing.PlatformCompatChangeRule;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import androidx.annotation.NonNull;
@@ -321,7 +323,22 @@
});
robot.applyOnActivity((a) -> {
a.createActivityWithComponentInNewTaskAndDisplay();
- a.setTopActivityCameraActive(false);
+ a.setIsCameraRunningAndWindowingModeEligibleFullscreen(false);
+ });
+
+ robot.checkOverrideOrientation(/* candidate */ SCREEN_ORIENTATION_PORTRAIT,
+ /* expected */ SCREEN_ORIENTATION_PORTRAIT);
+ });
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ public void testOverrideOrientationIfNeeded_fullscrOverrideFreeform_cameraActivity_unchanged() {
+ runTestScenario((robot) -> {
+ robot.applyOnActivity((a) -> {
+ robot.dw().allowEnterDesktopMode(true);
+ a.createActivityWithComponentInNewTaskAndDisplay();
+ a.setIsCameraRunningAndWindowingModeEligibleFreeform(false);
});
robot.checkOverrideOrientation(/* candidate */ SCREEN_ORIENTATION_PORTRAIT,
@@ -426,8 +443,8 @@
c.enablePolicyForIgnoringRequestedOrientation(true);
});
robot.applyOnActivity((a) -> {
- a.createActivityWithComponentInNewTask();
- a.enableTreatmentForTopActivity(true);
+ a.createActivityWithComponentInNewTaskAndDisplay();
+ a.enableFullscreenCameraCompatTreatmentForTopActivity(true);
});
robot.prepareRelaunchingAfterRequestedOrientationChanged(false);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatRobotBase.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatRobotBase.java
index 5f2a63a..0d929ab 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatRobotBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatRobotBase.java
@@ -39,6 +39,8 @@
private final AppCompatComponentPropRobot mOptPropRobot;
@NonNull
private final AppCompatResourcesRobot mResourcesRobot;
+ @NonNull
+ private final DesktopWindowingRobot mDesktopWindowingRobot;
AppCompatRobotBase(@NonNull WindowManagerService wm,
@NonNull ActivityTaskManagerService atm,
@@ -51,6 +53,7 @@
new AppCompatConfigurationRobot(wm.mAppCompatConfiguration);
mOptPropRobot = new AppCompatComponentPropRobot(wm);
mResourcesRobot = new AppCompatResourcesRobot(wm.mContext.getResources());
+ mDesktopWindowingRobot = new DesktopWindowingRobot();
}
AppCompatRobotBase(@NonNull WindowManagerService wm,
@@ -111,6 +114,11 @@
return mResourcesRobot;
}
+ @NonNull
+ DesktopWindowingRobot dw() {
+ return mDesktopWindowingRobot;
+ }
+
void applyOnResources(@NonNull Consumer<AppCompatResourcesRobot> consumer) {
consumer.accept(mResourcesRobot);
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractorKosmos.kt b/services/tests/wmtests/src/com/android/server/wm/DesktopWindowingRobot.java
similarity index 60%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractorKosmos.kt
copy to services/tests/wmtests/src/com/android/server/wm/DesktopWindowingRobot.java
index 3a7d7ba..285a5e2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractorKosmos.kt
+++ b/services/tests/wmtests/src/com/android/server/wm/DesktopWindowingRobot.java
@@ -14,10 +14,15 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.row.domain.interactor
+package com.android.server.wm;
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.statusbar.notification.row.data.repository.NotificationRowRepository
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-fun Kosmos.getNotificationRowInteractor(repository: NotificationRowRepository) =
- NotificationRowInteractor(repository = repository)
+import static org.mockito.ArgumentMatchers.any;
+
+/** Robot for changing desktop windowing properties. */
+class DesktopWindowingRobot {
+ void allowEnterDesktopMode(boolean isAllowed) {
+ doReturn(isAllowed).when(() -> DesktopModeHelper.canEnterDesktopMode(any()));
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
index b26c267..d2cf03d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
@@ -41,7 +41,10 @@
import static org.mockito.Mockito.verify;
import android.app.StatusBarManager;
+import android.graphics.Rect;
import android.os.Binder;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.view.InsetsFrameProvider;
import android.view.InsetsSource;
@@ -52,6 +55,7 @@
import androidx.test.filters.SmallTest;
import com.android.server.statusbar.StatusBarManagerInternal;
+import com.android.window.flags.Flags;
import org.junit.Before;
import org.junit.Test;
@@ -95,6 +99,7 @@
}
@Test
+ @DisableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
public void testControlsForDispatch_freeformTaskVisible() {
addStatusBar();
addNavigationBar();
@@ -108,6 +113,37 @@
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ public void testControlsForDispatch_fullscreenFreeformTaskVisible() {
+ addStatusBar();
+ addNavigationBar();
+
+ final WindowState win = createWindow(null, WINDOWING_MODE_FREEFORM,
+ ACTIVITY_TYPE_STANDARD, TYPE_APPLICATION, mDisplayContent, "app");
+ win.setBounds(new Rect());
+ final InsetsSourceControl[] controls = addWindowAndGetControlsForDispatch(win);
+
+ // The freeform (w/fullscreen bounds) app window can control both system bars.
+ assertNotNull(controls);
+ assertEquals(2, controls.length);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ public void testControlsForDispatch_nonFullscreenFreeformTaskVisible() {
+ addStatusBar();
+ addNavigationBar();
+
+ final WindowState win = createWindow(null, WINDOWING_MODE_FREEFORM,
+ ACTIVITY_TYPE_STANDARD, TYPE_APPLICATION, mDisplayContent, "app");
+ win.getTask().setBounds(new Rect(1, 1, 10, 10));
+ final InsetsSourceControl[] controls = addWindowAndGetControlsForDispatch(win);
+
+ // The freeform (but not fullscreen bounds) app window must not control any system bars.
+ assertNull(controls);
+ }
+
+ @Test
public void testControlsForDispatch_forceStatusBarVisible() {
addStatusBar().mAttrs.forciblyShownTypes |= statusBars();
addNavigationBar();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index 401964c..1fa6578 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -23,6 +23,10 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
+import static android.view.WindowInsets.Type.captionBar;
+import static android.view.WindowInsets.Type.ime;
+import static android.view.WindowInsets.Type.navigationBars;
+import static android.view.WindowInsets.Type.statusBars;
import static android.view.WindowInsets.Type.systemOverlays;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
@@ -36,6 +40,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyFloat;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
@@ -80,6 +85,8 @@
import android.os.ShellCallback;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.util.ArraySet;
import android.view.IRemoteAnimationFinishedCallback;
import android.view.IRemoteAnimationRunner;
import android.view.InsetsFrameProvider;
@@ -103,6 +110,7 @@
import java.io.FileDescriptor;
import java.util.ArrayList;
import java.util.Comparator;
+import java.util.List;
import java.util.NoSuchElementException;
@@ -967,7 +975,7 @@
Rect insetsRect = new Rect(0, 200, 1080, 700);
final int flags = FLAG_FORCE_CONSUMING;
final InsetsFrameProvider provider =
- new InsetsFrameProvider(owner, 1, WindowInsets.Type.captionBar())
+ new InsetsFrameProvider(owner, 1, captionBar())
.setArbitraryRectangle(insetsRect)
.setFlags(flags);
task.addLocalInsetsFrameProvider(provider, owner);
@@ -1678,6 +1686,178 @@
assertFalse("The source must be removed.", hasLocalSource(task, provider.getId()));
}
+ @Test
+ public void testSetExcludeInsetsTypes_appliedOnNodeAndChildren() {
+ final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm);
+ final TestWindowContainer root = builder.setLayer(0).build();
+
+ final TestWindowContainer child1 = root.addChildWindow();
+ final TestWindowContainer child2 = root.addChildWindow();
+ final TestWindowContainer child11 = child1.addChildWindow();
+ final TestWindowContainer child12 = child1.addChildWindow();
+ final TestWindowContainer child21 = child2.addChildWindow();
+
+ assertEquals(0, root.mMergedExcludeInsetsTypes);
+ assertEquals(0, child1.mMergedExcludeInsetsTypes);
+ assertEquals(0, child11.mMergedExcludeInsetsTypes);
+ assertEquals(0, child12.mMergedExcludeInsetsTypes);
+ assertEquals(0, child2.mMergedExcludeInsetsTypes);
+ assertEquals(0, child21.mMergedExcludeInsetsTypes);
+
+ child1.setExcludeInsetsTypes(ime());
+ assertEquals(0, root.mMergedExcludeInsetsTypes);
+ assertEquals(ime(), child1.mMergedExcludeInsetsTypes);
+ assertEquals(ime(), child11.mMergedExcludeInsetsTypes);
+ assertEquals(ime(), child12.mMergedExcludeInsetsTypes);
+ assertEquals(0, child2.mMergedExcludeInsetsTypes);
+ assertEquals(0, child21.mMergedExcludeInsetsTypes);
+
+ child11.setExcludeInsetsTypes(navigationBars());
+ assertEquals(0, root.mMergedExcludeInsetsTypes);
+ assertEquals(ime(), child1.mMergedExcludeInsetsTypes);
+ assertEquals(ime() | navigationBars(), child11.mMergedExcludeInsetsTypes);
+ assertEquals(ime(), child12.mMergedExcludeInsetsTypes);
+ assertEquals(0, child2.mMergedExcludeInsetsTypes);
+ assertEquals(0, child21.mMergedExcludeInsetsTypes);
+
+ // overwriting the same value has no change
+ for (int i = 0; i < 2; i++) {
+ root.setExcludeInsetsTypes(statusBars());
+ assertEquals(statusBars(), root.mMergedExcludeInsetsTypes);
+ assertEquals(statusBars() | ime(), child1.mMergedExcludeInsetsTypes);
+ assertEquals(statusBars() | ime() | navigationBars(),
+ child11.mMergedExcludeInsetsTypes);
+ assertEquals(statusBars() | ime(), child12.mMergedExcludeInsetsTypes);
+ assertEquals(statusBars(), child2.mMergedExcludeInsetsTypes);
+ assertEquals(statusBars(), child21.mMergedExcludeInsetsTypes);
+ }
+
+ // set and reset type statusBars on child. Should have no effect because of parent
+ child2.setExcludeInsetsTypes(statusBars());
+ assertEquals(statusBars(), root.mMergedExcludeInsetsTypes);
+ assertEquals(statusBars() | ime(), child1.mMergedExcludeInsetsTypes);
+ assertEquals(statusBars() | ime() | navigationBars(), child11.mMergedExcludeInsetsTypes);
+ assertEquals(statusBars() | ime(), child12.mMergedExcludeInsetsTypes);
+ assertEquals(statusBars(), child2.mMergedExcludeInsetsTypes);
+ assertEquals(statusBars(), child21.mMergedExcludeInsetsTypes);
+
+ // reset
+ child2.setExcludeInsetsTypes(0);
+ assertEquals(statusBars(), root.mMergedExcludeInsetsTypes);
+ assertEquals(statusBars() | ime(), child1.mMergedExcludeInsetsTypes);
+ assertEquals(statusBars() | ime() | navigationBars(), child11.mMergedExcludeInsetsTypes);
+ assertEquals(statusBars() | ime(), child12.mMergedExcludeInsetsTypes);
+ assertEquals(statusBars(), child2.mMergedExcludeInsetsTypes);
+ assertEquals(statusBars(), child21.mMergedExcludeInsetsTypes);
+
+ // when parent has statusBars also removed, it should be cleared from all children in the
+ // hierarchy
+ root.setExcludeInsetsTypes(0);
+ assertEquals(0, root.mMergedExcludeInsetsTypes);
+ assertEquals(ime(), child1.mMergedExcludeInsetsTypes);
+ assertEquals(ime() | navigationBars(), child11.mMergedExcludeInsetsTypes);
+ assertEquals(ime(), child12.mMergedExcludeInsetsTypes);
+ assertEquals(0, child2.mMergedExcludeInsetsTypes);
+ assertEquals(0, child21.mMergedExcludeInsetsTypes);
+
+ // change on node should have no effect on siblings
+ child12.setExcludeInsetsTypes(captionBar());
+ assertEquals(0, root.mMergedExcludeInsetsTypes);
+ assertEquals(ime(), child1.mMergedExcludeInsetsTypes);
+ assertEquals(ime() | navigationBars(), child11.mMergedExcludeInsetsTypes);
+ assertEquals(captionBar() | ime(), child12.mMergedExcludeInsetsTypes);
+ assertEquals(0, child2.mMergedExcludeInsetsTypes);
+ assertEquals(0, child21.mMergedExcludeInsetsTypes);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.view.inputmethod.Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
+ public void testSetExcludeInsetsTypes_appliedAfterReparenting() {
+ final SurfaceControl mockSurfaceControl = mock(SurfaceControl.class);
+ final DisplayContent mockDisplayContent = mock(DisplayContent.class);
+ final var mockInsetsStateController = mock(InsetsStateController.class);
+ doReturn(mockInsetsStateController).when(mockDisplayContent).getInsetsStateController();
+
+ final WindowContainer root1 = createWindowContainerSpy(mockSurfaceControl,
+ mockDisplayContent);
+ final WindowContainer root2 = createWindowContainerSpy(mockSurfaceControl,
+ mockDisplayContent);
+ final WindowContainer child = createWindowContainerSpy(mockSurfaceControl,
+ mockDisplayContent);
+ doNothing().when(child).onConfigurationChanged(any());
+
+ root1.setExcludeInsetsTypes(ime());
+ root2.setExcludeInsetsTypes(captionBar());
+ assertEquals(ime(), root1.mMergedExcludeInsetsTypes);
+ assertEquals(captionBar(), root2.mMergedExcludeInsetsTypes);
+ assertEquals(0, child.mMergedExcludeInsetsTypes);
+ clearInvocations(mockInsetsStateController);
+
+ root1.addChild(child, 0);
+ assertEquals(ime(), child.mMergedExcludeInsetsTypes);
+ verify(mockInsetsStateController).notifyInsetsChanged(
+ new ArraySet<>(List.of(child.asWindowState())));
+ clearInvocations(mockInsetsStateController);
+
+ // Make sure that reparenting does not call notifyInsetsChanged twice
+ child.reparent(root2, 0);
+ assertEquals(captionBar(), child.mMergedExcludeInsetsTypes);
+ verify(mockInsetsStateController).notifyInsetsChanged(
+ new ArraySet<>(List.of(child.asWindowState())));
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.view.inputmethod.Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
+ public void testSetExcludeInsetsTypes_notifyInsetsAfterChange() {
+ final var mockDisplayContent = mock(DisplayContent.class);
+ final var mockInsetsStateController = mock(InsetsStateController.class);
+ doReturn(mockInsetsStateController).when(mockDisplayContent).getInsetsStateController();
+
+ final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm);
+ final WindowState mockRootWs = mock(WindowState.class);
+ final TestWindowContainer root = builder.setLayer(0).setAsWindowState(mockRootWs).build();
+ root.mDisplayContent = mockDisplayContent;
+ verify(mockInsetsStateController, never()).notifyInsetsChanged(any());
+
+ root.setExcludeInsetsTypes(ime());
+ assertEquals(ime(), root.mMergedExcludeInsetsTypes);
+ verify(mockInsetsStateController).notifyInsetsChanged(
+ new ArraySet<>(List.of(root.asWindowState())));
+ clearInvocations(mockInsetsStateController);
+
+ // adding a child (while parent has set excludedInsetsTypes) should trigger
+ // notifyInsetsChanged
+ final WindowState mockChildWs = mock(WindowState.class);
+ final TestWindowContainer child1 = builder.setLayer(0).setAsWindowState(
+ mockChildWs).build();
+ child1.mDisplayContent = mockDisplayContent;
+ root.addChildWindow(child1);
+ // TestWindowContainer overrides onParentChanged and therefore doesn't call into
+ // mergeExcludeInsetsTypesAndNotifyInsetsChanged. This is checked in another test
+ assertTrue(child1.mOnParentChangedCalled);
+ child1.setExcludeInsetsTypes(ime());
+ assertEquals(ime(), child1.mMergedExcludeInsetsTypes);
+ verify(mockInsetsStateController).notifyInsetsChanged(
+ new ArraySet<>(List.of(child1.asWindowState())));
+ clearInvocations(mockInsetsStateController);
+
+ // not changing excludedInsetsTypes should not trigger notifyInsetsChanged again
+ root.setExcludeInsetsTypes(ime());
+ assertEquals(ime(), root.mMergedExcludeInsetsTypes);
+ assertEquals(ime(), child1.mMergedExcludeInsetsTypes);
+ verify(mockInsetsStateController, never()).notifyInsetsChanged(any());
+ }
+
+ private WindowContainer<?> createWindowContainerSpy(SurfaceControl mockSurfaceControl,
+ DisplayContent mockDisplayContent) {
+ final WindowContainer<?> wc = spy(new WindowContainer<>(mWm));
+ final WindowState mocWs = mock(WindowState.class);
+ doReturn(mocWs).when(wc).asWindowState();
+ wc.mSurfaceControl = mockSurfaceControl;
+ wc.mDisplayContent = mockDisplayContent;
+ return wc;
+ }
+
private static boolean hasLocalSource(WindowContainer container, int sourceId) {
if (container.mLocalInsetsSources == null) {
return false;
@@ -1693,6 +1873,7 @@
private boolean mFillsParent;
private boolean mWaitForTransitStart;
private Integer mOrientation;
+ private WindowState mWindowState;
private boolean mOnParentChangedCalled;
private boolean mOnDescendantOverrideCalled;
@@ -1714,7 +1895,7 @@
};
TestWindowContainer(WindowManagerService wm, int layer, boolean isAnimating,
- boolean isVisible, boolean waitTransitStart, Integer orientation) {
+ boolean isVisible, boolean waitTransitStart, Integer orientation, WindowState ws) {
super(wm);
mLayer = layer;
@@ -1723,6 +1904,7 @@
mFillsParent = true;
mOrientation = orientation;
mWaitForTransitStart = waitTransitStart;
+ mWindowState = ws;
spyOn(mSurfaceAnimator);
doReturn(mIsAnimating).when(mSurfaceAnimator).isAnimating();
doReturn(ANIMATION_TYPE_APP_TRANSITION).when(mSurfaceAnimator).getAnimationType();
@@ -1790,6 +1972,11 @@
boolean isWaitingForTransitionStart() {
return mWaitForTransitStart;
}
+
+ @Override
+ WindowState asWindowState() {
+ return mWindowState;
+ }
}
private static class TestWindowContainerBuilder {
@@ -1799,6 +1986,7 @@
private boolean mIsVisible;
private boolean mIsWaitTransitStart;
private Integer mOrientation;
+ private WindowState mWindowState;
TestWindowContainerBuilder(WindowManagerService wm) {
mWm = wm;
@@ -1806,6 +1994,7 @@
mIsAnimating = false;
mIsVisible = false;
mOrientation = null;
+ mWindowState = null;
}
TestWindowContainerBuilder setLayer(int layer) {
@@ -1828,6 +2017,11 @@
return this;
}
+ TestWindowContainerBuilder setAsWindowState(WindowState ws) {
+ mWindowState = ws;
+ return this;
+ }
+
TestWindowContainerBuilder setWaitForTransitionStart(boolean waitTransitStart) {
mIsWaitTransitStart = waitTransitStart;
return this;
@@ -1835,7 +2029,7 @@
TestWindowContainer build() {
return new TestWindowContainer(mWm, mLayer, mIsAnimating, mIsVisible,
- mIsWaitTransitStart, mOrientation);
+ mIsWaitTransitStart, mOrientation, mWindowState);
}
}
diff --git a/services/usb/java/com/UsbDataSignalDisableRequesters.java b/services/usb/java/com/UsbDataSignalDisableRequesters.java
new file mode 100644
index 0000000..d4d6492
--- /dev/null
+++ b/services/usb/java/com/UsbDataSignalDisableRequesters.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.usb;
+
+import android.util.ArraySet;
+
+/**
+ * A helper class to store and manage the request for disabling USB port data signaling.
+ *
+ * External requesters are identified by UIDs.
+ * Internal requesters are identified by a reason code enumerated in UsbManagerInternal.
+ *
+ * @hide
+ */
+public final class UsbDataSignalDisableRequesters {
+ final ArraySet<Integer> mExternalUids = new ArraySet<>();
+ final ArraySet<Integer> mInternalReasons = new ArraySet<>();
+
+ public boolean isEmpty() {
+ return mExternalUids.isEmpty() && mInternalReasons.isEmpty();
+ }
+}
\ No newline at end of file
diff --git a/services/usb/java/com/android/server/usb/UsbManagerInternal.java b/services/usb/java/com/android/server/usb/UsbManagerInternal.java
new file mode 100644
index 0000000..c97df6b
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/UsbManagerInternal.java
@@ -0,0 +1,48 @@
+/*
+ * 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.usb;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.hardware.usb.IUsbOperationInternal;
+import android.hardware.usb.UsbPort;
+import android.util.ArraySet;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * UsbManagerInternal provides internal APIs for the UsbService to
+ * reduce IPC overhead costs and support internal USB data signal stakers.
+ *
+ * @hide Only for use within the system server.
+ */
+public abstract class UsbManagerInternal {
+
+ public static final int OS_USB_DISABLE_REASON_AAPM = 0;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {OS_USB_DISABLE_REASON_AAPM})
+ public @interface OsUsbDisableReason {
+ }
+
+ public abstract boolean enableUsbData(String portId, boolean enable,
+ int operationId, IUsbOperationInternal callback, @OsUsbDisableReason int disableReason);
+
+ public abstract UsbPort[] getPorts();
+
+}
\ No newline at end of file
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index 9470c0a..ba9dff6 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -46,6 +46,7 @@
import android.hardware.usb.UsbManager;
import android.hardware.usb.UsbPort;
import android.hardware.usb.UsbPortStatus;
+
import android.os.Binder;
import android.os.Bundle;
import android.os.Looper;
@@ -69,6 +70,7 @@
import com.android.internal.util.dump.DualDumpOutputStream;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.FgThread;
+import com.android.server.LocalServices;
import com.android.server.SystemServerInitThreadPool;
import com.android.server.SystemService;
@@ -165,8 +167,10 @@
private final Object mLock = new Object();
// Key: USB port id
- // Value: A set of UIDs of requesters who request disabling usb data
- private final ArrayMap<String, ArraySet<Integer>> mUsbDisableRequesters = new ArrayMap<>();
+ // Value: UsbDataSignalDisableRequesters: UIDs of requesters who request
+ // disabling usb data and disable request reasons by local service callers
+ private final ArrayMap<String, UsbDataSignalDisableRequesters>
+ mUsbDisableRequesters = new ArrayMap<>();
/**
* @return the {@link UsbUserSettingsManager} for the given userId
@@ -221,6 +225,9 @@
filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
mContext.registerReceiverAsUser(receiver, UserHandle.ALL, filter, null, null);
+ if(android.hardware.usb.flags.Flags.enableUsbDataSignalStakingInternal()) {
+ LocalServices.addService(UsbManagerInternal.class, new UsbManagerInternalImpl());
+ }
}
// Ideally we should use the injector pattern so we wouldn't need this constructor for test
@@ -236,6 +243,10 @@
mUserManager = userManager;
mSettingsManager = usbSettingsManager;
mPermissionManager = new UsbPermissionManager(context, this);
+
+ if(android.hardware.usb.flags.Flags.enableUsbDataSignalStakingInternal()) {
+ LocalServices.addService(UsbManagerInternal.class, new UsbManagerInternalImpl());
+ }
}
/**
@@ -903,15 +914,21 @@
@Override
public boolean enableUsbData(String portId, boolean enable, int operationId,
IUsbOperationInternal callback) {
- return enableUsbDataInternal(portId, enable, operationId, callback, Binder.getCallingUid());
+ return enableUsbDataInternal(portId, enable, operationId, callback,
+ Binder.getCallingUid(), false);
}
/**
- * Internal function abstracted for testing with callerUid
+ * Manages the enablement of USB data. Requester field could mean two things:
+ * 1. UID of the app that requested USB data to be disabled if caller is external.
+ * 2. Enumberated disable request reason if the caller is internal.
+ *
+ * For internal requests, isInternalRequest should be set to true. Since
+ * internal requests all share the same UID, the request managed separately.
*/
@VisibleForTesting
boolean enableUsbDataInternal(String portId, boolean enable, int operationId,
- IUsbOperationInternal callback, int callerUid) {
+ IUsbOperationInternal callback, int requester, boolean isInternalRequest) {
Objects.requireNonNull(portId, "enableUsbData: portId must not be null. opId:"
+ operationId);
Objects.requireNonNull(callback, "enableUsbData: callback must not be null. opId:"
@@ -919,7 +936,7 @@
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
if (android.hardware.usb.flags.Flags.enableUsbDataSignalStaking()) {
- if (!shouldUpdateUsbSignaling(portId, enable, callerUid)) {
+ if (!shouldUpdateUsbSignaling(portId, enable, requester, isInternalRequest)) {
try {
callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
} catch (RemoteException e) {
@@ -949,25 +966,42 @@
}
/**
+ * Function to determine if USB data signaling state should be updated.
+ * Depending on if request is internal, input requester should be UID or enumerated disable
+ * reason.
+ *
* If enable = true, exclude UID from update list.
* If enable = false, include UID in update list.
* Return false if enable = true and the list is empty (no updates).
* Return true otherwise (let downstream decide on updates).
*/
- private boolean shouldUpdateUsbSignaling(String portId, boolean enable, int uid) {
+ private boolean shouldUpdateUsbSignaling(String portId, boolean enable,
+ int requester, boolean isInternalRequest) {
+ if(isInternalRequest &&
+ !android.hardware.usb.flags.Flags.enableUsbDataSignalStakingInternal())
+ return false;
synchronized (mUsbDisableRequesters) {
if (!mUsbDisableRequesters.containsKey(portId)) {
- mUsbDisableRequesters.put(portId, new ArraySet<>());
+ mUsbDisableRequesters.put(portId, new UsbDataSignalDisableRequesters());
}
-
- ArraySet<Integer> uidsOfDisableRequesters = mUsbDisableRequesters.get(portId);
+ UsbDataSignalDisableRequesters disableRequests =
+ mUsbDisableRequesters.get(portId);
if (enable) {
- uidsOfDisableRequesters.remove(uid);
- // re-enable USB port (return true) if there are no other disable requesters
- return uidsOfDisableRequesters.isEmpty();
+ if(isInternalRequest) {
+ disableRequests.mInternalReasons.remove(requester);
+ } else {
+ disableRequests.mExternalUids.remove(requester);
+ }
+ // re-enable USB port (return true) if there are no other
+ // disable requesters
+ return disableRequests.isEmpty();
} else {
- uidsOfDisableRequesters.add(uid);
+ if(isInternalRequest) {
+ disableRequests.mInternalReasons.add(requester);
+ } else {
+ disableRequests.mExternalUids.add(requester);
+ }
}
}
return true;
@@ -976,7 +1010,8 @@
@Override
public void enableUsbDataWhileDocked(String portId, int operationId,
IUsbOperationInternal callback) {
- enableUsbDataWhileDockedInternal(portId, operationId, callback, Binder.getCallingUid());
+ enableUsbDataWhileDockedInternal(portId, operationId, callback,
+ Binder.getCallingUid(), false);
}
/**
@@ -984,7 +1019,7 @@
*/
@VisibleForTesting
void enableUsbDataWhileDockedInternal(String portId, int operationId,
- IUsbOperationInternal callback, int callerUid) {
+ IUsbOperationInternal callback, int callerUid, boolean isInternalRequest) {
Objects.requireNonNull(portId, "enableUsbDataWhileDocked: portId must not be null. opId:"
+ operationId);
Objects.requireNonNull(callback,
@@ -993,7 +1028,7 @@
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
if (android.hardware.usb.flags.Flags.enableUsbDataSignalStaking()) {
- if (!shouldUpdateUsbSignaling(portId, true, callerUid)) {
+ if (!shouldUpdateUsbSignaling(portId, true, callerUid, isInternalRequest)) {
try {
callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
} catch (RemoteException e) {
@@ -1455,10 +1490,11 @@
public void onUidRemoved(int uid) {
synchronized (mUsbDisableRequesters) {
for (String portId : mUsbDisableRequesters.keySet()) {
- ArraySet<Integer> disabledUid = mUsbDisableRequesters.get(portId);
- if (disabledUid != null) {
- disabledUid.remove(uid);
- if (disabledUid.isEmpty()) {
+ UsbDataSignalDisableRequesters disableRequesters =
+ mUsbDisableRequesters.get(portId);
+ if (disableRequesters != null) {
+ disableRequesters.mExternalUids.remove(uid);
+ if (disableRequesters.isEmpty()) {
enableUsbData(portId, true, PACKAGE_MONITOR_OPERATION_ID,
new IUsbOperationInternal.Default());
}
@@ -1496,4 +1532,19 @@
}
}
}
+
+ private class UsbManagerInternalImpl extends UsbManagerInternal {
+ @Override
+ public boolean enableUsbData(String portId, boolean enable,
+ int operationId, IUsbOperationInternal callback,
+ @OsUsbDisableReason int disableReason) {
+ return enableUsbDataInternal(portId, enable, operationId, callback,
+ disableReason, true);
+ }
+
+ @Override
+ public UsbPort[] getPorts() {
+ return mPortManager.getPorts();
+ }
+ }
}
diff --git a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java
index ff9cba2..f001232 100644
--- a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java
+++ b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java
@@ -33,6 +33,7 @@
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.flags.Flags;
import com.android.internal.telephony.util.TelephonyUtils;
import java.util.ArrayList;
@@ -185,11 +186,7 @@
if (hasPrivileges) {
// Only update enabled state for the app on /system. Once it has been
// updated we shouldn't touch it.
- if (!isUpdatedSystemApp(ai) && enabledSetting
- == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
- || enabledSetting
- == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
- || (ai.flags & ApplicationInfo.FLAG_INSTALLED) == 0) {
+ if (shouldUpdateEnabledState(ai, enabledSetting)) {
Log.i(TAG, "Update state (" + packageName + "): ENABLED for user "
+ userId);
context.createContextAsUser(UserHandle.of(userId), 0)
@@ -330,6 +327,21 @@
}
}
+ private static boolean shouldUpdateEnabledState(ApplicationInfo appInfo, int enabledSetting) {
+ if (Flags.cleanupCarrierAppUpdateEnabledStateLogic()) {
+ return !isUpdatedSystemApp(appInfo)
+ && (enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
+ || enabledSetting
+ == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
+ || (appInfo.flags & ApplicationInfo.FLAG_INSTALLED) == 0);
+ } else {
+ return !isUpdatedSystemApp(appInfo)
+ && enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
+ || enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
+ || (appInfo.flags & ApplicationInfo.FLAG_INSTALLED) == 0;
+ }
+ }
+
/**
* Returns the list of "default" carrier apps.
*
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index ff966ae..f01cfc1 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -2741,7 +2741,7 @@
/**
* Returns a constant indicating the device phone type. This
- * indicates the type of radio used to transmit voice calls.
+ * indicates the type of radio used to transmit voice/data calls.
*
* @see #PHONE_TYPE_NONE
* @see #PHONE_TYPE_GSM
@@ -2753,7 +2753,7 @@
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY)
public int getPhoneType() {
- if (!isVoiceCapable()) {
+ if (!isVoiceCapable() && !isDataCapable()) {
return PHONE_TYPE_NONE;
}
return getCurrentPhoneType();
diff --git a/tests/FlickerTests/ActivityEmbedding/Android.bp b/tests/FlickerTests/ActivityEmbedding/Android.bp
index c681ce9..529f84a 100644
--- a/tests/FlickerTests/ActivityEmbedding/Android.bp
+++ b/tests/FlickerTests/ActivityEmbedding/Android.bp
@@ -24,71 +24,6 @@
default_applicable_licenses: ["frameworks_base_license"],
}
-////////////////////////////////////////////////////////////////////////////////
-// Begin to cleanup after CL merges
-
-filegroup {
- name: "FlickerTestsOtherCommon-src",
- srcs: ["src/**/ActivityEmbeddingTestBase.kt"],
-}
-
-filegroup {
- name: "FlickerTestsOtherOpen-src",
- srcs: ["src/**/open/*"],
-}
-
-filegroup {
- name: "FlickerTestsOtherRotation-src",
- srcs: ["src/**/rotation/*"],
-}
-
-java_library {
- name: "FlickerTestsOtherCommon",
- defaults: ["FlickerTestsDefault"],
- srcs: [":FlickerTestsOtherCommon-src"],
- static_libs: ["FlickerTestsBase"],
-}
-
-java_defaults {
- name: "FlickerTestsOtherDefaults",
- defaults: ["FlickerTestsDefault"],
- manifest: "AndroidManifest.xml",
- package_name: "com.android.server.wm.flicker",
- instrumentation_target_package: "com.android.server.wm.flicker",
- test_config_template: "AndroidTestTemplate.xml",
- static_libs: [
- "FlickerTestsBase",
- "FlickerTestsOtherCommon",
- ],
- data: ["trace_config/*"],
-}
-
-android_test {
- name: "FlickerTestsOtherOpen",
- defaults: ["FlickerTestsOtherDefaults"],
- srcs: [":FlickerTestsOtherOpen-src"],
-}
-
-android_test {
- name: "FlickerTestsOtherRotation",
- defaults: ["FlickerTestsOtherDefaults"],
- srcs: [":FlickerTestsOtherRotation-src"],
-}
-
-android_test {
- name: "FlickerTestsOther",
- defaults: ["FlickerTestsOtherDefaults"],
- srcs: ["src/**/*"],
- exclude_srcs: [
- ":FlickerTestsOtherOpen-src",
- ":FlickerTestsOtherRotation-src",
- ":FlickerTestsOtherCommon-src",
- ],
-}
-
-// End to cleanup after CL merges
-////////////////////////////////////////////////////////////////////////////////
-
android_test {
name: "FlickerTestsActivityEmbedding",
defaults: ["FlickerTestsDefault"],
@@ -97,10 +32,7 @@
instrumentation_target_package: "com.android.server.wm.flicker",
test_config_template: "AndroidTestTemplate.xml",
srcs: ["src/**/*"],
- static_libs: [
- "FlickerTestsBase",
- "FlickerTestsOtherCommon",
- ],
+ static_libs: ["FlickerTestsBase"],
data: ["trace_config/*"],
}
diff --git a/tests/FlickerTests/ActivityEmbedding/OWNERS b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/OWNERS
similarity index 100%
rename from tests/FlickerTests/ActivityEmbedding/OWNERS
rename to tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/OWNERS
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/close/CloseSecondaryActivityInSplitTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/close/CloseSecondaryActivityInSplitTest.kt
index 519b429..f44e282 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/close/CloseSecondaryActivityInSplitTest.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/close/CloseSecondaryActivityInSplitTest.kt
@@ -38,7 +38,7 @@
* Setup: Launch A|B in split with B being the secondary activity. Transitions: Finish B and expect
* A to become fullscreen.
*
- * To run this test: `atest FlickerTestsOther:CloseSecondaryActivityInSplitTest`
+ * To run this test: `atest FlickerTestsActivityEmbedding:CloseSecondaryActivityInSplitTest`
*/
@RequiresDevice
@RunWith(Parameterized::class)
diff --git a/tests/FlickerTests/ActivityEmbedding/OWNERS b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/close/OWNERS
similarity index 100%
copy from tests/FlickerTests/ActivityEmbedding/OWNERS
copy to tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/close/OWNERS
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/layoutchange/HorizontalSplitChangeRatioTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/layoutchange/HorizontalSplitChangeRatioTest.kt
index 4cd6d15b..7a76dd9 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/layoutchange/HorizontalSplitChangeRatioTest.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/layoutchange/HorizontalSplitChangeRatioTest.kt
@@ -39,7 +39,7 @@
* windows are equal in size. B is on the top and A is on the bottom. Transitions: Change the split
* ratio to A:B=0.7:0.3, expect bounds change for both A and B.
*
- * To run this test: `atest FlickerTestsOther:HorizontalSplitChangeRatioTest`
+ * To run this test: `atest FlickerTestsActivityEmbedding:HorizontalSplitChangeRatioTest`
*/
@RequiresDevice
@RunWith(Parameterized::class)
diff --git a/tests/FlickerTests/ActivityEmbedding/OWNERS b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/layoutchange/OWNERS
similarity index 100%
copy from tests/FlickerTests/ActivityEmbedding/OWNERS
copy to tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/layoutchange/OWNERS
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt
index 5df8b572..08b5f38 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt
@@ -39,7 +39,7 @@
* Setup: Launch A|B in split with B being the secondary activity. Transitions: A start C with
* alwaysExpand=true, expect C to launch in fullscreen and cover split A|B.
*
- * To run this test: `atest FlickerTestsOther:MainActivityStartsSecondaryWithAlwaysExpandTest`
+ * To run this test: `atest FlickerTestsActivityEmbedding:MainActivityStartsSecondaryWithAlwaysExpandTest`
*/
@RequiresDevice
@RunWith(Parameterized::class)
diff --git a/tests/FlickerTests/ActivityEmbedding/OWNERS b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OWNERS
similarity index 100%
copy from tests/FlickerTests/ActivityEmbedding/OWNERS
copy to tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OWNERS
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingPlaceholderSplitTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingPlaceholderSplitTest.kt
index 5009c7c..1f002a0 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingPlaceholderSplitTest.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingPlaceholderSplitTest.kt
@@ -34,7 +34,7 @@
* Test opening an activity that will launch another activity as ActivityEmbedding placeholder in
* split.
*
- * To run this test: `atest FlickerTestsOther:OpenActivityEmbeddingPlaceholderSplitTest`
+ * To run this test: `atest FlickerTestsActivityEmbedding:OpenActivityEmbeddingPlaceholderSplitTest`
*/
@RequiresDevice
@RunWith(Parameterized::class)
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingSecondaryToSplitTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingSecondaryToSplitTest.kt
index 6327d92..b78c3ec 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingSecondaryToSplitTest.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingSecondaryToSplitTest.kt
@@ -34,7 +34,7 @@
/**
* Test opening a secondary activity that will split with the main activity.
*
- * To run this test: `atest FlickerTestsOther:OpenActivityEmbeddingSecondaryToSplitTest`
+ * To run this test: `atest FlickerTestsActivityEmbedding:OpenActivityEmbeddingSecondaryToSplitTest`
*/
@RequiresDevice
@RunWith(Parameterized::class)
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenThirdActivityOverSplitTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenThirdActivityOverSplitTest.kt
index 78004cc..10167d71 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenThirdActivityOverSplitTest.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenThirdActivityOverSplitTest.kt
@@ -39,7 +39,7 @@
*
* Transitions: Let B start C, expect C to cover B and end up in split A|C.
*
- * To run this test: `atest FlickerTestsOther:OpenThirdActivityOverSplitTest`
+ * To run this test: `atest FlickerTestsActivityEmbedding:OpenThirdActivityOverSplitTest`
*/
@RequiresDevice
@RunWith(Parameterized::class)
diff --git a/tests/FlickerTests/ActivityEmbedding/OWNERS b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/OWNERS
similarity index 100%
copy from tests/FlickerTests/ActivityEmbedding/OWNERS
copy to tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/OWNERS
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt
index eed9225..a0b910b 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt
@@ -41,7 +41,7 @@
* Setup: Start from a split A|B. Transition: B enters PIP, observe the window first goes fullscreen
* then shrink to the bottom right corner on screen.
*
- * To run this test: `atest FlickerTestsOther:SecondaryActivityEnterPipTest`
+ * To run this test: `atest FlickerTestsActivityEmbedding:SecondaryActivityEnterPipTest`
*/
@RequiresDevice
@RunWith(Parameterized::class)
diff --git a/tests/FlickerTests/ActivityEmbedding/OWNERS b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/OWNERS
similarity index 100%
copy from tests/FlickerTests/ActivityEmbedding/OWNERS
copy to tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/OWNERS
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotateSplitNoChangeTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotateSplitNoChangeTest.kt
index f5e6c78..ea13f5f 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotateSplitNoChangeTest.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotateSplitNoChangeTest.kt
@@ -36,7 +36,7 @@
* Setup: Launch A|B in split with B being the secondary activity. Transitions: Rotate display, and
* expect A and B to split evenly in new rotation.
*
- * To run this test: `atest FlickerTestsOther:RotateSplitNoChangeTest`
+ * To run this test: `atest FlickerTestsActivityEmbedding:RotateSplitNoChangeTest`
*/
@RequiresDevice
@RunWith(Parameterized::class)
diff --git a/tests/FlickerTests/ActivityEmbedding/OWNERS b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rtl/OWNERS
similarity index 100%
copy from tests/FlickerTests/ActivityEmbedding/OWNERS
copy to tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rtl/OWNERS
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rtl/RTLStartSecondaryWithPlaceholderTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rtl/RTLStartSecondaryWithPlaceholderTest.kt
index 65a23e8..2a177d5 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rtl/RTLStartSecondaryWithPlaceholderTest.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rtl/RTLStartSecondaryWithPlaceholderTest.kt
@@ -37,7 +37,7 @@
* PlaceholderPrimary, which is configured to launch with PlaceholderSecondary in RTL. Expect split
* PlaceholderSecondary|PlaceholderPrimary covering split B|A.
*
- * To run this test: `atest FlickerTestsOther:RTLStartSecondaryWithPlaceholderTest`
+ * To run this test: `atest FlickerTestsActivityEmbedding:RTLStartSecondaryWithPlaceholderTest`
*/
@RequiresDevice
@RunWith(Parameterized::class)
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt
index c3e1a1f..0ca8f37 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt
@@ -47,7 +47,7 @@
* Setup: Launch A|B in split and secondaryApp, return to home. Transitions: Let AE Split A|B enter
* splitscreen with secondaryApp. Resulting in A|B|secondaryApp.
*
- * To run this test: `atest FlickerTestsOther:EnterSystemSplitTest`
+ * To run this test: `atest FlickerTestsActivityEmbedding:EnterSystemSplitTest`
*/
@RequiresDevice
@RunWith(Parameterized::class)
diff --git a/tests/FlickerTests/ActivityEmbedding/OWNERS b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/OWNERS
similarity index 100%
copy from tests/FlickerTests/ActivityEmbedding/OWNERS
copy to tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/OWNERS
diff --git a/tests/FlickerTests/AppClose/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt b/tests/FlickerTests/AppClose/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
index e19e1ce..56b718a 100644
--- a/tests/FlickerTests/AppClose/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
+++ b/tests/FlickerTests/AppClose/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
@@ -31,7 +31,7 @@
/**
* Test app closes by pressing back button
*
- * To run this test: `atest FlickerTests:CloseAppBackButtonTest`
+ * To run this test: `atest FlickerTestsAppClose:CloseAppBackButtonTest`
*
* Actions:
* ```
diff --git a/tests/FlickerTests/AppClose/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt b/tests/FlickerTests/AppClose/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
index 47ed642..5deacaf 100644
--- a/tests/FlickerTests/AppClose/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
+++ b/tests/FlickerTests/AppClose/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
@@ -31,7 +31,7 @@
/**
* Test app closes by pressing home button
*
- * To run this test: `atest FlickerTests:CloseAppHomeButtonTest`
+ * To run this test: `atest FlickerTestsAppClose:CloseAppHomeButtonTest`
*
* Actions:
* ```
diff --git a/tests/FlickerTests/AppLaunch/Android.bp b/tests/FlickerTests/AppLaunch/Android.bp
index b61739f..17d0f96 100644
--- a/tests/FlickerTests/AppLaunch/Android.bp
+++ b/tests/FlickerTests/AppLaunch/Android.bp
@@ -24,69 +24,13 @@
default_applicable_licenses: ["frameworks_base_license"],
}
-////////////////////////////////////////////////////////////////////////////////
-// Begin to cleanup after CL merges
-
-filegroup {
- name: "FlickerTestsAppLaunchCommon-src",
- srcs: ["src/**/common/*"],
-}
-
-filegroup {
- name: "FlickerTestsAppLaunch1-src",
- srcs: ["src/**/OpenAppFrom*"],
-}
-
-java_library {
- name: "FlickerTestsAppLaunchCommon",
- defaults: ["FlickerTestsDefault"],
- srcs: [":FlickerTestsAppLaunchCommon-src"],
- static_libs: ["FlickerTestsBase"],
-}
-
-android_test {
- name: "FlickerTestsAppLaunch1",
- defaults: ["FlickerTestsDefault"],
- manifest: "AndroidManifest.xml",
- test_config_template: "AndroidTestTemplate.xml",
- srcs: [":FlickerTestsAppLaunch1-src"],
- static_libs: [
- "FlickerTestsBase",
- "FlickerTestsAppLaunchCommon",
- ],
- data: ["trace_config/*"],
-}
-
-android_test {
- name: "FlickerTestsAppLaunch2",
- defaults: ["FlickerTestsDefault"],
- manifest: "AndroidManifest.xml",
- test_config_template: "AndroidTestTemplate.xml",
- srcs: ["src/**/*"],
- exclude_srcs: [
- ":FlickerTestsAppLaunchCommon-src",
- ":FlickerTestsAppLaunch1-src",
- ],
- static_libs: [
- "FlickerTestsBase",
- "FlickerTestsAppLaunchCommon",
- ],
- data: ["trace_config/*"],
-}
-
-// End to cleanup after CL merges
-////////////////////////////////////////////////////////////////////////////////
-
android_test {
name: "FlickerTestsAppLaunch",
defaults: ["FlickerTestsDefault"],
manifest: "AndroidManifest.xml",
test_config_template: "AndroidTestTemplate.xml",
srcs: ["src/**/*"],
- static_libs: [
- "FlickerTestsBase",
- "FlickerTestsAppLaunchCommon",
- ],
+ static_libs: ["FlickerTestsBase"],
data: ["trace_config/*"],
}
diff --git a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/ActivityTransitionTest.kt b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/ActivityTransitionTest.kt
index ffa90a3..01cdbb8 100644
--- a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/ActivityTransitionTest.kt
+++ b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/ActivityTransitionTest.kt
@@ -35,7 +35,7 @@
/**
* Test the back and forward transition between 2 activities.
*
- * To run this test: `atest FlickerTests:ActivitiesTransitionTest`
+ * To run this test: `atest FlickerTestsAppLaunch:ActivitiesTransitionTest`
*
* Actions:
* ```
diff --git a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt
index 8c285bd..3d9321c 100644
--- a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt
+++ b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt
@@ -30,7 +30,7 @@
/**
* Test cold launching an app from launcher
*
- * To run this test: `atest FlickerTests:OpenAppColdFromIcon`
+ * To run this test: `atest FlickerTestsAppLaunch:OpenAppColdFromIcon`
*
* Actions:
* ```
diff --git a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdAfterCameraTest.kt b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdAfterCameraTest.kt
index 57da05f..9207530 100644
--- a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdAfterCameraTest.kt
+++ b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdAfterCameraTest.kt
@@ -30,7 +30,7 @@
/**
* Test launching an app after cold opening camera
*
- * To run this test: `atest FlickerTests:OpenAppAfterCameraTest`
+ * To run this test: `atest FlickerTestsAppLaunch:OpenAppAfterCameraTest`
*
* Notes: Some default assertions are inherited [OpenAppTransition]
*/
diff --git a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt
index 267f282..cbe7c32 100644
--- a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt
+++ b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt
@@ -35,7 +35,7 @@
/**
* Test cold launching an app from launcher
*
- * To run this test: `atest FlickerTests:OpenAppColdTest`
+ * To run this test: `atest FlickerTestsAppLaunch:OpenAppColdTest`
*
* Actions:
* ```
diff --git a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt
index 83065de..b2941e7 100644
--- a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt
+++ b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt
@@ -34,7 +34,7 @@
/**
* Test warm launching an app from launcher
*
- * To run this test: `atest FlickerTests:OpenAppWarmTest`
+ * To run this test: `atest FlickerTestsAppLaunch:OpenAppWarmTest`
*
* Actions:
* ```
diff --git a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt
index 6e6a327..4048e0c 100644
--- a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt
+++ b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt
@@ -41,7 +41,7 @@
*
* This test assumes the device doesn't have AOD enabled
*
- * To run this test: `atest FlickerTests:OpenAppNonResizeableTest`
+ * To run this test: `atest FlickerTestsAppLaunch:OpenAppNonResizeableTest`
*
* Actions:
* ```
diff --git a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
index 6d3eaeb..064c76f 100644
--- a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
+++ b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
@@ -35,7 +35,7 @@
/**
* Test launching an app from the recents app view (the overview)
*
- * To run this test: `atest FlickerTests:OpenAppFromOverviewTest`
+ * To run this test: `atest FlickerTestsAppLaunch:OpenAppFromOverviewTest`
*
* Actions:
* ```
diff --git a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt
index bec02d0..41423fd 100644
--- a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt
+++ b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt
@@ -41,7 +41,7 @@
/**
* Test cold launching camera from launcher by double pressing power button
*
- * To run this test: `atest FlickerTests:OpenCameraOnDoubleClickPowerButton`
+ * To run this test: `atest FlickerTestsAppLaunch:OpenCameraOnDoubleClickPowerButton`
*
* Actions:
* ```
diff --git a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenTransferSplashscreenAppFromLauncherTransition.kt b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenTransferSplashscreenAppFromLauncherTransition.kt
index e0aef8d..9d7a9c6 100644
--- a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenTransferSplashscreenAppFromLauncherTransition.kt
+++ b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenTransferSplashscreenAppFromLauncherTransition.kt
@@ -34,7 +34,7 @@
/**
* Test cold launching an app from launcher
*
- * To run this test: `atest FlickerTests:OpenTransferSplashscreenAppFromLauncherTransition`
+ * To run this test: `atest FlickerTestsAppLaunch:OpenTransferSplashscreenAppFromLauncherTransition`
*
* Actions:
* ```
diff --git a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt
index f114499..7e2d472 100644
--- a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt
+++ b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt
@@ -42,7 +42,7 @@
/**
* Test the [android.app.ActivityOptions.makeCustomTaskAnimation].
*
- * To run this test: `atest FlickerTests:OverrideTaskTransitionTest`
+ * To run this test: `atest FlickerTestsAppLaunch:OverrideTaskTransitionTest`
*
* Actions:
* ```
diff --git a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
index a71599d..95e8126 100644
--- a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
+++ b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
@@ -49,7 +49,7 @@
/**
* Test the back and forward transition between 2 activities.
*
- * To run this test: `atest FlickerTests:TaskTransitionTest`
+ * To run this test: `atest FlickerTestsAppLaunch:TaskTransitionTest`
*
* Actions:
* ```
diff --git a/tests/FlickerTests/IME/Android.bp b/tests/FlickerTests/IME/Android.bp
index f80e6b4..cba3d09 100644
--- a/tests/FlickerTests/IME/Android.bp
+++ b/tests/FlickerTests/IME/Android.bp
@@ -24,27 +24,6 @@
default_applicable_licenses: ["frameworks_base_license"],
}
-////////////////////////////////////////////////////////////////////////////////
-// Begin to cleanup after CL merges
-
-filegroup {
- name: "FlickerTestsImeCommon-src",
- srcs: ["src/**/common/*"],
-}
-
-filegroup {
- name: "FlickerTestsIme1-src",
- srcs: ["src/**/Close*"],
-}
-
-filegroup {
- name: "FlickerTestsIme2-src",
- srcs: ["src/**/ShowImeOnAppStart*"],
-}
-
-// End to cleanup after CL merges
-////////////////////////////////////////////////////////////////////////////////
-
android_test {
name: "FlickerTestsIme",
defaults: ["FlickerTestsDefault"],
@@ -60,67 +39,6 @@
}
////////////////////////////////////////////////////////////////////////////////
-// Begin to cleanup after CL merges
-
-java_library {
- name: "FlickerTestsImeCommon",
- defaults: ["FlickerTestsDefault"],
- srcs: [":FlickerTestsImeCommon-src"],
- static_libs: ["FlickerTestsBase"],
-}
-
-android_test {
- name: "FlickerTestsIme1",
- defaults: ["FlickerTestsDefault"],
- manifest: "AndroidManifest.xml",
- test_config_template: "AndroidTestTemplate.xml",
- test_suites: [
- "device-tests",
- "device-platinum-tests",
- ],
- srcs: [":FlickerTestsIme1-src"],
- static_libs: [
- "FlickerTestsBase",
- "FlickerTestsImeCommon",
- ],
- data: ["trace_config/*"],
-}
-
-android_test {
- name: "FlickerTestsIme2",
- defaults: ["FlickerTestsDefault"],
- manifest: "AndroidManifest.xml",
- test_config_template: "AndroidTestTemplate.xml",
- srcs: [":FlickerTestsIme2-src"],
- static_libs: [
- "FlickerTestsBase",
- "FlickerTestsImeCommon",
- ],
- data: ["trace_config/*"],
-}
-
-android_test {
- name: "FlickerTestsIme3",
- defaults: ["FlickerTestsDefault"],
- manifest: "AndroidManifest.xml",
- test_config_template: "AndroidTestTemplate.xml",
- srcs: ["src/**/*"],
- exclude_srcs: [
- ":FlickerTestsIme1-src",
- ":FlickerTestsIme2-src",
- ":FlickerTestsImeCommon-src",
- ],
- static_libs: [
- "FlickerTestsBase",
- "FlickerTestsImeCommon",
- ],
- data: ["trace_config/*"],
-}
-
-// End to cleanup after CL merges
-////////////////////////////////////////////////////////////////////////////////
-
-////////////////////////////////////////////////////////////////////////////////
// Begin breakdowns for FlickerTestsIme module
test_module_config {
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 2b6ddcb..48ca36f 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
@@ -33,7 +33,7 @@
import org.junit.runners.Parameterized
/**
- * To run this test: `atest FlickerTestsIme1:CloseImeOnDismissPopupDialogTest`
+ * To run this test: `atest FlickerTestsIme:CloseImeOnDismissPopupDialogTest`
*/
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
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 0344197..e3f3aca 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
@@ -34,7 +34,7 @@
/**
* Test IME window closing to home transitions.
- * To run this test: `atest FlickerTestsIme1:CloseImeOnGoHomeTest`
+ * To run this test: `atest FlickerTestsIme:CloseImeOnGoHomeTest`
*/
@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 fde1373..3509e5b 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 @@
*
* More details on b/190352379
*
- * To run this test: `atest FlickerTestsIme1:CloseImeShownOnAppStartOnGoHomeTest`
+ * To run this test: `atest FlickerTestsIme:CloseImeShownOnAppStartOnGoHomeTest`
*/
@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 ed6e8df..53d7a3f 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
@@ -43,7 +43,7 @@
*
* More details on b/190352379
*
- * To run this test: `atest FlickerTestsIme1:CloseImeShownOnAppStartToAppOnPressBackTest`
+ * To run this test: `atest FlickerTestsIme:CloseImeShownOnAppStartToAppOnPressBackTest`
*/
@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 522c68b..4bc2705 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
@@ -35,7 +35,7 @@
/**
* Test IME window closing back to app window transitions.
- * To run this test: `atest FlickerTestsIme1:CloseImeToAppOnPressBackTest`
+ * To run this test: `atest FlickerTestsIme:CloseImeToAppOnPressBackTest`
*/
@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 05771e8..6117bb0 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 @@
* 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 FlickerTestsIme:CloseImeToHomeOnFinishActivityTest`
*/
@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 336fe6f..9b8d86d 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
@@ -37,7 +37,7 @@
/**
* Test IME window shown on the app with fixing portrait orientation.
- * To run this test: `atest FlickerTestsIme2:OpenImeWindowToFixedPortraitAppTest`
+ * To run this test: `atest FlickerTestsIme:OpenImeWindowToFixedPortraitAppTest`
*/
@RequiresDevice
@RunWith(Parameterized::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 34a7085..f806fae 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
@@ -34,7 +34,7 @@
/**
* Test IME window opening transitions.
- * To run this test: `atest FlickerTestsIme2:ShowImeOnAppStartWhenLaunchingAppFromOverviewTest`
+ * To run this test: `atest FlickerTestsIme:ShowImeOnAppStartWhenLaunchingAppFromOverviewTest`
*/
@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 7c72c31..cc19f62 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
@@ -36,7 +36,7 @@
/**
* Test IME windows switching with 2-Buttons or gestural navigation.
- * To run this test: `atest FlickerTestsIme2:ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest`
+ * To run this test: `atest FlickerTestsIme:ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest`
*/
@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 fe5320c..4a4d372 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 @@
/**
* Launch an app that automatically displays the IME
*
- * To run this test: `atest FlickerTestsIme2:ShowImeOnAppStartWhenLaunchingAppTest`
+ * To run this test: `atest FlickerTestsIme:ShowImeOnAppStartWhenLaunchingAppTest`
*
* 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 82e53c8..d47e7ad 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
@@ -36,7 +36,7 @@
/**
* Test IME window closing on lock and opening on screen unlock.
- * To run this test: `atest FlickerTestsIme2:ShowImeOnUnlockScreenTest`
+ * To run this test: `atest FlickerTestsIme:ShowImeOnUnlockScreenTest`
*/
@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 9eaf998..47bf324 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
@@ -33,7 +33,7 @@
/**
* Test IME window opening transitions.
- * To run this test: `atest FlickerTestsIme2:ShowImeWhenFocusingOnInputFieldTest`
+ * To run this test: `atest FlickerTestsIme:ShowImeWhenFocusingOnInputFieldTest`
*/
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
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 7186a2c..e3118b4 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 @@
/**
* 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 FlickerTestsIme:ShowImeWhileDismissingThemedPopupDialogTest`
*/
@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 eb63e49..064c07e 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
@@ -39,7 +39,7 @@
/**
* Test IME window layer will be associated with the app task when going to the overview screen.
- * To run this test: `atest FlickerTestsIme2:ShowImeWhileEnteringOverviewTest`
+ * To run this test: `atest FlickerTestsIme:ShowImeWhileEnteringOverviewTest`
*/
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
diff --git a/tests/FlickerTests/QuickSwitch/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt b/tests/FlickerTests/QuickSwitch/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
index 9bb62e1..1a32f20 100644
--- a/tests/FlickerTests/QuickSwitch/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
+++ b/tests/FlickerTests/QuickSwitch/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
@@ -38,7 +38,7 @@
/**
* Test quick switching back to previous app from last opened app
*
- * To run this test: `atest FlickerTests:QuickSwitchBetweenTwoAppsBackTest`
+ * To run this test: `atest FlickerTestsQuickswitch:QuickSwitchBetweenTwoAppsBackTest`
*
* Actions:
* ```
diff --git a/tests/FlickerTests/QuickSwitch/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt b/tests/FlickerTests/QuickSwitch/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
index 491b994..d82dddd 100644
--- a/tests/FlickerTests/QuickSwitch/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
+++ b/tests/FlickerTests/QuickSwitch/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
@@ -37,7 +37,7 @@
/**
* Test quick switching back to previous app from last opened app
*
- * To run this test: `atest FlickerTests:QuickSwitchBetweenTwoAppsForwardTest`
+ * To run this test: `atest FlickerTestsQuickswitch:QuickSwitchBetweenTwoAppsForwardTest`
*
* Actions:
* ```
diff --git a/tests/FlickerTests/QuickSwitch/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt b/tests/FlickerTests/QuickSwitch/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
index de54c95..ab36628 100644
--- a/tests/FlickerTests/QuickSwitch/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
+++ b/tests/FlickerTests/QuickSwitch/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
@@ -38,7 +38,7 @@
/**
* Test quick switching to last opened app from launcher
*
- * To run this test: `atest FlickerTests:QuickSwitchFromLauncherTest`
+ * To run this test: `atest FlickerTestsQuickswitch:QuickSwitchFromLauncherTest`
*
* Actions:
* ```
diff --git a/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
index 05ab364..49e2553 100644
--- a/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
+++ b/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
@@ -48,7 +48,7 @@
* Stop tracing
* ```
*
- * To run this test: `atest FlickerTests:ChangeAppRotationTest`
+ * To run this test: `atest FlickerTestsRotation:ChangeAppRotationTest`
*
* To run only the presubmit assertions add: `--
*
diff --git a/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
index a413628..d7f91e0 100644
--- a/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
+++ b/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
@@ -55,7 +55,7 @@
* Stop tracing
* ```
*
- * To run this test: `atest FlickerTests:SeamlessAppRotationTest`
+ * To run this test: `atest FlickerTestsRotation:SeamlessAppRotationTest`
*
* To run only the presubmit assertions add: `--
*
diff --git a/tests/Internal/src/com/android/internal/os/ApplicationSharedMemoryTest.java b/tests/Internal/src/com/android/internal/os/ApplicationSharedMemoryTest.java
index e3a129f..d03ad5c 100644
--- a/tests/Internal/src/com/android/internal/os/ApplicationSharedMemoryTest.java
+++ b/tests/Internal/src/com/android/internal/os/ApplicationSharedMemoryTest.java
@@ -61,8 +61,13 @@
@Test
public void canRead() {
ApplicationSharedMemory instance = ApplicationSharedMemory.getInstance();
- instance.getLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis();
- // Don't actually care about the value of the above.
+ try {
+ instance.getLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis();
+ // Don't actually care about the value of the above.
+ } catch (java.time.DateTimeException e) {
+ // This exception is okay during testing. It means there was no time source, which
+ // could be because of network problems or a feature being flagged off.
+ }
}
/** Application processes should not have mutable access. */
diff --git a/tests/Internal/src/com/android/internal/os/OWNERS b/tests/Internal/src/com/android/internal/os/OWNERS
new file mode 100644
index 0000000..64ffa46
--- /dev/null
+++ b/tests/Internal/src/com/android/internal/os/OWNERS
@@ -0,0 +1,2 @@
+# ApplicationSharedMemory
+per-file *ApplicationSharedMemory* = file:/PERFORMANCE_OWNERS
diff --git a/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java b/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java
index 56845ae..51d57f0 100644
--- a/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java
+++ b/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java
@@ -18,6 +18,10 @@
import static android.hardware.usb.UsbOperationInternal.USB_OPERATION_ERROR_INTERNAL;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -31,12 +35,15 @@
import android.content.Context;
import android.hardware.usb.IUsbOperationInternal;
import android.hardware.usb.flags.Flags;
+import android.hardware.usb.UsbPort;
import android.os.RemoteException;
import android.os.UserManager;
import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.runner.AndroidJUnit4;
+import com.android.server.LocalServices;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -71,26 +78,38 @@
private static final int TEST_SECOND_CALLER_ID = 2000;
+ private static final int TEST_INTERNAL_REQUESTER_REASON_1 = 100;
+
+ private static final int TEST_INTERNAL_REQUESTER_REASON_2 = 200;
+
private UsbService mUsbService;
+ private UsbManagerInternal mUsbManagerInternal;
+
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Before
public void setUp() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_USB_DATA_SIGNAL_STAKING);
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_USB_DATA_SIGNAL_STAKING_INTERNAL);
+ LocalServices.removeAllServicesForTest();
MockitoAnnotations.initMocks(this);
- when(mUsbPortManager.enableUsbData(eq(TEST_PORT_ID), anyBoolean(), eq(TEST_TRANSACTION_ID),
- eq(mCallback), any())).thenReturn(true);
+ when(mUsbPortManager.enableUsbData(eq(TEST_PORT_ID), anyBoolean(),
+ eq(TEST_TRANSACTION_ID), eq(mCallback), any())).thenReturn(true);
mUsbService = new UsbService(mContext, mUsbPortManager, mUsbAlsaManager,
mUserManager, mUsbSettingsManager);
+ mUsbManagerInternal = LocalServices.getService(UsbManagerInternal.class);
+ assertWithMessage("LocalServices.getService(UsbManagerInternal.class)")
+ .that(mUsbManagerInternal).isNotNull();
}
- private void assertToggleUsbSuccessfully(int uid, boolean enable) {
+ private void assertToggleUsbSuccessfully(int requester, boolean enable,
+ boolean isInternalRequest) {
assertTrue(mUsbService.enableUsbDataInternal(TEST_PORT_ID, enable,
- TEST_TRANSACTION_ID, mCallback, uid));
+ TEST_TRANSACTION_ID, mCallback, requester, isInternalRequest));
verify(mUsbPortManager).enableUsbData(TEST_PORT_ID,
enable, TEST_TRANSACTION_ID, mCallback, null);
@@ -100,9 +119,10 @@
clearInvocations(mCallback);
}
- private void assertToggleUsbFailed(int uid, boolean enable) throws Exception {
+ private void assertToggleUsbFailed(int requester, boolean enable,
+ boolean isInternalRequest) throws Exception {
assertFalse(mUsbService.enableUsbDataInternal(TEST_PORT_ID, enable,
- TEST_TRANSACTION_ID, mCallback, uid));
+ TEST_TRANSACTION_ID, mCallback, requester, isInternalRequest));
verifyZeroInteractions(mUsbPortManager);
verify(mCallback).onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
@@ -116,15 +136,16 @@
*/
@Test
public void disableUsb_successfullyDisable() {
- assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false);
+ assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false, false);
}
/**
- * Verify enableUsbData successfully enables USB port without error given no other stakers
+ * Verify enableUsbData successfully enables USB port without error given
+ * no other stakers
*/
@Test
public void enableUsbWhenNoOtherStakers_successfullyEnable() {
- assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, true);
+ assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, true, false);
}
/**
@@ -132,47 +153,132 @@
*/
@Test
public void enableUsbPortWithOtherStakers_failsToEnable() throws Exception {
- assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false);
+ assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false, false);
- assertToggleUsbFailed(TEST_SECOND_CALLER_ID, true);
+ assertToggleUsbFailed(TEST_SECOND_CALLER_ID, true, false);
}
/**
- * Verify enableUsbData successfully enables USB port when the last staker is removed
+ * Verify enableUsbData successfully enables USB port when the last staker
+ * is removed
*/
@Test
public void enableUsbByTheOnlyStaker_successfullyEnable() {
- assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false);
+ assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false, false);
- assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, true);
+ assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, true, false);
}
/**
- * Verify enableUsbDataWhileDockedInternal does not enable USB port if other stakers are present
+ * Verify enableUsbDataWhileDockedInternal does not enable USB port if other
+ * stakers are present
*/
@Test
public void enableUsbWhileDockedWhenThereAreOtherStakers_failsToEnable()
throws RemoteException {
- assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false);
+ assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false, false);
mUsbService.enableUsbDataWhileDockedInternal(TEST_PORT_ID, TEST_TRANSACTION_ID,
- mCallback, TEST_SECOND_CALLER_ID);
+ mCallback, TEST_SECOND_CALLER_ID, false);
verifyZeroInteractions(mUsbPortManager);
verify(mCallback).onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
}
/**
- * Verify enableUsbDataWhileDockedInternal does enable USB port if other stakers are
- * not present
+ * Verify enableUsbDataWhileDockedInternal does enable USB port if other
+ * stakers are not present
*/
@Test
public void enableUsbWhileDockedWhenThereAreNoStakers_SuccessfullyEnable() {
mUsbService.enableUsbDataWhileDockedInternal(TEST_PORT_ID, TEST_TRANSACTION_ID,
- mCallback, TEST_SECOND_CALLER_ID);
+ mCallback, TEST_SECOND_CALLER_ID, false);
verify(mUsbPortManager).enableUsbDataWhileDocked(TEST_PORT_ID, TEST_TRANSACTION_ID,
mCallback, null);
verifyZeroInteractions(mCallback);
}
+
+ /**
+ * Verify enableUsbData successfully enables USB port without error given no
+ * other stakers for internal requests
+ */
+ @Test
+ public void enableUsbWhenNoOtherStakers_forInternalRequest_successfullyEnable() {
+ assertToggleUsbSuccessfully(TEST_INTERNAL_REQUESTER_REASON_1, true, true);
+ }
+
+ /**
+ * Verify enableUsbData does not enable USB port if other internal stakers
+ * are present for internal requests
+ */
+ @Test
+ public void enableUsbPortWithOtherInternalStakers_forInternalRequest_failsToEnable()
+ throws Exception {
+ assertToggleUsbSuccessfully(TEST_INTERNAL_REQUESTER_REASON_1, false, true);
+
+ assertToggleUsbFailed(TEST_INTERNAL_REQUESTER_REASON_2, true, true);
+ }
+
+ /**
+ * Verify enableUsbData does not enable USB port if other external stakers
+ * are present for internal requests
+ */
+ @Test
+ public void enableUsbPortWithOtherExternalStakers_forInternalRequest_failsToEnable()
+ throws Exception {
+ assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false, false);
+
+ assertToggleUsbFailed(TEST_INTERNAL_REQUESTER_REASON_2, true, true);
+ }
+
+ /**
+ * Verify enableUsbData does not enable USB port if other internal stakers
+ * are present for external requests
+ */
+ @Test
+ public void enableUsbPortWithOtherInternalStakers_forExternalRequest_failsToEnable()
+ throws Exception {
+ assertToggleUsbSuccessfully(TEST_INTERNAL_REQUESTER_REASON_1, false, true);
+
+ assertToggleUsbFailed(TEST_FIRST_CALLER_ID, true, false);
+ }
+
+ /**
+ * Verify enableUsbData successfully enables USB port when the last staker
+ * is removed for internal requests
+ */
+ @Test
+ public void enableUsbByTheOnlyStaker_forInternalRequest_successfullyEnable() {
+ assertToggleUsbSuccessfully(TEST_INTERNAL_REQUESTER_REASON_1, false, false);
+
+ assertToggleUsbSuccessfully(TEST_INTERNAL_REQUESTER_REASON_1, true, false);
+ }
+
+ /**
+ * Verify USB Manager internal calls mPortManager to get UsbPorts
+ */
+ @Test
+ public void usbManagerInternal_getPorts_callsPortManager() {
+ when(mUsbPortManager.getPorts()).thenReturn(new UsbPort[] {});
+
+ UsbPort[] ports = mUsbManagerInternal.getPorts();
+
+ verify(mUsbPortManager).getPorts();
+ assertEquals(ports.length, 0);
+ }
+
+ @Test
+ public void usbManagerInternal_enableUsbData_successfullyEnable() {
+ boolean desiredEnableState = true;
+
+ assertTrue(mUsbManagerInternal.enableUsbData(TEST_PORT_ID, desiredEnableState,
+ TEST_TRANSACTION_ID, mCallback, TEST_INTERNAL_REQUESTER_REASON_1));
+
+ verify(mUsbPortManager).enableUsbData(TEST_PORT_ID,
+ desiredEnableState, TEST_TRANSACTION_ID, mCallback, null);
+ verifyZeroInteractions(mCallback);
+ clearInvocations(mUsbPortManager);
+ clearInvocations(mCallback);
+ }
}
diff --git a/tools/processors/property_cache/Android.bp b/tools/processors/property_cache/Android.bp
new file mode 100644
index 0000000..81fab7a
--- /dev/null
+++ b/tools/processors/property_cache/Android.bp
@@ -0,0 +1,57 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+ default_team: "trendy_team_framework_android_multiuser",
+}
+
+java_library_host {
+ name: "libcached-property-annotation-processor",
+ srcs: [
+ ":framework-annotations",
+ "src/**/*.java",
+ ],
+ static_libs: [
+ "codegen-version-info",
+ "android.multiuser.flags-aconfig-java-host",
+ "guava",
+ ],
+ use_tools_jar: true,
+}
+
+java_plugin {
+ name: "cached-property-annotation-processor",
+ processor_class: "android.processor.property_cache.CachedPropertyProcessor",
+ static_libs: ["libcached-property-annotation-processor"],
+}
+
+java_aconfig_library {
+ name: "android.multiuser.flags-aconfig-java-host",
+ aconfig_declarations: "android.multiuser.flags-aconfig",
+ host_supported: true,
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
+java_test_host {
+ name: "cached-property-annotation-processor-test",
+ srcs: ["test/java/**/*.java"],
+ java_resources: [":CachedPropertyAnnotationJavaTestSource"],
+ static_libs: [
+ "compile-testing-prebuilt",
+ "truth",
+ "junit",
+ "guava",
+ "libcached-property-annotation-processor",
+ ],
+ test_suites: ["general-tests"],
+}
+
+filegroup {
+ name: "CachedPropertyAnnotationJavaTestSource",
+ srcs: ["test/resources/*.java"],
+ path: "test/resources/",
+ visibility: ["//visibility:private"],
+}
diff --git a/tools/processors/property_cache/TEST_MAPPING b/tools/processors/property_cache/TEST_MAPPING
new file mode 100644
index 0000000..7177abc
--- /dev/null
+++ b/tools/processors/property_cache/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "postsubmit": [
+ {
+ "name": "cached-property-annotation-processor-test"
+ }
+ ]
+}
diff --git a/tools/processors/property_cache/src/java/android/processor/property_cache/CacheConfig.java b/tools/processors/property_cache/src/java/android/processor/property_cache/CacheConfig.java
new file mode 100644
index 0000000..c665c84
--- /dev/null
+++ b/tools/processors/property_cache/src/java/android/processor/property_cache/CacheConfig.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.processor.property_cache;
+
+import com.android.internal.annotations.CachedProperty;
+import com.android.internal.annotations.CachedPropertyDefaults;
+
+import com.google.common.base.CaseFormat;
+
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+
+public class CacheConfig {
+ private final CacheModifiers mModifiers;
+ private final int mMaxSize;
+ private final String mModuleName;
+ private final String mApiName;
+ private final String mClassName;
+ private final String mQualifiedName;
+ private String mPropertyName;
+ private String mMethodName;
+ private int mNumberOfParams = 0;
+ private String mInputType = Constants.JAVA_LANG_VOID;
+ private String mResultType;
+
+ public CacheConfig(TypeElement classElement, ExecutableElement method) {
+ CachedPropertyDefaults classAnnotation = classElement.getAnnotation(
+ CachedPropertyDefaults.class);
+ CachedProperty methodAnnotation = method.getAnnotation(CachedProperty.class);
+
+ mModuleName = methodAnnotation.module().isEmpty() ? classAnnotation.module()
+ : methodAnnotation.module();
+ mClassName = classElement.getSimpleName().toString();
+ mQualifiedName = classElement.getQualifiedName().toString();
+ mModifiers = new CacheModifiers(methodAnnotation.modsFlagOnOrNone());
+ mMethodName = method.getSimpleName().toString();
+ mPropertyName = getPropertyName(mMethodName);
+ mApiName = methodAnnotation.api().isEmpty() ? getUniqueApiName(mClassName, mPropertyName)
+ : methodAnnotation.api();
+ mMaxSize = methodAnnotation.max() == -1 ? classAnnotation.max() : methodAnnotation.max();
+ mNumberOfParams = method.getParameters().size();
+ if (mNumberOfParams > 0) {
+ mInputType = primitiveTypeToObjectEquivalent(
+ method.getParameters().get(0).asType().toString());
+ }
+ mResultType = primitiveTypeToObjectEquivalent(method.getReturnType().toString());
+ }
+
+ public CacheModifiers getModifiers() {
+ return mModifiers;
+ }
+
+ public int getMaxSize() {
+ return mMaxSize;
+ }
+
+ public String getApiName() {
+ return mApiName;
+ }
+
+ public String getClassName() {
+ return mClassName;
+ }
+
+ public String getQualifiedName() {
+ return mQualifiedName;
+ }
+
+ public String getModuleName() {
+ return mModuleName;
+ }
+
+ public String getMethodName() {
+ return mMethodName;
+ }
+
+ public String getPropertyName() {
+ return mPropertyName;
+ }
+
+ public String getPropertyVariable() {
+ return (mModifiers.isStatic() ? "s" : "m") + mPropertyName;
+ }
+
+ private String getPropertyName(String methodName) {
+ if (methodName.startsWith("get")) {
+ return methodName.substring(3);
+ } else if (methodName.startsWith("is")) {
+ return methodName.substring(2);
+ } else {
+ return CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, methodName);
+ }
+ }
+
+ public int getNumberOfParams() {
+ return mNumberOfParams;
+ }
+
+ public String getInputType() {
+ return mInputType;
+ }
+
+ public String getResultType() {
+ return mResultType;
+ }
+
+ /**
+ * This method returns the unique api name for a given class and property name.
+ * Property name is retrieved from the method name.
+ * Both names are combined and converted to lower snake case.
+ *
+ * @param className The name of the class that contains the property.
+ * @param propertyName The name of the property.
+ * @return The registration name for the property.
+ */
+ private String getUniqueApiName(String className, String propertyName) {
+ return CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, className + propertyName);
+ }
+
+ private String primitiveTypeToObjectEquivalent(String simpleType) {
+ // checking against primitive types
+ return Constants.PRIMITIVE_TYPE_MAP.getOrDefault(simpleType, simpleType);
+ }
+}
diff --git a/tools/processors/property_cache/src/java/android/processor/property_cache/CacheModifiers.java b/tools/processors/property_cache/src/java/android/processor/property_cache/CacheModifiers.java
new file mode 100644
index 0000000..fda9b2c
--- /dev/null
+++ b/tools/processors/property_cache/src/java/android/processor/property_cache/CacheModifiers.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.processor.property_cache;
+
+import com.android.internal.annotations.CacheModifier;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class CacheModifiers {
+ private final boolean mIsStatic;
+ private static final String STATIC_MODIFIER_STRING = "static ";
+
+ CacheModifiers(CacheModifier[] modifierArray) {
+ final List<CacheModifier> modifiers = Arrays.asList(modifierArray);
+ mIsStatic = modifiers.contains(CacheModifier.STATIC);
+ }
+
+ public boolean isStatic() {
+ return mIsStatic;
+ }
+
+ public String getStaticModifier() {
+ return mIsStatic ? STATIC_MODIFIER_STRING : Constants.EMPTY_STRING;
+ }
+}
diff --git a/tools/processors/property_cache/src/java/android/processor/property_cache/CachedPropertyProcessor.java b/tools/processors/property_cache/src/java/android/processor/property_cache/CachedPropertyProcessor.java
new file mode 100644
index 0000000..0361012
--- /dev/null
+++ b/tools/processors/property_cache/src/java/android/processor/property_cache/CachedPropertyProcessor.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.processor.property_cache;
+
+import com.android.internal.annotations.CachedProperty;
+import com.android.internal.annotations.CachedPropertyDefaults;
+
+import com.google.common.collect.ImmutableSet;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.Filer;
+import javax.annotation.processing.RoundEnvironment;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.util.ElementFilter;
+import javax.tools.JavaFileObject;
+
+public class CachedPropertyProcessor extends AbstractProcessor {
+
+ IpcDataCacheComposer mIpcDataCacheComposer =
+ new IpcDataCacheComposer();
+
+ @Override
+ public Set<String> getSupportedAnnotationTypes() {
+ return new HashSet<String>(
+ ImmutableSet.of(CachedPropertyDefaults.class.getCanonicalName()));
+ }
+
+ @Override
+ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ for (Element element : roundEnv.getElementsAnnotatedWith(CachedPropertyDefaults.class)) {
+ try {
+ generateCachedClass((TypeElement) element, processingEnv.getFiler());
+ } catch (IOException e) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+ return false;
+ }
+
+ private void generateCachedClass(TypeElement classElement, Filer filer) throws IOException {
+ String packageName =
+ processingEnv
+ .getElementUtils()
+ .getPackageOf(classElement)
+ .getQualifiedName()
+ .toString();
+ String className = classElement.getSimpleName().toString() + "Cache";
+ JavaFileObject jfo = filer.createSourceFile(packageName + "." + className);
+ Writer writer = jfo.openWriter();
+ writer.write("package " + packageName + ";\n\n");
+ writer.write("import android.os.IpcDataCache;\n");
+ writer.write("\n /** \n * This class is auto-generated \n * @hide \n **/");
+ writer.write("\npublic class " + className + " {\n");
+
+ List<ExecutableElement> methods =
+ ElementFilter.methodsIn(classElement.getEnclosedElements());
+ String initCache = String.format(Constants.METHOD_COMMENT,
+ " - initialise all caches for class " + className)
+ + "\npublic static void initCache() {";
+ for (ExecutableElement method : methods) {
+ if (method.getAnnotation(CachedProperty.class) != null) {
+ mIpcDataCacheComposer.generatePropertyCache(writer, classElement, method);
+ initCache += "\n " + mIpcDataCacheComposer.generateInvalidatePropertyCall();
+ }
+ }
+ initCache += "\n}";
+ writer.write(initCache);
+ writer.write("\n}");
+ writer.write("\n");
+ writer.close();
+ }
+}
diff --git a/tools/processors/property_cache/src/java/android/processor/property_cache/Constants.java b/tools/processors/property_cache/src/java/android/processor/property_cache/Constants.java
new file mode 100644
index 0000000..03961bc
--- /dev/null
+++ b/tools/processors/property_cache/src/java/android/processor/property_cache/Constants.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.processor.property_cache;
+
+import com.google.common.collect.ImmutableMap;
+
+public final class Constants {
+ public static final String EMPTY_STRING = "";
+ public static final String JAVA_LANG_VOID = "java.lang.Void";
+ public static final ImmutableMap<String, String> PRIMITIVE_TYPE_MAP =
+ ImmutableMap.of(
+ "int", "java.lang.Integer",
+ "boolean", "java.lang.Boolean",
+ "long", "java.lang.Long",
+ "float", "java.lang.Float",
+ "double", "java.lang.Double",
+ "byte", "java.lang.Byte",
+ "short", "java.lang.Short",
+ "char", "java.lang.Character");
+
+ public static final String METHOD_COMMENT = "\n /**"
+ + "\n * This method is auto-generated%s"
+ + "\n * "
+ + "\n * @hide"
+ + "\n */";
+}
diff --git a/tools/processors/property_cache/src/java/android/processor/property_cache/IpcDataCacheComposer.java b/tools/processors/property_cache/src/java/android/processor/property_cache/IpcDataCacheComposer.java
new file mode 100644
index 0000000..8526a04
--- /dev/null
+++ b/tools/processors/property_cache/src/java/android/processor/property_cache/IpcDataCacheComposer.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.processor.property_cache;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+
+public class IpcDataCacheComposer {
+
+ private static final String PROPERTY_DEFINITION_LINE = "private %s%s %s;\n";
+ private static final String METHOD_NAME_LINE = "\npublic %s%s %s(%s%s%s\n) {\n";
+ private static final String RETURN_IF_NOT_NULL_LINE =
+ "if (%s != null) {\n return %s.%s;\n }";
+
+ private CacheConfig mCacheConfig;
+
+ /**
+ * Generates code for property cache.
+ *
+ * @param writer writer to write code to.
+ * @param classElement class element to generate code for.
+ * @param method method element to generate code for.
+ * @throws IOException if writer throws IOException.
+ */
+ public void generatePropertyCache(Writer writer, TypeElement classElement,
+ ExecutableElement method) throws IOException {
+
+ mCacheConfig = new CacheConfig(classElement, method);
+
+ ParamComposer inputParam = new ParamComposer(null, null);
+ ParamComposer binderParam = new ParamComposer(
+ String.format("IpcDataCache.RemoteCall<%s, %s>", mCacheConfig.getInputType(),
+ mCacheConfig.getResultType()), "binderCall");
+
+ ParamComposer bypassParam = new ParamComposer(null, null); // empty if method have no params
+ String queryCall = "query(null)";
+ if (mCacheConfig.getNumberOfParams() > 0) {
+ bypassParam = new ParamComposer(
+ String.format("IpcDataCache.BypassCall<%s> ", mCacheConfig.getInputType()),
+ "bypassPredicate");
+ inputParam = new ParamComposer(mCacheConfig.getInputType(), "query");
+ queryCall = "query(query)";
+ }
+ String propertyClass =
+ "IpcDataCache<" + mCacheConfig.getInputType() + ", " + mCacheConfig.getResultType()
+ + ">";
+ String invalidateName = "invalidate" + mCacheConfig.getPropertyName();
+ String lockObject = mCacheConfig.getPropertyVariable() + "Lock";
+ writer.write("private " + mCacheConfig.getModifiers().getStaticModifier() + "final Object "
+ + lockObject + " = new Object();\n");
+ writer.write(String.format(PROPERTY_DEFINITION_LINE,
+ mCacheConfig.getModifiers().getStaticModifier(), propertyClass,
+ mCacheConfig.getPropertyVariable()));
+
+ writer.write(propertyInvalidatedCacheMethod(binderParam, bypassParam, inputParam, queryCall,
+ lockObject));
+
+ // If binder param is not empty then generate getter without binder param to be called
+ if (!bypassParam.getParam().isEmpty()) {
+ writer.write(propertyInvalidatedCacheMethod(binderParam, new ParamComposer(null, null),
+ inputParam, queryCall, lockObject));
+ }
+ writer.write(String.format(Constants.METHOD_COMMENT,
+ "- invalidate cache for {@link " + mCacheConfig.getQualifiedName() + "#"
+ + mCacheConfig.getMethodName() + "}"));
+ writer.write("\n public static final void " + invalidateName + "() {");
+ writer.write(
+ "\n IpcDataCache.invalidateCache(\"" + mCacheConfig.getModuleName() + "\", \""
+ + mCacheConfig.getApiName() + "\");");
+ writer.write("\n }");
+ writer.write("\n");
+ writer.write("\n");
+ }
+
+ /**
+ * Generates code to call cache invalidation.
+ *
+ * @return code string calling cache invalidation.
+ */
+ public String generateInvalidatePropertyCall() {
+ String invalidateName = "invalidate" + mCacheConfig.getPropertyName();
+ return mCacheConfig.getClassName() + "Cache." + invalidateName + "();";
+ }
+
+ /**
+ * Generates code for getter that returns cached value or calls binder and caches result.
+ *
+ * @param binderParam parameter for binder call.
+ * @param bypassParam parameter for bypass predicate.
+ * @param inputParam parameter for input value.
+ * @param queryCall cache query call syntax.
+ * @param lockObject object to synchronize on.
+ * @return String with code for method.
+ */
+ private String propertyInvalidatedCacheMethod(ParamComposer binderParam,
+ ParamComposer bypassParam, ParamComposer inputParam, String queryCall,
+ String lockObject) {
+ String result = "\n";
+ CacheModifiers modifiers = mCacheConfig.getModifiers();
+ String paramsComments = binderParam.getParamComment(
+ "lambda for remote call" + " {@link " + mCacheConfig.getQualifiedName() + "#"
+ + mCacheConfig.getMethodName() + " }") + bypassParam.getParamComment(
+ "lambda to bypass remote call") + inputParam.getParamComment(
+ "parameter to call remote lambda");
+ result += String.format(Constants.METHOD_COMMENT, paramsComments);
+ result += String.format(METHOD_NAME_LINE, modifiers.getStaticModifier(),
+ mCacheConfig.getResultType(), mCacheConfig.getMethodName(),
+ binderParam.getParam(), bypassParam.getNextParam(),
+ inputParam.getNextParam());
+ result += String.format(RETURN_IF_NOT_NULL_LINE, mCacheConfig.getPropertyVariable(),
+ mCacheConfig.getPropertyVariable(), queryCall);
+ result += "\n synchronized (" + lockObject + " ) {";
+ result += "\n if (" + mCacheConfig.getPropertyVariable() + " == null) {";
+ result += "\n " + mCacheConfig.getPropertyVariable() + " = new IpcDataCache" + "("
+ + generateCreateIpcConfig() + ", " + binderParam.getName()
+ + bypassParam.getNextName() + ");\n";
+ result += "\n }";
+ result += "\n }";
+ result += "\n return " + mCacheConfig.getPropertyVariable() + "." + queryCall + ";";
+ result += "\n }";
+ result += "\n";
+ return result;
+ }
+
+ /**
+ * Generates code for new IpcDataCache.Config object for given configuration.
+ *
+ * @return String with code for new IpcDataCache.Config object.
+ */
+ public String generateCreateIpcConfig() {
+ return "new IpcDataCache.Config(" + mCacheConfig.getMaxSize() + ", " + "\""
+ + mCacheConfig.getModuleName() + "\"" + ", " + "\"" + mCacheConfig.getApiName()
+ + "\"" + ", " + "\"" + mCacheConfig.getPropertyName() + "\"" + ")";
+ }
+}
diff --git a/tools/processors/property_cache/src/java/android/processor/property_cache/ParamComposer.java b/tools/processors/property_cache/src/java/android/processor/property_cache/ParamComposer.java
new file mode 100644
index 0000000..307443a
--- /dev/null
+++ b/tools/processors/property_cache/src/java/android/processor/property_cache/ParamComposer.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.processor.property_cache;
+
+public class ParamComposer {
+ private String mType;
+ private String mName;
+
+ /** Creates ParamComposer with given type and name.
+ *
+ * @param type type of parameter.
+ * @param name name of parameter.
+ */
+ public ParamComposer(String type, String name) {
+ mType = type;
+ mName = name;
+ }
+
+ /** Returns name of parameter.
+ *
+ * @return name of parameter.
+ */
+ public String getName() {
+ if (mName != null) {
+ return mName;
+ }
+ return Constants.EMPTY_STRING;
+ }
+
+ /** Returns name of parameter for next parameter followed by comma.
+ *
+ * @return name of parameter for next parameter if exists, empty string otherwise.
+ */
+ public String getNextName() {
+ if (!getName().isEmpty()) {
+ return ", " + getName();
+ }
+ return Constants.EMPTY_STRING;
+ }
+
+ /**
+ * Returns type of parameter.
+ *
+ * @return type of parameter.
+ */
+ public String getType() {
+ if (mType != null) {
+ return mType;
+ }
+ return Constants.EMPTY_STRING;
+ }
+
+ /**
+ * Returns type and name of parameter.
+ *
+ * @return type and name of parameter if exists, empty string otherwise.
+ */
+ public String getParam() {
+ if (!getType().isEmpty() && !getName().isEmpty()) {
+ return getType() + " " + getName();
+ }
+ return Constants.EMPTY_STRING;
+ }
+
+ /**
+ * Returns type and name of parameter for next parameter followed by comma.
+ *
+ * @return type and name of parameter for next parameter if exists, empty string otherwise.
+ */
+ public String getNextParam() {
+ if (!getType().isEmpty() && !getName().isEmpty()) {
+ return ", " + getParam();
+ }
+ return Constants.EMPTY_STRING;
+ }
+
+ /**
+ * Returns comment for parameter.
+ *
+ * @param description of parameter.
+ * @return comment for parameter if exists, empty string otherwise.
+ */
+ public String getParamComment(String description) {
+ if (!getType().isEmpty() && !getName().isEmpty()) {
+ return "\n * @param " + getName() + " - " + description;
+ }
+ return Constants.EMPTY_STRING;
+ }
+}
diff --git a/tools/processors/property_cache/test/java/android/processor/property_cache/CachedPropertyProcessorTest.java b/tools/processors/property_cache/test/java/android/processor/property_cache/CachedPropertyProcessorTest.java
new file mode 100644
index 0000000..1e23c78
--- /dev/null
+++ b/tools/processors/property_cache/test/java/android/processor/property_cache/CachedPropertyProcessorTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.processor.property_cache.test;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.testing.compile.CompilationSubject.assertThat;
+
+import android.processor.property_cache.CachedPropertyProcessor;
+
+import com.google.testing.compile.Compilation;
+import com.google.testing.compile.Compiler;
+import com.google.testing.compile.JavaFileObjects;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import javax.tools.JavaFileObject;
+import javax.tools.StandardLocation;
+
+/** Tests the {@link CachedPropertyProcessor}. */
+@RunWith(JUnit4.class)
+public class CachedPropertyProcessorTest {
+ private final Compiler mCompiler =
+ Compiler.javac().withProcessors(new CachedPropertyProcessor());
+
+ @Test
+ public void testDefaultValues() {
+ JavaFileObject expectedJava = JavaFileObjects.forResource("DefaultCache.java");
+
+ Compilation compilation = mCompiler.compile(JavaFileObjects.forResource("Default.java"));
+ assertThat(compilation).succeeded();
+ assertThat(compilation)
+ .generatedFile(StandardLocation.SOURCE_OUTPUT,
+ "android/processor/property_cache/test/DefaultCache.java")
+ .hasSourceEquivalentTo(expectedJava);
+ }
+
+ @Test
+ public void testCustomValues() {
+ JavaFileObject expectedJava = JavaFileObjects.forResource("CustomCache.java");
+
+ Compilation compilation = mCompiler.compile(JavaFileObjects.forResource("Custom.java"));
+ assertThat(compilation).succeeded();
+ assertThat(compilation)
+ .generatedFile(StandardLocation.SOURCE_OUTPUT,
+ "android/processor/property_cache/test/CustomCache.java")
+ .hasSourceEquivalentTo(expectedJava);
+ }
+}
diff --git a/tools/processors/property_cache/test/java/android/processor/property_cache/shadows/IpcDataCache.java b/tools/processors/property_cache/test/java/android/processor/property_cache/shadows/IpcDataCache.java
new file mode 100644
index 0000000..e5ef48c
--- /dev/null
+++ b/tools/processors/property_cache/test/java/android/processor/property_cache/shadows/IpcDataCache.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+// Mocked class for generation compilation tests purposes only.
+public class IpcDataCache<Input, Output> {
+ public static class Config {
+ public Config(int max, String module, String api, String name) {
+ }
+ }
+
+ /** Shadow method for generated code compilation tests purposes only.
+ *
+ * @param query - shadow parameter from IpcDataCache in Frameworks.
+ * @return null
+ */
+ public Output query(Input query) {
+ return null;
+ }
+
+ /** Shadow method for generated code compilation tests purposes only.
+ *
+ * @param key - shadow parameter from IpcDataCache in Frameworks;
+ */
+ public static void invalidateCache(String key) {
+ }
+
+ /** Shadow method for generated code compilation tests purposes only.
+ *
+ * @param query - shadow parameter from IpcDataCache in Frameworks;
+ * @return null
+ */
+ public Output recompute(Input query) {
+ return null;
+ }
+
+ /** Shadow method for generated code compilation tests purposes only.
+ *
+ * @param query - parameter equivalent to IpcDataCache in android framework.
+ * @param query - shadow parameter from IpcDataCache in Frameworks;
+ * @return false
+ */
+ public boolean bypass(Input query) {
+ return false;
+ }
+
+ /** Shadow method for generated code compilation tests purposes only.
+ *
+ * @param module - parameter equivalent to IpcDataCache in android framework.
+ * @param key - parameter equivalent to IpcDataCache in android framework.
+ * @return module + key sttring
+ */
+ public static String createPropertyName(String module, String key) {
+ return module + key;
+ }
+
+ public abstract static class QueryHandler<Input, Output> {
+ /** Shadow method for generated code compilation tests purposes only.
+ *
+ * @param query - parameter equivalent to IpcDataCache.QueryHandler in android framework.
+ * @return expected value
+ */
+ public abstract Output apply(Input query);
+ /** Shadow method for generated code compilation tests purposes only.
+ *
+ * @param query - parameter equivalent to IpcDataCache.QueryHandler in android framework.
+ */
+ public boolean shouldBypassCache(Input query) {
+ return false;
+ }
+ }
+
+ public interface RemoteCall<Input, Output> {
+ /** Shadow method for generated code compilation tests purposes only.
+ *
+ * @param query - parameter equivalent to IpcDataCache.RemoteCall in android framework.
+ */
+ Output apply(Input query);
+ }
+
+ public interface BypassCall<Input> {
+ /** Shadow method for generated code compilation tests purposes only.
+ *
+ * @param query - parameter equivalent to IpcDataCache.BypassCall in android framework.
+ */
+ boolean apply(Input query);
+ }
+
+ public IpcDataCache(
+ int maxEntries,
+ String module,
+ String api,
+ String cacheName,
+ QueryHandler<Input, Output> computer) {
+ }
+
+ public IpcDataCache(Config config, QueryHandler<Input, Output> computer) {
+ }
+
+ public IpcDataCache(Config config, RemoteCall<Input, Output> computer) {
+ }
+
+ public IpcDataCache(Config config, RemoteCall<Input, Output> computer,
+ BypassCall<Input> bypassCall) {
+ }
+
+ /** Shadow method for generated code compilation tests purposes only.*/
+ public void invalidateCache() {
+ }
+
+
+ /** Shadow method for generated code compilation tests purposes only.
+ *
+ * @param module - shadow parameter from IpcDataCache in Frameworks.
+ * @param api - shadow parameter from IpcDataCache in Frameworks.
+ */
+ public static void invalidateCache(String module, String api) {
+ }
+
+}
diff --git a/tools/processors/property_cache/test/resources/Custom.java b/tools/processors/property_cache/test/resources/Custom.java
new file mode 100644
index 0000000..05024da
--- /dev/null
+++ b/tools/processors/property_cache/test/resources/Custom.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.processor.property_cache.test;
+
+import com.android.internal.annotations.CacheModifier;
+import com.android.internal.annotations.CachedProperty;
+import com.android.internal.annotations.CachedPropertyDefaults;
+
+import java.util.Date;
+
+@CachedPropertyDefaults(max = 4, module = "bluetooth")
+public class Custom {
+ BirthdayManagerService mService = new BirthdayManagerService();
+ Object mCache = new CustomCache();
+
+ public Custom() {
+ CustomCache.initCache();
+ }
+
+ /**
+ * Testing custom class values to generate static IpcDataCache
+ *
+ * @param userId - user Id
+ * @return birthday date of given user Id
+ */
+ @CachedProperty()
+ public Date getBirthday(int userId) {
+ return CustomCache.getBirthday(mService::getBirthday, userId);
+ }
+
+ /**
+ * Testing custom class values to generate static IpcDataCache
+ *
+ * @param userId - user Id
+ * @return number of days till birthday of given user Id
+ */
+ @CachedProperty(modsFlagOnOrNone = {CacheModifier.STATIC})
+ public int getDaysTillBirthday(int userId) {
+ return CustomCache.getDaysTillBirthday(mService::getDaysTillBirthday, userId);
+ }
+
+ /**
+ * Testing custom class values to generate non-static IpcDataCache
+ *
+ * @param userId - user Id
+ * @return number of days since birthday of given user Id
+ */
+ @CachedProperty(modsFlagOnOrNone = {})
+ public int getDaysSinceBirthday(int userId) {
+ return ((CustomCache) mCache).getDaysSinceBirthday(mService::getDaysSinceBirthday, userId);
+ }
+
+ /**
+ * Testing custom class values to generate static IpcDataCache with max capasity of 1
+ *
+ * @return number of days till birthay of current user
+ */
+ @CachedProperty(modsFlagOnOrNone = {CacheModifier.STATIC}, max = 1)
+ public int getDaysTillMyBirthday() {
+ return CustomCache.getDaysTillMyBirthday((Void) -> mService.getDaysTillMyBirthday());
+ }
+
+ /**
+ * Testing custom class values to generate static IpcDataCache with max capasity of 1 and custom
+ * api
+ *
+ * @return number of days since birthay of current user
+ */
+ @CachedProperty(modsFlagOnOrNone = {}, max = 1, api = "my_unique_key")
+ public int getDaysSinceMyBirthday() {
+ return ((CustomCache) mCache).getDaysSinceMyBirthday(
+ (Void) -> mService.getDaysSinceMyBirthday());
+ }
+
+ /**
+ * Testing custom class values to generate static IpcDataCache with custom module name
+ *
+ * @return birthday wishes of given user Id
+ */
+ @CachedProperty(module = "telephony")
+ public String getBirthdayWishesFromUser(int userId) {
+ return CustomCache.getBirthdayWishesFromUser(mService::getBirthdayWishesFromUser,
+ userId);
+ }
+
+ class BirthdayManagerService {
+ int mDaysTillBirthday = 182;
+
+ public Date getBirthday(int userId) {
+ return new Date(2024, 6, 1 + userId);
+ }
+
+ public int getDaysTillBirthday(int userId) {
+ return mDaysTillBirthday + userId;
+ }
+
+ public int getDaysSinceBirthday(int userId) {
+ return 365 - getDaysTillBirthday(userId);
+ }
+
+ public int getDaysTillMyBirthday() {
+ return 0;
+ }
+
+ public int getDaysSinceMyBirthday() {
+ return 365;
+ }
+
+ public String getBirthdayWishesFromUser(int userId) {
+ return "Happy Birthday!\n- " + userId;
+ }
+ }
+}
diff --git a/tools/processors/property_cache/test/resources/CustomCache.java b/tools/processors/property_cache/test/resources/CustomCache.java
new file mode 100644
index 0000000..326467f
--- /dev/null
+++ b/tools/processors/property_cache/test/resources/CustomCache.java
@@ -0,0 +1,384 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.processor.property_cache.test;
+
+import android.os.IpcDataCache;
+
+/**
+ * This class is auto-generated
+ *
+ * @hide
+ **/
+public class CustomCache {
+ private static final Object sBirthdayLock = new Object();
+ private static IpcDataCache<java.lang.Integer, java.util.Date> sBirthday;
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link android.processor.property_cache.test.Custom#getBirthday }
+ * @param bypassPredicate - lambda to bypass remote call
+ * @param query - parameter to call remote lambda
+ * @hide
+ */
+ public static java.util.Date getBirthday(
+ IpcDataCache.RemoteCall<java.lang.Integer, java.util.Date> binderCall,
+ IpcDataCache.BypassCall<java.lang.Integer> bypassPredicate, java.lang.Integer query) {
+ if (sBirthday != null) {
+ return sBirthday.query(query);
+ }
+ synchronized (sBirthdayLock) {
+ if (sBirthday == null) {
+ sBirthday = new IpcDataCache(
+ new IpcDataCache.Config(4, "bluetooth", "custom_birthday", "Birthday"),
+ binderCall, bypassPredicate);
+
+ }
+ }
+ return sBirthday.query(query);
+ }
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link android.processor.property_cache.test.Custom#getBirthday }
+ * @param query - parameter to call remote lambda
+ * @hide
+ */
+ public static java.util.Date getBirthday(
+ IpcDataCache.RemoteCall<java.lang.Integer, java.util.Date> binderCall,
+ java.lang.Integer query) {
+ if (sBirthday != null) {
+ return sBirthday.query(query);
+ }
+ synchronized (sBirthdayLock) {
+ if (sBirthday == null) {
+ sBirthday = new IpcDataCache(
+ new IpcDataCache.Config(4, "bluetooth", "custom_birthday", "Birthday"),
+ binderCall);
+ }
+ }
+ return sBirthday.query(query);
+ }
+
+ /**
+ * This method is auto-generated- invalidate cache for
+ * {@link android.processor.property_cache.test.Custom#getBirthday}
+ *
+ * @hide
+ */
+ public static final void invalidateBirthday() {
+ IpcDataCache.invalidateCache("bluetooth", "custom_birthday");
+ }
+
+ private static final Object sDaysTillBirthdayLock = new Object();
+ private static IpcDataCache<java.lang.Integer, java.lang.Integer> sDaysTillBirthday;
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link
+ * android.processor.property_cache.test.Custom#getDaysTillBirthday }
+ * @param bypassPredicate - lambda to bypass remote call
+ * @param query - parameter to call remote lambda
+ * @hide
+ */
+ public static java.lang.Integer getDaysTillBirthday(
+ IpcDataCache.RemoteCall<java.lang.Integer, java.lang.Integer> binderCall,
+ IpcDataCache.BypassCall<java.lang.Integer> bypassPredicate, java.lang.Integer query) {
+ if (sDaysTillBirthday != null) {
+ return sDaysTillBirthday.query(query);
+ }
+ synchronized (sDaysTillBirthdayLock) {
+ if (sDaysTillBirthday == null) {
+ sDaysTillBirthday = new IpcDataCache(
+ new IpcDataCache.Config(4, "bluetooth", "custom_days_till_birthday",
+ "DaysTillBirthday"), binderCall, bypassPredicate);
+
+ }
+ }
+ return sDaysTillBirthday.query(query);
+ }
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link android.processor.property_cache.test.Custom#getDaysTillBirthday }
+ * @param query - parameter to call remote lambda
+ * @hide
+ */
+ public static java.lang.Integer getDaysTillBirthday(
+ IpcDataCache.RemoteCall<java.lang.Integer, java.lang.Integer> binderCall,
+ java.lang.Integer query) {
+ if (sDaysTillBirthday != null) {
+ return sDaysTillBirthday.query(query);
+ }
+ synchronized (sDaysTillBirthdayLock) {
+ if (sDaysTillBirthday == null) {
+ sDaysTillBirthday = new IpcDataCache(
+ new IpcDataCache.Config(4, "bluetooth", "custom_days_till_birthday",
+ "DaysTillBirthday"), binderCall);
+
+ }
+ }
+ return sDaysTillBirthday.query(query);
+ }
+
+ /**
+ * This method is auto-generated- invalidate cache for
+ * {@link android.processor.property_cache.test.Custom#getDaysTillBirthday}
+ *
+ * @hide
+ */
+ public static final void invalidateDaysTillBirthday() {
+ IpcDataCache.invalidateCache("bluetooth", "custom_days_till_birthday");
+ }
+
+ private final Object mDaysSinceBirthdayLock = new Object();
+ private IpcDataCache<java.lang.Integer, java.lang.Integer> mDaysSinceBirthday;
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link
+ * android.processor.property_cache.test.Custom#getDaysSinceBirthday }
+ * @param bypassPredicate - lambda to bypass remote call
+ * @param query - parameter to call remote lambda
+ * @hide
+ */
+ public java.lang.Integer getDaysSinceBirthday(
+ IpcDataCache.RemoteCall<java.lang.Integer, java.lang.Integer> binderCall,
+ IpcDataCache.BypassCall<java.lang.Integer> bypassPredicate, java.lang.Integer query) {
+ if (mDaysSinceBirthday != null) {
+ return mDaysSinceBirthday.query(query);
+ }
+ synchronized (mDaysSinceBirthdayLock) {
+ if (mDaysSinceBirthday == null) {
+ mDaysSinceBirthday = new IpcDataCache(
+ new IpcDataCache.Config(4, "bluetooth", "custom_days_since_birthday",
+ "DaysSinceBirthday"), binderCall, bypassPredicate);
+
+ }
+ }
+ return mDaysSinceBirthday.query(query);
+ }
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link android.processor.property_cache.test.Custom#getDaysSinceBirthday
+ * }
+ * @param query - parameter to call remote lambda
+ * @hide
+ */
+ public java.lang.Integer getDaysSinceBirthday(
+ IpcDataCache.RemoteCall<java.lang.Integer, java.lang.Integer> binderCall,
+ java.lang.Integer query) {
+ if (mDaysSinceBirthday != null) {
+ return mDaysSinceBirthday.query(query);
+ }
+ synchronized (mDaysSinceBirthdayLock) {
+ if (mDaysSinceBirthday == null) {
+ mDaysSinceBirthday = new IpcDataCache(
+ new IpcDataCache.Config(4, "bluetooth", "custom_days_since_birthday",
+ "DaysSinceBirthday"), binderCall);
+
+ }
+ }
+ return mDaysSinceBirthday.query(query);
+ }
+
+ /**
+ * This method is auto-generated- invalidate cache for
+ * {@link android.processor.property_cache.test.Custom#getDaysSinceBirthday}
+ *
+ * @hide
+ */
+ public static final void invalidateDaysSinceBirthday() {
+ IpcDataCache.invalidateCache("bluetooth", "custom_days_since_birthday");
+ }
+
+ private static final Object sDaysTillMyBirthdayLock = new Object();
+ private static IpcDataCache<java.lang.Void, java.lang.Integer> sDaysTillMyBirthday;
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link android.processor.property_cache.test.Custom#getDaysTillMyBirthday
+ * }
+ * @hide
+ */
+ public static java.lang.Integer getDaysTillMyBirthday(
+ IpcDataCache.RemoteCall<java.lang.Void, java.lang.Integer> binderCall) {
+ if (sDaysTillMyBirthday != null) {
+ return sDaysTillMyBirthday.query(null);
+ }
+ synchronized (sDaysTillMyBirthdayLock) {
+ if (sDaysTillMyBirthday == null) {
+ sDaysTillMyBirthday = new IpcDataCache(
+ new IpcDataCache.Config(1, "bluetooth", "custom_days_till_my_birthday",
+ "DaysTillMyBirthday"), binderCall);
+
+ }
+ }
+ return sDaysTillMyBirthday.query(null);
+ }
+
+ /**
+ * This method is auto-generated- invalidate cache for
+ * {@link android.processor.property_cache.test.Custom#getDaysTillMyBirthday}
+ *
+ * @hide
+ */
+ public static final void invalidateDaysTillMyBirthday() {
+ IpcDataCache.invalidateCache("bluetooth", "custom_days_till_my_birthday");
+ }
+
+ private final Object mDaysSinceMyBirthdayLock = new Object();
+ private IpcDataCache<java.lang.Void, java.lang.Integer> mDaysSinceMyBirthday;
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link android.processor.property_cache.test.Custom#getDaysSinceMyBirthday
+ * }
+ * @hide
+ */
+ public java.lang.Integer getDaysSinceMyBirthday(
+ IpcDataCache.RemoteCall<java.lang.Void, java.lang.Integer> binderCall) {
+ if (mDaysSinceMyBirthday != null) {
+ return mDaysSinceMyBirthday.query(null);
+ }
+ synchronized (mDaysSinceMyBirthdayLock) {
+ if (mDaysSinceMyBirthday == null) {
+ mDaysSinceMyBirthday = new IpcDataCache(
+ new IpcDataCache.Config(1, "bluetooth", "my_unique_key",
+ "DaysSinceMyBirthday"), binderCall);
+
+ }
+ }
+ return mDaysSinceMyBirthday.query(null);
+ }
+
+ /**
+ * This method is auto-generated- invalidate cache for
+ * {@link android.processor.property_cache.test.Custom#getDaysSinceMyBirthday}
+ *
+ * @hide
+ */
+ public static final void invalidateDaysSinceMyBirthday() {
+ IpcDataCache.invalidateCache("bluetooth", "my_unique_key");
+ }
+
+ private static final Object sBirthdayWishesFromUserLock = new Object();
+ private static IpcDataCache<java.lang.Integer, java.lang.String> sBirthdayWishesFromUser;
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link
+ * android.processor.property_cache.test.Custom#getBirthdayWishesFromUser
+ * }
+ * @param bypassPredicate - lambda to bypass remote call
+ * @param query - parameter to call remote lambda
+ * @hide
+ */
+ public static java.lang.String getBirthdayWishesFromUser(
+ IpcDataCache.RemoteCall<java.lang.Integer, java.lang.String> binderCall,
+ IpcDataCache.BypassCall<java.lang.Integer> bypassPredicate, java.lang.Integer query) {
+ if (sBirthdayWishesFromUser != null) {
+ return sBirthdayWishesFromUser.query(query);
+ }
+ synchronized (sBirthdayWishesFromUserLock) {
+ if (sBirthdayWishesFromUser == null) {
+ sBirthdayWishesFromUser = new IpcDataCache(
+ new IpcDataCache.Config(4, "telephony", "custom_birthday_wishes_from_user",
+ "BirthdayWishesFromUser"), binderCall, bypassPredicate);
+
+ }
+ }
+ return sBirthdayWishesFromUser.query(query);
+ }
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link
+ * android.processor.property_cache.test.Custom#getBirthdayWishesFromUser }
+ * @param query - parameter to call remote lambda
+ * @hide
+ */
+ public static java.lang.String getBirthdayWishesFromUser(
+ IpcDataCache.RemoteCall<java.lang.Integer, java.lang.String> binderCall,
+ java.lang.Integer query) {
+ if (sBirthdayWishesFromUser != null) {
+ return sBirthdayWishesFromUser.query(query);
+ }
+ synchronized (sBirthdayWishesFromUserLock) {
+ if (sBirthdayWishesFromUser == null) {
+ sBirthdayWishesFromUser = new IpcDataCache(
+ new IpcDataCache.Config(4, "telephony", "custom_birthday_wishes_from_user",
+ "BirthdayWishesFromUser"), binderCall);
+
+ }
+ }
+ return sBirthdayWishesFromUser.query(query);
+ }
+
+ /**
+ * This method is auto-generated- invalidate cache for
+ * {@link android.processor.property_cache.test.Custom#getBirthdayWishesFromUser}
+ *
+ * @hide
+ */
+ public static final void invalidateBirthdayWishesFromUser() {
+ IpcDataCache.invalidateCache("telephony", "custom_birthday_wishes_from_user");
+ }
+
+
+ /**
+ * This method is auto-generated - initialise all caches for class CustomCache
+ *
+ * @hide
+ */
+ public static void initCache() {
+ CustomCache.invalidateBirthday();
+ CustomCache.invalidateDaysTillBirthday();
+ CustomCache.invalidateDaysSinceBirthday();
+ CustomCache.invalidateDaysTillMyBirthday();
+ CustomCache.invalidateDaysSinceMyBirthday();
+ CustomCache.invalidateBirthdayWishesFromUser();
+ }
+}
diff --git a/tools/processors/property_cache/test/resources/Default.java b/tools/processors/property_cache/test/resources/Default.java
new file mode 100644
index 0000000..d2449aa
--- /dev/null
+++ b/tools/processors/property_cache/test/resources/Default.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.processor.property_cache.test;
+
+import com.android.internal.annotations.CacheModifier;
+import com.android.internal.annotations.CachedProperty;
+import com.android.internal.annotations.CachedPropertyDefaults;
+
+import java.util.Date;
+
+@CachedPropertyDefaults()
+public class Default {
+ BirthdayManagerService mService = new BirthdayManagerService();
+ Object mCache = new DefaultCache();
+
+ /** Testing default class values to generate static IpcDataCache
+ *
+ * @param userId - user Id
+ * @return birthday date of given user Id
+ */
+ @CachedProperty()
+ public Date getBirthday(int userId) {
+ return DefaultCache.getBirthday(mService::getBirthday, userId);
+ }
+
+ /** Testing default class values to generate static IpcDataCache
+ *
+ * @param userId - user Id
+ * @return number of days till birthday of given user Id
+ */
+ @CachedProperty(modsFlagOnOrNone = {CacheModifier.STATIC})
+ public int getDaysTillBirthday(int userId) {
+ return DefaultCache.getDaysTillBirthday(mService::getDaysTillBirthday, userId);
+ }
+
+ /** Testing generate non-static IpcDataCache
+ *
+ * @param userId - user Id
+ * @return number of days since birthday of given user Id
+ */
+ @CachedProperty(modsFlagOnOrNone = {})
+ public int getDaysSinceBirthday(int userId) {
+ return ((DefaultCache) mCache).getDaysSinceBirthday(mService::getDaysSinceBirthday, userId);
+ }
+
+ /** Testing default class values to generate static IpcDataCache with max capacity of 1
+ *
+ * @return number of days till birthay of current user
+ */
+ @CachedProperty(
+ modsFlagOnOrNone = {CacheModifier.STATIC},
+ max = 1)
+ public int getDaysTillMyBirthday() {
+ return DefaultCache.getDaysTillMyBirthday((Void) -> mService.getDaysTillMyBirthday());
+ }
+
+ /** Testing default class values to generate static IpcDataCache with max capacity of 1 and
+ custom api
+ *
+ * @return number of days since birthay of current user
+ */
+ @CachedProperty(
+ modsFlagOnOrNone = {},
+ max = 1,
+ api = "my_unique_key")
+ public int getDaysSinceMyBirthday() {
+ return ((DefaultCache) mCache).getDaysSinceMyBirthday(
+ (Void) -> mService.getDaysSinceMyBirthday());
+ }
+
+ /** Testing default class values to generate static IpcDataCache with custom module name
+ *
+ * @return birthday wishes of given user Id
+ */
+ @CachedProperty(module = "telephony")
+ public String getBirthdayWishesFromUser(int userId) {
+ return DefaultCache.getBirthdayWishesFromUser(mService::getBirthdayWishesFromUser,
+ userId);
+ }
+
+ class BirthdayManagerService {
+
+ BirthdayManagerService() {
+ DefaultCache.initCache();
+ }
+
+ public Date getBirthday(int userId) {
+ return new Date();
+ }
+
+ public int getDaysTillBirthday(int userId) {
+ return 0;
+ }
+
+ public int getDaysSinceBirthday(int userId) {
+ return 0;
+ }
+
+ public int getDaysTillMyBirthday() {
+ return 0;
+ }
+
+ public int getDaysSinceMyBirthday() {
+ return 0;
+ }
+
+ public String getBirthdayWishesFromUser(int userId) {
+ return "Happy Birthday!\n- " + userId;
+ }
+ }
+}
diff --git a/tools/processors/property_cache/test/resources/DefaultCache.java b/tools/processors/property_cache/test/resources/DefaultCache.java
new file mode 100644
index 0000000..9531118
--- /dev/null
+++ b/tools/processors/property_cache/test/resources/DefaultCache.java
@@ -0,0 +1,402 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.processor.property_cache.test;
+
+import android.os.IpcDataCache;
+
+/**
+ * This class is auto-generated
+ *
+ * @hide
+ **/
+public class DefaultCache {
+ private static final Object sBirthdayLock = new Object();
+ private static IpcDataCache<java.lang.Integer, java.util.Date> sBirthday;
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link android.processor.property_cache.test.Default#getBirthday }
+ * @param bypassPredicate - lambda to bypass remote call
+ * @param query - parameter to call remote lambda
+ * @hide
+ */
+ public static java.util.Date getBirthday(
+ IpcDataCache.RemoteCall<java.lang.Integer, java.util.Date> binderCall,
+ IpcDataCache.BypassCall<java.lang.Integer> bypassPredicate, java.lang.Integer query
+ ) {
+ if (sBirthday != null) {
+ return sBirthday.query(query);
+ }
+ synchronized (sBirthdayLock) {
+ if (sBirthday == null) {
+ sBirthday = new IpcDataCache(
+ new IpcDataCache.Config(32, "system_server",
+ "default_birthday", "Birthday"),
+ binderCall, bypassPredicate);
+
+ }
+ }
+ return sBirthday.query(query);
+ }
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link android.processor.property_cache.test.Default#getBirthday }
+ * @param query - parameter to call remote lambda
+ * @hide
+ */
+ public static java.util.Date getBirthday(
+ IpcDataCache.RemoteCall<java.lang.Integer, java.util.Date> binderCall,
+ java.lang.Integer query
+ ) {
+ if (sBirthday != null) {
+ return sBirthday.query(query);
+ }
+ synchronized (sBirthdayLock) {
+ if (sBirthday == null) {
+ sBirthday = new IpcDataCache(
+ new IpcDataCache.Config(32, "system_server",
+ "default_birthday", "Birthday"),
+ binderCall);
+
+ }
+ }
+ return sBirthday.query(query);
+ }
+
+ /**
+ * This method is auto-generated- invalidate cache for
+ * {@link android.processor.property_cache.test.Default#getBirthday}
+ *
+ * @hide
+ */
+ public static final void invalidateBirthday() {
+ IpcDataCache.invalidateCache("system_server", "default_birthday");
+ }
+
+ private static final Object sDaysTillBirthdayLock = new Object();
+ private static IpcDataCache<java.lang.Integer, java.lang.Integer> sDaysTillBirthday;
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link
+ * android.processor.property_cache.test.Default#getDaysTillBirthday }
+ * @param bypassPredicate - lambda to bypass remote call
+ * @param query - parameter to call remote lambda
+ * @hide
+ */
+ public static java.lang.Integer getDaysTillBirthday(
+ IpcDataCache.RemoteCall<java.lang.Integer, java.lang.Integer> binderCall,
+ IpcDataCache.BypassCall<java.lang.Integer> bypassPredicate, java.lang.Integer query
+ ) {
+ if (sDaysTillBirthday != null) {
+ return sDaysTillBirthday.query(query);
+ }
+ synchronized (sDaysTillBirthdayLock) {
+ if (sDaysTillBirthday == null) {
+ sDaysTillBirthday = new IpcDataCache(
+ new IpcDataCache.Config(32, "system_server", "default_days_till_birthday",
+ "DaysTillBirthday"), binderCall, bypassPredicate);
+
+ }
+ }
+ return sDaysTillBirthday.query(query);
+ }
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link android.processor.property_cache.test.Default#getDaysTillBirthday
+ * }
+ * @param query - parameter to call remote lambda
+ * @hide
+ */
+ public static java.lang.Integer getDaysTillBirthday(
+ IpcDataCache.RemoteCall<java.lang.Integer, java.lang.Integer> binderCall,
+ java.lang.Integer query
+ ) {
+ if (sDaysTillBirthday != null) {
+ return sDaysTillBirthday.query(query);
+ }
+ synchronized (sDaysTillBirthdayLock) {
+ if (sDaysTillBirthday == null) {
+ sDaysTillBirthday = new IpcDataCache(
+ new IpcDataCache.Config(32, "system_server", "default_days_till_birthday",
+ "DaysTillBirthday"), binderCall);
+
+ }
+ }
+ return sDaysTillBirthday.query(query);
+ }
+
+ /**
+ * This method is auto-generated- invalidate cache for
+ * {@link android.processor.property_cache.test.Default#getDaysTillBirthday}
+ *
+ * @hide
+ */
+ public static final void invalidateDaysTillBirthday() {
+ IpcDataCache.invalidateCache("system_server", "default_days_till_birthday");
+ }
+
+ private final Object mDaysSinceBirthdayLock = new Object();
+ private IpcDataCache<java.lang.Integer, java.lang.Integer> mDaysSinceBirthday;
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link
+ * android.processor.property_cache.test.Default#getDaysSinceBirthday }
+ * @param bypassPredicate - lambda to bypass remote call
+ * @param query - parameter to call remote lambda
+ * @hide
+ */
+ public java.lang.Integer getDaysSinceBirthday(
+ IpcDataCache.RemoteCall<java.lang.Integer, java.lang.Integer> binderCall,
+ IpcDataCache.BypassCall<java.lang.Integer> bypassPredicate, java.lang.Integer query
+ ) {
+ if (mDaysSinceBirthday != null) {
+ return mDaysSinceBirthday.query(query);
+ }
+ synchronized (mDaysSinceBirthdayLock) {
+ if (mDaysSinceBirthday == null) {
+ mDaysSinceBirthday = new IpcDataCache(
+ new IpcDataCache.Config(32, "system_server", "default_days_since_birthday",
+ "DaysSinceBirthday"), binderCall, bypassPredicate);
+
+ }
+ }
+ return mDaysSinceBirthday.query(query);
+ }
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link android.processor.property_cache.test.Default#getDaysSinceBirthday
+ * }
+ * @param query - parameter to call remote lambda
+ * @hide
+ */
+ public java.lang.Integer getDaysSinceBirthday(
+ IpcDataCache.RemoteCall<java.lang.Integer, java.lang.Integer> binderCall,
+ java.lang.Integer query
+ ) {
+ if (mDaysSinceBirthday != null) {
+ return mDaysSinceBirthday.query(query);
+ }
+ synchronized (mDaysSinceBirthdayLock) {
+ if (mDaysSinceBirthday == null) {
+ mDaysSinceBirthday = new IpcDataCache(
+ new IpcDataCache.Config(32, "system_server", "default_days_since_birthday",
+ "DaysSinceBirthday"), binderCall);
+
+ }
+ }
+ return mDaysSinceBirthday.query(query);
+ }
+
+ /**
+ * This method is auto-generated- invalidate cache for
+ * {@link android.processor.property_cache.test.Default#getDaysSinceBirthday}
+ *
+ * @hide
+ */
+ public static final void invalidateDaysSinceBirthday() {
+ IpcDataCache.invalidateCache("system_server", "default_days_since_birthday");
+ }
+
+ private static final Object sDaysTillMyBirthdayLock = new Object();
+ private static IpcDataCache<java.lang.Void, java.lang.Integer> sDaysTillMyBirthday;
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link android.processor.property_cache.test.Default#getDaysTillMyBirthday
+ * }
+ * @hide
+ */
+ public static java.lang.Integer getDaysTillMyBirthday(
+ IpcDataCache.RemoteCall<java.lang.Void, java.lang.Integer> binderCall
+ ) {
+ if (sDaysTillMyBirthday != null) {
+ return sDaysTillMyBirthday.query(null);
+ }
+ synchronized (sDaysTillMyBirthdayLock) {
+ if (sDaysTillMyBirthday == null) {
+ sDaysTillMyBirthday = new IpcDataCache(
+ new IpcDataCache.Config(1, "system_server", "default_days_till_my_birthday",
+ "DaysTillMyBirthday"), binderCall);
+
+ }
+ }
+ return sDaysTillMyBirthday.query(null);
+ }
+
+ /**
+ * This method is auto-generated- invalidate cache for
+ * {@link android.processor.property_cache.test.Default#getDaysTillMyBirthday}
+ *
+ * @hide
+ */
+ public static final void invalidateDaysTillMyBirthday() {
+ IpcDataCache.invalidateCache("system_server", "default_days_till_my_birthday");
+ }
+
+ private final Object mDaysSinceMyBirthdayLock = new Object();
+ private IpcDataCache<java.lang.Void, java.lang.Integer> mDaysSinceMyBirthday;
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link
+ * android.processor.property_cache.test.Default#getDaysSinceMyBirthday }
+ * @hide
+ */
+ public java.lang.Integer getDaysSinceMyBirthday(
+ IpcDataCache.RemoteCall<java.lang.Void, java.lang.Integer> binderCall
+ ) {
+ if (mDaysSinceMyBirthday != null) {
+ return mDaysSinceMyBirthday.query(null);
+ }
+ synchronized (mDaysSinceMyBirthdayLock) {
+ if (mDaysSinceMyBirthday == null) {
+ mDaysSinceMyBirthday = new IpcDataCache(
+ new IpcDataCache.Config(1, "system_server", "my_unique_key",
+ "DaysSinceMyBirthday"), binderCall);
+
+ }
+ }
+ return mDaysSinceMyBirthday.query(null);
+ }
+
+ /**
+ * This method is auto-generated- invalidate cache for
+ * {@link android.processor.property_cache.test.Default#getDaysSinceMyBirthday}
+ *
+ * @hide
+ */
+ public static final void invalidateDaysSinceMyBirthday() {
+ IpcDataCache.invalidateCache("system_server", "my_unique_key");
+ }
+
+ private static final Object sBirthdayWishesFromUserLock = new Object();
+ private static IpcDataCache<java.lang.Integer, java.lang.String> sBirthdayWishesFromUser;
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link
+ *
+ * android.processor.property_cache.test.Default#getBirthdayWishesFromUser
+ * }
+ * @param bypassPredicate - lambda to bypass remote call
+ * @param query - parameter to call remote lambda
+ * @hide
+ */
+ public static java.lang.String getBirthdayWishesFromUser(
+ IpcDataCache.RemoteCall<java.lang.Integer, java.lang.String> binderCall,
+ IpcDataCache.BypassCall<java.lang.Integer> bypassPredicate, java.lang.Integer query
+ ) {
+ if (sBirthdayWishesFromUser != null) {
+ return sBirthdayWishesFromUser.query(query);
+ }
+ synchronized (sBirthdayWishesFromUserLock) {
+ if (sBirthdayWishesFromUser == null) {
+ sBirthdayWishesFromUser = new IpcDataCache(
+ new IpcDataCache.Config(32, "telephony",
+ "default_birthday_wishes_from_user",
+ "BirthdayWishesFromUser"), binderCall, bypassPredicate);
+
+ }
+ }
+ return sBirthdayWishesFromUser.query(query);
+ }
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link
+ * android.processor.property_cache.test.Default#getBirthdayWishesFromUser }
+ * @param query - parameter to call remote lambda
+ * @hide
+ */
+ public static java.lang.String getBirthdayWishesFromUser(
+ IpcDataCache.RemoteCall<java.lang.Integer, java.lang.String> binderCall,
+ java.lang.Integer query
+ ) {
+ if (sBirthdayWishesFromUser != null) {
+ return sBirthdayWishesFromUser.query(query);
+ }
+ synchronized (sBirthdayWishesFromUserLock) {
+ if (sBirthdayWishesFromUser == null) {
+ sBirthdayWishesFromUser = new IpcDataCache(
+ new IpcDataCache.Config(32, "telephony",
+ "default_birthday_wishes_from_user",
+ "BirthdayWishesFromUser"), binderCall);
+
+ }
+ }
+ return sBirthdayWishesFromUser.query(query);
+ }
+
+ /**
+ * This method is auto-generated- invalidate cache for
+ * {@link android.processor.property_cache.test.Default#getBirthdayWishesFromUser}
+ *
+ * @hide
+ */
+ public static final void invalidateBirthdayWishesFromUser() {
+ IpcDataCache.invalidateCache("telephony", "default_birthday_wishes_from_user");
+ }
+
+
+ /**
+ * This method is auto-generated - initialise all caches for class DefaultCache
+ *
+ * @hide
+ */
+ public static void initCache() {
+ DefaultCache.invalidateBirthday();
+ DefaultCache.invalidateDaysTillBirthday();
+ DefaultCache.invalidateDaysSinceBirthday();
+ DefaultCache.invalidateDaysTillMyBirthday();
+ DefaultCache.invalidateDaysSinceMyBirthday();
+ DefaultCache.invalidateBirthdayWishesFromUser();
+ }
+}