Merge changes I14b726e9,I84244b34,I9d21f576 into main
* changes:
[PIP2] Hide PiP menu when PiP is moved or resized.
[PIP2] Hook up expand icon to PipScheduler#scheduleExitPipViaExpand.
[PIP2] Attach PiP menu to leash.
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/apex/jobscheduler/framework/java/android/app/job/JobParameters.java b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
index 52a761f..31d2ecd 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
@@ -34,6 +34,7 @@
import android.os.Parcelable;
import android.os.PersistableBundle;
import android.os.RemoteException;
+import android.os.Process;
import android.system.SystemCleaner;
import android.util.Log;
@@ -638,6 +639,12 @@
* @hide
*/
public void enableCleaner() {
+ // JobParameters objects are passed by reference in local Binder
+ // transactions for clients running as SYSTEM. The life cycle of the
+ // JobParameters objects are no longer controlled by the client.
+ if (Process.myUid() == Process.SYSTEM_UID) {
+ return;
+ }
if (mJobCleanupCallback == null) {
initCleaner(new JobCleanupCallback(IJobCallback.Stub.asInterface(callback), jobId));
}
diff --git a/core/api/current.txt b/core/api/current.txt
index 5c624a9..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
}
@@ -6858,43 +6860,43 @@
@FlaggedApi("android.app.api_rich_ongoing") public static class Notification.ProgressStyle extends android.app.Notification.Style {
ctor public Notification.ProgressStyle();
+ method @NonNull public android.app.Notification.ProgressStyle addProgressPoint(@NonNull android.app.Notification.ProgressStyle.Point);
method @NonNull public android.app.Notification.ProgressStyle addProgressSegment(@NonNull android.app.Notification.ProgressStyle.Segment);
- method @NonNull public android.app.Notification.ProgressStyle addProgressStep(@NonNull android.app.Notification.ProgressStyle.Step);
method public int getProgress();
method @Nullable public android.graphics.drawable.Icon getProgressEndIcon();
method public int getProgressMax();
+ method @NonNull public java.util.List<android.app.Notification.ProgressStyle.Point> getProgressPoints();
method @NonNull public java.util.List<android.app.Notification.ProgressStyle.Segment> getProgressSegments();
method @Nullable public android.graphics.drawable.Icon getProgressStartIcon();
- method @NonNull public java.util.List<android.app.Notification.ProgressStyle.Step> getProgressSteps();
method @Nullable public android.graphics.drawable.Icon getProgressTrackerIcon();
method public boolean isProgressIndeterminate();
method public boolean isStyledByProgress();
method @NonNull public android.app.Notification.ProgressStyle setProgress(int);
method @NonNull public android.app.Notification.ProgressStyle setProgressEndIcon(@Nullable android.graphics.drawable.Icon);
method @NonNull public android.app.Notification.ProgressStyle setProgressIndeterminate(boolean);
+ method @NonNull public android.app.Notification.ProgressStyle setProgressPoints(@NonNull java.util.List<android.app.Notification.ProgressStyle.Point>);
method @NonNull public android.app.Notification.ProgressStyle setProgressSegments(@NonNull java.util.List<android.app.Notification.ProgressStyle.Segment>);
method @NonNull public android.app.Notification.ProgressStyle setProgressStartIcon(@Nullable android.graphics.drawable.Icon);
- method @NonNull public android.app.Notification.ProgressStyle setProgressSteps(@NonNull java.util.List<android.app.Notification.ProgressStyle.Step>);
method @NonNull public android.app.Notification.ProgressStyle setProgressTrackerIcon(@Nullable android.graphics.drawable.Icon);
method @NonNull public android.app.Notification.ProgressStyle setStyledByProgress(boolean);
}
+ public static final class Notification.ProgressStyle.Point {
+ ctor public Notification.ProgressStyle.Point(int);
+ method @ColorInt public int getColor();
+ method public int getId();
+ method public int getPosition();
+ method @NonNull public android.app.Notification.ProgressStyle.Point setColor(@ColorInt int);
+ method @NonNull public android.app.Notification.ProgressStyle.Point setId(int);
+ }
+
public static final class Notification.ProgressStyle.Segment {
ctor public Notification.ProgressStyle.Segment(int);
method @ColorInt public int getColor();
+ method public int getId();
method public int getLength();
- method public int getStableId();
method @NonNull public android.app.Notification.ProgressStyle.Segment setColor(@ColorInt int);
- method @NonNull public android.app.Notification.ProgressStyle.Segment setStableId(int);
- }
-
- public static final class Notification.ProgressStyle.Step {
- ctor public Notification.ProgressStyle.Step(int);
- method @ColorInt public int getColor();
- method public int getPosition();
- method public int getStableId();
- method @NonNull public android.app.Notification.ProgressStyle.Step setColor(@ColorInt int);
- method @NonNull public android.app.Notification.ProgressStyle.Step setStableId(int);
+ method @NonNull public android.app.Notification.ProgressStyle.Segment setId(int);
}
public abstract static class Notification.Style {
@@ -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
@@ -8787,7 +8791,7 @@
@FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public abstract class AppFunctionService extends android.app.Service {
ctor public AppFunctionService();
method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent);
- method @Deprecated @MainThread public abstract void onExecuteFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>);
+ method @Deprecated @MainThread public void onExecuteFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>);
method @MainThread public void onExecuteFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>);
field @NonNull public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService";
}
@@ -8822,13 +8826,12 @@
field @NonNull public static final android.os.Parcelable.Creator<android.app.appfunctions.ExecuteAppFunctionResponse> CREATOR;
field public static final String PROPERTY_RETURN_VALUE = "returnValue";
field public static final int RESULT_APP_UNKNOWN_ERROR = 2; // 0x2
- field public static final int RESULT_CANCELLED = 7; // 0x7
+ field public static final int RESULT_CANCELLED = 6; // 0x6
field public static final int RESULT_DENIED = 1; // 0x1
- field public static final int RESULT_DISABLED = 6; // 0x6
+ field public static final int RESULT_DISABLED = 5; // 0x5
field public static final int RESULT_INTERNAL_ERROR = 3; // 0x3
field public static final int RESULT_INVALID_ARGUMENT = 4; // 0x4
field public static final int RESULT_OK = 0; // 0x0
- field public static final int RESULT_TIMED_OUT = 5; // 0x5
}
}
@@ -12114,6 +12117,7 @@
method @NonNull public void setResourceValue(@NonNull String, int, @NonNull String, @Nullable String);
method @NonNull public void setResourceValue(@NonNull String, @NonNull android.os.ParcelFileDescriptor, @Nullable String);
method @FlaggedApi("android.content.res.asset_file_descriptor_frro") @NonNull public void setResourceValue(@NonNull String, @NonNull android.content.res.AssetFileDescriptor, @Nullable String);
+ method @FlaggedApi("android.content.res.dimension_frro") public void setResourceValue(@NonNull String, float, int, @Nullable String);
method public void setTargetOverlayable(@Nullable String);
}
@@ -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
@@ -32821,7 +32824,7 @@
field @NonNull public static final String RELEASE_OR_PREVIEW_DISPLAY;
field @Deprecated public static final String SDK;
field public static final int SDK_INT;
- field @FlaggedApi("android.sdk.major_minor_versioning_scheme") public static final int SDK_MINOR_INT;
+ field @FlaggedApi("android.sdk.major_minor_versioning_scheme") public static final int SDK_INT_FULL;
field public static final String SECURITY_PATCH;
}
@@ -32865,6 +32868,9 @@
field public static final int VANILLA_ICE_CREAM = 35; // 0x23
}
+ @FlaggedApi("android.sdk.major_minor_versioning_scheme") public static class Build.VERSION_CODES_FULL {
+ }
+
public final class Bundle extends android.os.BaseBundle implements java.lang.Cloneable android.os.Parcelable {
ctor public Bundle();
ctor public Bundle(ClassLoader);
@@ -36989,7 +36995,7 @@
}
@FlaggedApi("android.provider.new_default_account_api_enabled") public static final class ContactsContract.RawContacts.DefaultAccount {
- ctor public ContactsContract.RawContacts.DefaultAccount();
+ method @FlaggedApi("android.provider.new_default_account_api_enabled") @NonNull public static android.provider.ContactsContract.RawContacts.DefaultAccount.DefaultAccountAndState getDefaultAccountForNewContacts(@NonNull android.content.ContentResolver);
}
@FlaggedApi("android.provider.new_default_account_api_enabled") public static final class ContactsContract.RawContacts.DefaultAccount.DefaultAccountAndState {
@@ -37429,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";
@@ -61652,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 4b6c62e..879c7a2 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();
@@ -11950,6 +11955,10 @@
field @Deprecated public static final String STATE = "state";
}
+ @FlaggedApi("android.provider.new_default_account_api_enabled") public static final class ContactsContract.RawContacts.DefaultAccount {
+ method @FlaggedApi("android.provider.new_default_account_api_enabled") @RequiresPermission(android.Manifest.permission.SET_DEFAULT_ACCOUNT_FOR_CONTACTS) public static void setDefaultAccountForNewContacts(@NonNull android.content.ContentResolver, @NonNull android.provider.ContactsContract.RawContacts.DefaultAccount.DefaultAccountAndState);
+ }
+
public static final class ContactsContract.Settings implements android.provider.ContactsContract.SettingsColumns {
method @RequiresPermission(android.Manifest.permission.SET_DEFAULT_ACCOUNT_FOR_CONTACTS) public static void setDefaultAccount(@NonNull android.content.ContentResolver, @Nullable android.accounts.Account);
}
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 5b556cc..0c02ba4 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1960,8 +1960,12 @@
@Override
public void dumpCacheInfo(ParcelFileDescriptor pfd, String[] args) {
- PropertyInvalidatedCache.dumpCacheInfo(pfd, args);
- IoUtils.closeQuietly(pfd);
+ try {
+ PropertyInvalidatedCache.dumpCacheInfo(pfd, args);
+ BroadcastStickyCache.dump(pfd);
+ } finally {
+ IoUtils.closeQuietly(pfd);
+ }
}
private File getDatabasesDir(Context context) {
@@ -6281,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/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index f27dc32..5907af0 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -10153,6 +10153,9 @@
}
p.writeInt(Parcel.EX_HAS_NOTED_APPOPS_REPLY_HEADER);
+ final int sizePosition = p.dataPosition();
+ // Write size placeholder. With this size we can easily skip it in native.
+ p.writeInt(0);
int numAttributionWithNotesAppOps = notedAppOps.size();
p.writeInt(numAttributionWithNotesAppOps);
@@ -10169,6 +10172,12 @@
}
}
}
+
+ final int payloadPosition = p.dataPosition();
+ p.setDataPosition(sizePosition);
+ // Total header size including 4 bytes size itself.
+ p.writeInt(payloadPosition - sizePosition);
+ p.setDataPosition(payloadPosition);
}
/**
@@ -10182,6 +10191,8 @@
* @hide
*/
public static void readAndLogNotedAppops(@NonNull Parcel p) {
+ // Skip size.
+ p.readInt();
int numAttributionsWithNotedAppOps = p.readInt();
for (int i = 0; i < numAttributionsWithNotedAppOps; i++) {
diff --git a/core/java/android/app/BroadcastStickyCache.java b/core/java/android/app/BroadcastStickyCache.java
index d6f061b..ea81731 100644
--- a/core/java/android/app/BroadcastStickyCache.java
+++ b/core/java/android/app/BroadcastStickyCache.java
@@ -27,16 +27,21 @@
import android.net.nsd.NsdManager;
import android.net.wifi.WifiManager;
import android.net.wifi.p2p.WifiP2pManager;
+import android.os.ParcelFileDescriptor;
import android.os.SystemProperties;
import android.os.UpdateLock;
import android.telephony.TelephonyManager;
import android.util.ArrayMap;
+import android.util.IndentingPrintWriter;
import android.view.WindowManagerPolicyConstants;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FastPrintWriter;
+import java.io.FileOutputStream;
+import java.io.PrintWriter;
import java.util.ArrayList;
/** @hide */
@@ -214,6 +219,41 @@
}
}
+ public static void dump(@NonNull ParcelFileDescriptor pfd) {
+ if (!Flags.useStickyBcastCache()) {
+ return;
+ }
+ final PrintWriter pw = new FastPrintWriter(new FileOutputStream(pfd.getFileDescriptor()));
+ synchronized (sCachedStickyBroadcasts) {
+ dumpLocked(pw);
+ }
+ pw.flush();
+ }
+
+ @GuardedBy("sCachedStickyBroadcasts")
+ private static void dumpLocked(@NonNull PrintWriter pw) {
+ final IndentingPrintWriter ipw = new IndentingPrintWriter(
+ pw, " " /* singleIndent */, " " /* prefix */);
+ ipw.println("Cached sticky broadcasts:");
+ ipw.increaseIndent();
+ final int count = sCachedStickyBroadcasts.size();
+ if (count == 0) {
+ ipw.println("<empty>");
+ } else {
+ for (int i = 0; i < count; ++i) {
+ final CachedStickyBroadcast cachedStickyBroadcast = sCachedStickyBroadcasts.get(i);
+ ipw.print("Entry #"); ipw.print(i); ipw.println(":");
+ ipw.increaseIndent();
+ ipw.print("filter="); ipw.println(cachedStickyBroadcast.filter.toLongString());
+ ipw.print("intent="); ipw.println(cachedStickyBroadcast.intent);
+ ipw.print("version="); ipw.println(cachedStickyBroadcast.version);
+ ipw.print("handle="); ipw.println(cachedStickyBroadcast.propertyHandle);
+ ipw.decreaseIndent();
+ }
+ }
+ ipw.decreaseIndent();
+ }
+
private static final class CachedStickyBroadcast {
@NonNull public final IntentFilter filter;
@Nullable public Intent intent;
diff --git a/core/java/android/app/IUserSwitchObserver.aidl b/core/java/android/app/IUserSwitchObserver.aidl
index cfdb426..1ff7a17 100644
--- a/core/java/android/app/IUserSwitchObserver.aidl
+++ b/core/java/android/app/IUserSwitchObserver.aidl
@@ -19,10 +19,10 @@
import android.os.IRemoteCallback;
/** {@hide} */
-oneway interface IUserSwitchObserver {
+interface IUserSwitchObserver {
void onBeforeUserSwitching(int newUserId);
- void onUserSwitching(int newUserId, IRemoteCallback reply);
- void onUserSwitchComplete(int newUserId);
- void onForegroundProfileSwitch(int newProfileId);
- void onLockedBootComplete(int newUserId);
+ oneway void onUserSwitching(int newUserId, IRemoteCallback reply);
+ oneway void onUserSwitchComplete(int newUserId);
+ oneway void onForegroundProfileSwitch(int newProfileId);
+ oneway void onLockedBootComplete(int newUserId);
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index e8b0a36f..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,
@@ -1637,16 +1641,16 @@
public static final String EXTRA_PROGRESS_SEGMENTS = "android.progressSegments";
/**
- * {@link #extras} key: an arraylist of {@link android.app.Notification.ProgressStyle.Step}
+ * {@link #extras} key: an arraylist of {@link ProgressStyle.Point}
* bundles provided by a
* {@link android.app.Notification.ProgressStyle} notification as supplied to
- * {@link ProgressStyle#setProgressSteps}
- * or {@link ProgressStyle#addProgressStep(ProgressStyle.Step)}.
+ * {@link ProgressStyle#setProgressPoints}
+ * or {@link ProgressStyle#addProgressPoint(ProgressStyle.Point)}.
* This extra is a parcelable array list of bundles.
* @hide
*/
@FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
- public static final String EXTRA_PROGRESS_STEPS = "android.progressSteps";
+ public static final String EXTRA_PROGRESS_POINTS = "android.progressPoints";
/**
* {@link #extras} key: whether the progress bar should be styled by its progress as
@@ -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.
@@ -11159,7 +11163,7 @@
/**
* A Notification Style used to to define a notification whose expanded state includes
- * a highly customizable progress bar with segments, steps, a custom tracker icon,
+ * a highly customizable progress bar with segments, points, a custom tracker icon,
* and custom icons at the start and end of the progress bar.
*
* This style is suggested for use cases where the app is showing a tracker to the
@@ -11185,8 +11189,8 @@
* .addProgressSegment(new Segment(552).setColor(Color.YELLOW))
* .addProgressSegment(new Segment(253).setColor(Color.YELLOW))
* .addProgressSegment(new Segment(94).setColor(Color.BLUE))
- * .addProgressStep(new Step(60).setColor(Color.RED))
- * .addProgressStep(new Step(560).setColor(Color.YELLOW))
+ * .addProgressPoint(new Point(60).setColor(Color.RED))
+ * .addProgressPoint(new Point(560).setColor(Color.YELLOW))
* )
* </pre>
*
@@ -11199,17 +11203,17 @@
*/
@FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
public static class ProgressStyle extends Notification.Style {
- private static final String KEY_ELEMENT_STABLE_ID = "stableId";
+ private static final String KEY_ELEMENT_ID = "id";
private static final String KEY_ELEMENT_COLOR = "colorInt";
private static final String KEY_SEGMENT_LENGTH = "length";
- private static final String KEY_STEP_POSITION = "position";
+ private static final String KEY_POINT_POSITION = "position";
private static final int MAX_PROGRESS_SEGMENT_LIMIT = 15;
- private static final int MAX_PROGRESS_STEP_LIMIT = 5;
+ private static final int MAX_PROGRESS_STOP_LIMIT = 5;
private static final int DEFAULT_PROGRESS_MAX = 100;
private List<Segment> mProgressSegments = new ArrayList<>();
- private List<Step> mProgressSteps = new ArrayList<>();
+ private List<Point> mProgressPoints = new ArrayList<>();
private int mProgress = 0;
@@ -11246,7 +11250,7 @@
nonIndeterminateCheckResult = !Objects.equals(mProgress, progressStyle.mProgress)
|| !Objects.equals(mIsStyledByProgress, progressStyle.mIsStyledByProgress)
|| !Objects.equals(mProgressSegments, progressStyle.mProgressSegments)
- || !Objects.equals(mProgressSteps, progressStyle.mProgressSteps)
+ || !Objects.equals(mProgressPoints, progressStyle.mProgressPoints)
|| !Objects.equals(mTrackerIcon, progressStyle.mTrackerIcon);
}
@@ -11300,48 +11304,47 @@
}
/**
- * Gets the steps that are displayed on the progress bar.
+ * Gets the points that are displayed on the progress bar.
*.
- * @see #setProgressSteps
- * @see #addProgressStep
- * @see Step
+ * @see #setProgressPoints
+ * @see #addProgressPoint
+ * @see Point
*/
- public @NonNull List<Step> getProgressSteps() {
- return mProgressSteps;
+ public @NonNull List<Point> getProgressPoints() {
+ return mProgressPoints;
}
/**
- * Replaces all the progress steps.
+ * Replaces all the progress points.
*
- * Steps are designated points within a progressbar to visualize
- * distinct stages or milestones.
- * For example, you might use steps to mark stops in a multi-stop
- * navigation journey, where each step represents a destination.
- * @see Step
+ * Points within a progress bar are used to visualize distinct stages or milestones.
+ * For example, you might use points to mark stops in a multi-stop
+ * navigation journey, where each point represents a destination.
+ * @see Point
*/
- public @NonNull ProgressStyle setProgressSteps(@NonNull List<Step> steps) {
- mProgressSteps = new ArrayList<>(steps);
+ public @NonNull ProgressStyle setProgressPoints(@NonNull List<Point> points) {
+ mProgressPoints = new ArrayList<>(points);
return this;
}
/**
- * Adds another step.
+ * Adds another point.
*
- * Steps are designated points within a progressbar to visualize
- * distinct stages or milestones.
- * For example, you might use steps to mark stops in a multi-stop
- * navigation journey, where each step represents a destination.
+ * Points within a progress bar are used to visualize distinct stages or milestones.
*
- * Steps can be added in any order, as their
+ * For example, you might use points to mark stops in a multi-stop
+ * navigation journey, where each point represents a destination.
+ *
+ * Points can be added in any order, as their
* position within the progress bar is determined by their individual
- * {@link Step#getPosition()}.
- * @see Step
+ * {@link Point#getPosition()}.
+ * @see Point
*/
- public @NonNull ProgressStyle addProgressStep(@NonNull Step step) {
- if (mProgressSteps == null) {
- mProgressSteps = new ArrayList<>();
+ public @NonNull ProgressStyle addProgressPoint(@NonNull Point point) {
+ if (mProgressPoints == null) {
+ mProgressPoints = new ArrayList<>();
}
- mProgressSteps.add(step);
+ mProgressPoints.add(point);
return this;
}
@@ -11414,7 +11417,7 @@
* When specified, the following fields are ignored:
* @see #setProgress
* @see #setProgressSegments
- * @see #setProgressSteps
+ * @see #setProgressPoints
* @see #setProgressTrackerIcon
* @see #setStyledByProgress
*
@@ -11435,7 +11438,7 @@
}
/**
- * Indicates whether the segments and steps will be styled differently
+ * Indicates whether the segments and points will be styled differently
* based on whether they are behind or ahead of the current progress.
* When true, segments appearing ahead of the current progress will be given a
* slightly different appearance to indicate that it is part of the progress bar
@@ -11558,8 +11561,8 @@
super.addExtras(extras);
extras.putParcelableArrayList(EXTRA_PROGRESS_SEGMENTS,
getProgressSegmentsAsBundleList(mProgressSegments));
- extras.putParcelableArrayList(EXTRA_PROGRESS_STEPS,
- getProgressStepsAsBundleList(mProgressSteps));
+ extras.putParcelableArrayList(EXTRA_PROGRESS_POINTS,
+ getProgressPointsAsBundleList(mProgressPoints));
extras.putInt(EXTRA_PROGRESS, mProgress);
extras.putBoolean(EXTRA_PROGRESS_INDETERMINATE, mIndeterminate);
@@ -11599,8 +11602,8 @@
mTrackerIcon = extras.getParcelable(EXTRA_PROGRESS_TRACKER_ICON, Icon.class);
mStartIcon = extras.getParcelable(EXTRA_PROGRESS_START_ICON, Icon.class);
mEndIcon = extras.getParcelable(EXTRA_PROGRESS_END_ICON, Icon.class);
- mProgressSteps = getProgressStepsFromBundleList(
- extras.getParcelableArrayList(EXTRA_PROGRESS_STEPS, Bundle.class));
+ mProgressPoints = getProgressPointsFromBundleList(
+ extras.getParcelableArrayList(EXTRA_PROGRESS_POINTS, Bundle.class));
}
/**
@@ -11613,6 +11616,30 @@
// actually be included.
return true;
}
+ /**
+ * @hide
+ */
+ @Override
+ public RemoteViews makeContentView(boolean increasedHeight) {
+ final StandardTemplateParams p = mBuilder.mParams.reset()
+ .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
+ .hideProgress(true)
+ .fillTextsFrom(mBuilder);
+
+ return getStandardView(mBuilder.getBaseLayoutResource(), p, null /* result */);
+ }
+ /**
+ * @hide
+ */
+ @Override
+ public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
+ final StandardTemplateParams p = mBuilder.mParams.reset()
+ .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP)
+ .hideProgress(true)
+ .fillTextsFrom(mBuilder);
+
+ return getStandardView(mBuilder.getHeadsUpBaseLayoutResource(), p, null /* result */);
+ }
private static @NonNull ArrayList<Bundle> getProgressSegmentsAsBundleList(
@Nullable List<Segment> progressSegments) {
@@ -11626,7 +11653,7 @@
final Bundle bundle = new Bundle();
bundle.putInt(KEY_SEGMENT_LENGTH, segment.getLength());
- bundle.putInt(KEY_ELEMENT_STABLE_ID, segment.getStableId());
+ bundle.putInt(KEY_ELEMENT_ID, segment.getId());
bundle.putInt(KEY_ELEMENT_COLOR, segment.getColor());
segments.add(bundle);
@@ -11647,11 +11674,11 @@
continue;
}
- final int stableId = segmentBundle.getInt(KEY_ELEMENT_STABLE_ID);
+ final int id = segmentBundle.getInt(KEY_ELEMENT_ID);
final int color = segmentBundle.getInt(KEY_ELEMENT_COLOR,
Notification.COLOR_DEFAULT);
final Segment segment = new Segment(length)
- .setStableId(stableId).setColor(color);
+ .setId(id).setColor(color);
segments.add(segment);
}
@@ -11660,48 +11687,48 @@
return segments;
}
- private static @NonNull ArrayList<Bundle> getProgressStepsAsBundleList(
- @Nullable List<Step> progressSteps) {
- final ArrayList<Bundle> steps = new ArrayList<>();
- if (progressSteps != null && !progressSteps.isEmpty()) {
- for (int i = 0; i < progressSteps.size(); i++) {
- final Step step = progressSteps.get(i);
- if (step.getPosition() < 0) {
+ private static @NonNull ArrayList<Bundle> getProgressPointsAsBundleList(
+ @Nullable List<Point> progressPoints) {
+ final ArrayList<Bundle> points = new ArrayList<>();
+ if (progressPoints != null && !progressPoints.isEmpty()) {
+ for (int i = 0; i < progressPoints.size(); i++) {
+ final Point point = progressPoints.get(i);
+ if (point.getPosition() < 0) {
continue;
}
final Bundle bundle = new Bundle();
- bundle.putInt(KEY_STEP_POSITION, step.getPosition());
- bundle.putInt(KEY_ELEMENT_STABLE_ID, step.getStableId());
- bundle.putInt(KEY_ELEMENT_COLOR, step.getColor());
+ bundle.putInt(KEY_POINT_POSITION, point.getPosition());
+ bundle.putInt(KEY_ELEMENT_ID, point.getId());
+ bundle.putInt(KEY_ELEMENT_COLOR, point.getColor());
- steps.add(bundle);
+ points.add(bundle);
}
}
- return steps;
+ return points;
}
- private static @NonNull List<Step> getProgressStepsFromBundleList(
- @Nullable List<Bundle> stepBundleList) {
- final ArrayList<Step> steps = new ArrayList<>();
+ private static @NonNull List<Point> getProgressPointsFromBundleList(
+ @Nullable List<Bundle> pointBundleList) {
+ final ArrayList<Point> points = new ArrayList<>();
- if (stepBundleList != null && !stepBundleList.isEmpty()) {
- for (int i = 0; i < stepBundleList.size(); i++) {
- final Bundle segmentBundle = stepBundleList.get(i);
- final int position = segmentBundle.getInt(KEY_STEP_POSITION);
+ if (pointBundleList != null && !pointBundleList.isEmpty()) {
+ for (int i = 0; i < pointBundleList.size(); i++) {
+ final Bundle pointBundle = pointBundleList.get(i);
+ final int position = pointBundle.getInt(KEY_POINT_POSITION);
if (position < 0) {
continue;
}
- final int stableId = segmentBundle.getInt(KEY_ELEMENT_STABLE_ID);
- final int color = segmentBundle.getInt(KEY_ELEMENT_COLOR,
+ final int id = pointBundle.getInt(KEY_ELEMENT_ID);
+ final int color = pointBundle.getInt(KEY_ELEMENT_COLOR,
Notification.COLOR_DEFAULT);
- final Step step = new Step(position).setStableId(stableId).setColor(color);
- steps.add(step);
+ final Point point = new Point(position).setId(id).setColor(color);
+ points.add(point);
}
}
- return steps;
+ return points;
}
/**
@@ -11712,7 +11739,7 @@
*/
public static final class Segment {
private int mLength;
- private int mStableId = 0;
+ private int mId = 0;
@ColorInt
private int mColor = Notification.COLOR_DEFAULT;
@@ -11735,19 +11762,19 @@
}
/**
- * Gets the stable id of this Segment.
+ * Gets the id of this Segment.
*
- * @see #setStableId
+ * @see #setId
*/
- public int getStableId() {
- return mStableId;
+ public int getId() {
+ return mId;
}
/**
* Optional ID used to uniquely identify the element across updates.
*/
- public @NonNull Segment setStableId(int stableId) {
- mStableId = stableId;
+ public @NonNull Segment setId(int id) {
+ mId = id;
return this;
}
@@ -11776,45 +11803,44 @@
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
- Segment segment = (Segment) o;
- return mLength == segment.mLength && mStableId == segment.mStableId
+ final Segment segment = (Segment) o;
+ return mLength == segment.mLength && mId == segment.mId
&& mColor == segment.mColor;
}
@Override
public int hashCode() {
- return Objects.hash(mLength, mStableId, mColor);
+ return Objects.hash(mLength, mId, mColor);
}
}
/**
- * A step within the progress bar, defining its position and color.
- * Steps are designated points within a progressbar to visualize
- * distinct stages or milestones.
- * For example, you might use steps to mark stops in a multi-stop
- * navigation journey, where each step represents a destination.
+ * A point within the progress bar, defining its position and color.
+ * Points within a progress bar are used to visualize distinct stages or milestones.
+ * For example, you might use points to mark stops in a multi-stop
+ * navigation journey, where each point represents a destination.
*/
- public static final class Step {
+ public static final class Point {
private int mPosition;
- private int mStableId;
+ private int mId;
@ColorInt
private int mColor = Notification.COLOR_DEFAULT;
/**
- * Create a step element.
- * The position of this step on the progress bar
+ * Create a point element.
+ * The position of this point on the progress bar
* relative to {@link ProgressStyle#getProgressMax}
* @param position
* See {@link #getPosition}
*/
- public Step(int position) {
+ public Point(int position) {
mPosition = position;
}
/**
- * Gets the position of this Step.
- * The position of this step on the progress bar
+ * Gets the position of this Point.
+ * The position of this point on the progress bar
* relative to {@link ProgressStyle#getProgressMax}.
*/
public int getPosition() {
@@ -11823,17 +11849,17 @@
/**
- * Optional ID used to uniqurely identify the element across updates.
+ * Optional ID used to uniquely identify the element across updates.
*/
- public int getStableId() {
- return mStableId;
+ public int getId() {
+ return mId;
}
/**
- * Optional ID used to uniqurely identify the element across updates.
+ * Optional ID used to uniquely identify the element across updates.
*/
- public @NonNull Step setStableId(int stableId) {
- mStableId = stableId;
+ public @NonNull Point setId(int id) {
+ mId = id;
return this;
}
@@ -11850,7 +11876,7 @@
/**
* Optional color of this Segment
*/
- public @NonNull Step setColor(@ColorInt int color) {
+ public @NonNull Point setColor(@ColorInt int color) {
mColor = color;
return this;
}
@@ -11862,14 +11888,14 @@
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
- Step step = (Step) o;
- return mPosition == step.mPosition && mStableId == step.mStableId
- && mColor == step.mColor;
+ final Point point = (Point) o;
+ return mPosition == point.mPosition && mId == point.mId
+ && mColor == point.mColor;
}
@Override
public int hashCode() {
- return Objects.hash(mPosition, mStableId, mColor);
+ return Objects.hash(mPosition, mId, mColor);
}
}
}
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/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/AppFunctionService.java b/core/java/android/app/appfunctions/AppFunctionService.java
index 8e41773..7a68a65 100644
--- a/core/java/android/app/appfunctions/AppFunctionService.java
+++ b/core/java/android/app/appfunctions/AppFunctionService.java
@@ -35,6 +35,7 @@
import android.os.CancellationSignal;
import android.os.RemoteCallback;
import android.os.RemoteException;
+import android.util.Log;
import java.util.function.Consumer;
@@ -166,9 +167,13 @@
*/
@MainThread
@Deprecated
- public abstract void onExecuteFunction(
+ public void onExecuteFunction(
@NonNull ExecuteAppFunctionRequest request,
- @NonNull Consumer<ExecuteAppFunctionResponse> callback);
+ @NonNull Consumer<ExecuteAppFunctionResponse> callback) {
+ Log.w(
+ "AppFunctionService",
+ "Calling deprecated default implementation of onExecuteFunction");
+ }
/**
* Called by the system to execute a specific app function.
diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
index 2851e92..a879b1b 100644
--- a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
+++ b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
@@ -96,17 +96,14 @@
*/
public static final int RESULT_INVALID_ARGUMENT = 4;
- /** The operation was timed out. */
- public static final int RESULT_TIMED_OUT = 5;
-
/** The caller tried to execute a disabled app function. */
- public static final int RESULT_DISABLED = 6;
+ public static final int RESULT_DISABLED = 5;
/**
* The operation was cancelled. Use this error code to report that a cancellation is done after
* receiving a cancellation signal.
*/
- public static final int RESULT_CANCELLED = 7;
+ public static final int RESULT_CANCELLED = 6;
/** The result code of the app function execution. */
@ResultCode private final int mResultCode;
@@ -282,7 +279,6 @@
RESULT_APP_UNKNOWN_ERROR,
RESULT_INTERNAL_ERROR,
RESULT_INVALID_ARGUMENT,
- RESULT_TIMED_OUT,
RESULT_DISABLED,
RESULT_CANCELLED
})
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 8916ce2..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;
@@ -84,11 +86,28 @@
int getDevicePolicy(int policyType);
/**
- * Returns whether the device has a valid microphone.
- */
+ * Returns whether the device has a valid microphone.
+ */
boolean hasCustomAudioInputSupport();
/**
+ * Returns whether this device is allowed to create mirror displays.
+ */
+ 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.
*/
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
@@ -132,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")
@@ -184,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);
/**
@@ -255,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);
@@ -262,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/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig
index e9fa3e1..9eb6d56 100644
--- a/core/java/android/companion/virtual/flags/flags.aconfig
+++ b/core/java/android/companion/virtual/flags/flags.aconfig
@@ -132,8 +132,16 @@
}
flag {
- namespace: "virtual_devices"
- name: "camera_timestamp_from_surface"
- description: "Pass the surface timestamp to the capture result"
- bug: "351341245"
+ namespace: "virtual_devices"
+ name: "camera_timestamp_from_surface"
+ description: "Pass the surface timestamp to the capture result"
+ bug: "351341245"
+}
+
+flag {
+ namespace: "virtual_devices"
+ name: "enable_limited_vdm_role"
+ description: "New VDM role without trusted displays or input"
+ bug: "370657575"
+ is_exported: true
}
diff --git a/core/java/android/content/om/FabricatedOverlay.java b/core/java/android/content/om/FabricatedOverlay.java
index 40ffb0f..64e9c33 100644
--- a/core/java/android/content/om/FabricatedOverlay.java
+++ b/core/java/android/content/om/FabricatedOverlay.java
@@ -476,6 +476,20 @@
return entry;
}
+ @NonNull
+ private static FabricatedOverlayInternalEntry generateFabricatedOverlayInternalEntry(
+ @NonNull String resourceName, float dimensionValue,
+ @TypedValue.ComplexDimensionUnit int dimensionUnit, @Nullable String configuration) {
+ final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
+ entry.resourceName = resourceName;
+ entry.dataType = TypedValue.TYPE_DIMENSION;
+ Preconditions.checkArgumentInRange(dimensionUnit,
+ TypedValue.COMPLEX_UNIT_PX, TypedValue.COMPLEX_UNIT_MM, "dimensionUnit");
+ entry.data = TypedValue.createComplexDimension(dimensionValue, dimensionUnit);
+ entry.configuration = configuration;
+ return entry;
+ }
+
/**
* Sets the resource value in the fabricated overlay for the integer-like types with the
* configuration.
@@ -586,4 +600,25 @@
mOverlay.entries.add(
generateFabricatedOverlayInternalEntry(resourceName, value, configuration));
}
+
+ /**
+ * Sets the resource value in the fabricated overlay for the dimension type with the
+ * configuration.
+ *
+ * @param resourceName name of the target resource to overlay (in the form
+ * [package]:type/entry)
+ * @param dimensionValue the float representing the dimension value
+ * @param dimensionUnit the integer representing the dimension unit
+ * @param configuration The string representation of the config this overlay is enabled for
+ */
+ @FlaggedApi(android.content.res.Flags.FLAG_DIMENSION_FRRO)
+ public void setResourceValue(
+ @NonNull String resourceName,
+ float dimensionValue,
+ @TypedValue.ComplexDimensionUnit int dimensionUnit,
+ @Nullable String configuration) {
+ ensureValidResourceName(resourceName);
+ mOverlay.entries.add(generateFabricatedOverlayInternalEntry(resourceName, dimensionValue,
+ dimensionUnit, configuration));
+ }
}
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index fd1a896..cf65539 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -68,6 +68,13 @@
}
flag {
+ name: "multiuser_widget"
+ namespace: "multiuser"
+ description: "Implement the Multiuser Widget"
+ bug: "365748524"
+}
+
+flag {
name: "enable_biometrics_to_unlock_private_space"
is_exported: true
namespace: "profile_experiences"
@@ -449,7 +456,6 @@
}
}
-
flag {
name: "caching_development_improvements"
namespace: "multiuser"
@@ -457,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/content/res/flags.aconfig b/core/java/android/content/res/flags.aconfig
index a5f8199..0af2f25 100644
--- a/core/java/android/content/res/flags.aconfig
+++ b/core/java/android/content/res/flags.aconfig
@@ -66,3 +66,11 @@
# This flag is read at boot time.
is_fixed_read_only: true
}
+
+flag {
+ name: "dimension_frro"
+ is_exported: true
+ namespace: "resource_manager"
+ description: "Feature flag for passing a dimension to create an frro"
+ bug: "369672322"
+}
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/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 7185719..6affd12 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -792,7 +792,6 @@
public void setVirtualDisplaySurface(IVirtualDisplayCallback token, Surface surface) {
try {
mDm.setVirtualDisplaySurface(token, surface);
- setVirtualDisplayState(token, surface != null);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
@@ -815,14 +814,6 @@
}
}
- void setVirtualDisplayState(IVirtualDisplayCallback token, boolean isOn) {
- try {
- mDm.setVirtualDisplayState(token, isOn);
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
- }
-
void setVirtualDisplayRotation(IVirtualDisplayCallback token, @Surface.Rotation int rotation) {
try {
mDm.setVirtualDisplayRotation(token, rotation);
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index e598097..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
@@ -455,6 +456,11 @@
public abstract void onPresentation(int displayId, boolean isShown);
/**
+ * Called upon the usage of stylus.
+ */
+ public abstract void stylusGestureStarted(long eventTime);
+
+ /**
* Describes the requested power state of the display.
*
* This object is intended to describe the general characteristics of the
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index aa1539f6..b612bca 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -115,9 +115,6 @@
void releaseVirtualDisplay(in IVirtualDisplayCallback token);
// No permissions required but must be same Uid as the creator.
- void setVirtualDisplayState(in IVirtualDisplayCallback token, boolean isOn);
-
- // No permissions required but must be same Uid as the creator.
void setVirtualDisplayRotation(in IVirtualDisplayCallback token, int rotation);
// Get a stable metric for the device's display size. No permissions required.
diff --git a/core/java/android/hardware/display/VirtualDisplay.java b/core/java/android/hardware/display/VirtualDisplay.java
index 6cc938f..32b6405 100644
--- a/core/java/android/hardware/display/VirtualDisplay.java
+++ b/core/java/android/hardware/display/VirtualDisplay.java
@@ -112,18 +112,6 @@
}
/**
- * Sets the on/off state for a virtual display.
- *
- * @param isOn Whether the display should be on or off.
- * @hide
- */
- public void setDisplayState(boolean isOn) {
- if (mToken != null) {
- mGlobal.setVirtualDisplayState(mToken, isOn);
- }
- }
-
- /**
* Sets the rotation of the virtual display.
*
* @param rotation the new rotation of the display. May be one of {@link Surface#ROTATION_0},
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/BatteryStats.java b/core/java/android/os/BatteryStats.java
index b9b5295..c41e626 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -3088,8 +3088,9 @@
public static final String[] HISTORY_EVENT_CHECKIN_NAMES = new String[] {
"Enl", "Epr", "Efg", "Etp", "Esy", "Ewl", "Ejb", "Eur", "Euf", "Ecn",
- "Eac", "Epi", "Epu", "Eal", "Est", "Eai", "Eaa", "Etw",
- "Esw", "Ewa", "Elw", "Eec", "Esc", "Eds"
+ "Eac", "Epi", "Epu", "Eal", "Est", "Eai", "Eaa",
+ "Etw", "Esw", "Ewa", "Elw", "Esc",
+ "Eds"
};
@FunctionalInterface
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/Build.java b/core/java/android/os/Build.java
index a8267d1..479aa98 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -22,6 +22,7 @@
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SuppressAutoDoc;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.app.ActivityThread;
@@ -401,33 +402,42 @@
* device. This value never changes while a device is booted, but it may
* increase when the hardware manufacturer provides an OTA update.
* <p>
- * Together with {@link SDK_MINOR_INT}, this constant defines the
- * <pre>major.minor</pre> version of Android. <pre>SDK_INT</pre> is
- * increased and <pre>SDK_MINOR_INT</pre> is set to 0 on new Android
- * dessert releases. Between these, Android may also release so called
- * minor releases where <pre>SDK_INT</pre> remains unchanged and
- * <pre>SDK_MINOR_INT</pre> is increased. Minor releases can add new
- * APIs, and have stricter guarantees around backwards compatibility
- * (e.g. no changes gated by <pre>targetSdkVersion</pre>) compared to
- * major releases.
+ * This constant records the major version of Android. Use {@link
+ * SDK_INT_FULL} if you need to consider the minor version of Android
+ * as well.
* <p>
* Possible values are defined in {@link Build.VERSION_CODES}.
+ * @see #SDK_INT_FULL
*/
public static final int SDK_INT = SystemProperties.getInt(
"ro.build.version.sdk", 0);
/**
- * The minor SDK version of the software currently running on this hardware
- * device. This value never changes while a device is booted, but it may
- * increase when the hardware manufacturer provides an OTA update.
+ * The major and minor SDK version of the software currently running on
+ * this hardware device. This value never changes while a device is
+ * booted, but it may increase when the hardware manufacturer provides
+ * an OTA update.
* <p>
- * Together with {@link SDK_INT}, this constant defines the
- * <pre>major.minor</pre> version of Android. See {@link SDK_INT} for
- * more information.
+ * <code>SDK_INT</code> is increased on new Android dessert releases,
+ * also called major releases. Between these, Android may also release
+ * minor releases where <code>SDK_INT</code> remains unchanged. Minor
+ * releases can add new APIs, and have stricter guarantees around
+ * backwards compatibility (e.g. no changes gated by
+ * <code>targetSdkVersion</code>) compared to major releases.
+ * <p>
+ * <code>SDK_INT_FULL</code> is increased on every release.
+ * <p>
+ * Possible values are defined in {@link
+ * android.os.Build.VERSION_CODES_FULL}.
*/
@FlaggedApi(Flags.FLAG_MAJOR_MINOR_VERSIONING_SCHEME)
- public static final int SDK_MINOR_INT = SystemProperties.getInt(
- "ro.build.version.sdk_minor", 0);
+ public static final int SDK_INT_FULL;
+
+ static {
+ SDK_INT_FULL = VERSION_CODES_FULL.SDK_INT_MULTIPLIER
+ * SystemProperties.getInt("ro.build.version.sdk", 0)
+ + SystemProperties.getInt("ro.build.version.sdk_minor", 0);
+ }
/**
* The SDK version of the software that <em>initially</em> shipped on
@@ -1264,6 +1274,25 @@
}
/**
+ * Enumeration of the currently known SDK major and minor version codes.
+ * The numbers increase for every release, and are guaranteed to be ordered
+ * by the release date of each release. The actual values should be
+ * considered an implementation detail, and the current encoding scheme may
+ * change in the future.
+ *
+ * @see android.os.Build.VERSION#SDK_INT_FULL
+ */
+ @FlaggedApi(Flags.FLAG_MAJOR_MINOR_VERSIONING_SCHEME)
+ @SuppressLint("AcronymName")
+ public static class VERSION_CODES_FULL {
+ private VERSION_CODES_FULL() {}
+
+ // Use the last 5 digits for the minor version. This allows the
+ // minor version to be set to CUR_DEVELOPMENT.
+ private static final int SDK_INT_MULTIPLIER = 100000;
+ }
+
+ /**
* The vendor API for 2024 Q2
*
* <p>For Android 14-QPR3 and later, the vendor API level is completely decoupled from the SDK
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/RemoteCallbackList.java b/core/java/android/os/RemoteCallbackList.java
index 2cb86f7..769cbdd 100644
--- a/core/java/android/os/RemoteCallbackList.java
+++ b/core/java/android/os/RemoteCallbackList.java
@@ -92,9 +92,9 @@
/**
* Add a new callback to the list. This callback will remain in the list
* until a corresponding call to {@link #unregister} or its hosting process
- * goes away. If the callback was already registered (determined by
+ * goes away. If the callback was already registered (determined by
* checking to see if the {@link IInterface#asBinder callback.asBinder()}
- * object is already in the list), then it will be left as-is.
+ * object is already in the list), then it will be replaced with the new callback.
* Registrations are not counted; a single call to {@link #unregister}
* will remove a callback after any number calls to register it.
*
@@ -106,7 +106,7 @@
*
* @param cookie Optional additional data to be associated with this
* callback.
- *
+ *
* @return Returns true if the callback was successfully added to the list.
* Returns false if it was not added, either because {@link #kill} had
* previously been called or the callback's process has gone away.
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/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index 27b1dfb..d557046 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -3027,6 +3027,46 @@
*/
@FlaggedApi(Flags.FLAG_NEW_DEFAULT_ACCOUNT_API_ENABLED)
public static final class DefaultAccount {
+ /**
+ * Key in the outgoing Bundle for the default account list.
+ *
+ * @hide
+ */
+ public static final String KEY_ELIGIBLE_DEFAULT_ACCOUNTS =
+ "key_eligible_default_accounts";
+ /**
+ * The method to invoke in order to query eligiblie default accounts.
+ *
+ * @hide
+ */
+ public static final String QUERY_ELIGIBLE_DEFAULT_ACCOUNTS_METHOD =
+ "queryEligibleDefaultAccounts";
+ /**
+ * Key in the Bundle for the default account state.
+ *
+ * @hide
+ */
+ public static final String KEY_DEFAULT_ACCOUNT_STATE =
+ "key_default_account_state";
+ /**
+ * The method to invoke in order to set the default account.
+ *
+ * @hide
+ */
+ public static final String SET_DEFAULT_ACCOUNT_FOR_NEW_CONTACTS_METHOD =
+ "setDefaultAccountForNewContacts";
+ /**
+ * The method to invoke in order to query the default account.
+ *
+ * @hide
+ */
+ public static final String QUERY_DEFAULT_ACCOUNT_FOR_NEW_CONTACTS_METHOD =
+ "queryDefaultAccountForNewContacts";
+
+ private DefaultAccount() {
+
+ }
+
/**
* Represents the state of the default account, and the actual {@link Account} if it's
@@ -3228,6 +3268,94 @@
public @interface DefaultAccountState {
}
}
+
+ /**
+ * Get the account that is set as the default account for new contacts, which should be
+ * initially selected when creating a new contact on contact management apps.
+ *
+ * @param resolver the ContentResolver to query.
+ *
+ * @return the default account state for new contacts.
+ * @throws RuntimeException if failed to look up the default account.
+ * @throws IllegalStateException if the default account is in an invalid state.
+ */
+ @FlaggedApi(Flags.FLAG_NEW_DEFAULT_ACCOUNT_API_ENABLED)
+ public static @NonNull DefaultAccountAndState getDefaultAccountForNewContacts(
+ @NonNull ContentResolver resolver) {
+ Bundle response = nullSafeCall(resolver, ContactsContract.AUTHORITY_URI,
+ QUERY_DEFAULT_ACCOUNT_FOR_NEW_CONTACTS_METHOD, null, null);
+
+ int defaultContactsAccountState = response.getInt(KEY_DEFAULT_ACCOUNT_STATE, -1);
+ if (defaultContactsAccountState
+ == DefaultAccountAndState.DEFAULT_ACCOUNT_STATE_CLOUD) {
+ String accountName = response.getString(Settings.ACCOUNT_NAME);
+ String accountType = response.getString(Settings.ACCOUNT_TYPE);
+ if (TextUtils.isEmpty(accountName) || TextUtils.isEmpty(accountType)) {
+ throw new IllegalStateException(
+ "account name and type cannot be null or empty");
+ }
+ return new DefaultAccountAndState(
+ DefaultAccountAndState.DEFAULT_ACCOUNT_STATE_CLOUD,
+ new Account(accountName, accountType));
+ } else if (defaultContactsAccountState
+ == DefaultAccountAndState.DEFAULT_ACCOUNT_STATE_LOCAL
+ || defaultContactsAccountState
+ == DefaultAccountAndState.DEFAULT_ACCOUNT_STATE_NOT_SET) {
+ return new DefaultAccountAndState(defaultContactsAccountState, /*cloudAccount=*/
+ null);
+ } else {
+ throw new IllegalStateException("Invalid default account state");
+ }
+ }
+
+ /**
+ * Sets the default account that should be initially selected when creating a new
+ * contact on
+ * contact management apps. Apps can only set one of
+ * The following accounts as the default account:
+ * <ol>
+ * <li> local account
+ * <li> cloud account that are eligible to be set as default account.
+ * </ol>
+ *
+ * @param resolver the ContentResolver to query.
+ * @param defaultAccountAndState the default account and state to be set. To set the
+ * local
+ * account as the
+ * default account, this parameter should be
+ * {@link DefaultAccountAndState#ofLocal()}. To set the a
+ * cloud
+ * account as the default account, this parameter should
+ * be
+ * {@link DefaultAccountAndState#ofCloud(Account)}. To
+ * set
+ * the
+ * default account to a "not set" state, this parameter
+ * should
+ * be {@link DefaultAccountAndState#ofNotSet()}.
+ *
+ * @throws RuntimeException if it fails to set the default account.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.SET_DEFAULT_ACCOUNT_FOR_CONTACTS)
+ @FlaggedApi(Flags.FLAG_NEW_DEFAULT_ACCOUNT_API_ENABLED)
+ @SystemApi
+ public static void setDefaultAccountForNewContacts(@NonNull ContentResolver resolver,
+ @NonNull DefaultAccountAndState defaultAccountAndState) {
+ Bundle extras = new Bundle();
+
+ extras.putInt(KEY_DEFAULT_ACCOUNT_STATE, defaultAccountAndState.getState());
+ if (defaultAccountAndState.getState()
+ == DefaultAccountAndState.DEFAULT_ACCOUNT_STATE_CLOUD) {
+ Account cloudAccount = defaultAccountAndState.getAccount();
+ assert cloudAccount != null;
+ extras.putString(Settings.ACCOUNT_NAME, cloudAccount.name);
+ extras.putString(Settings.ACCOUNT_TYPE, cloudAccount.type);
+ }
+ nullSafeCall(resolver, ContactsContract.AUTHORITY_URI,
+ SET_DEFAULT_ACCOUNT_FOR_NEW_CONTACTS_METHOD, null, extras);
+ }
}
/**
@@ -9055,30 +9183,6 @@
public static final String KEY_DEFAULT_ACCOUNT = "key_default_account";
/**
- * Key in the Bundle for the default account state.
- *
- * @hide
- */
- public static final String KEY_DEFAULT_ACCOUNT_STATE =
- "key_default_contacts_account_state";
-
- /**
- * The method to invoke in order to set the default account.
- *
- * @hide
- */
- public static final String SET_DEFAULT_ACCOUNT_FOR_NEW_CONTACTS_METHOD =
- "setDefaultAccountForNewContacts";
-
- /**
- * The method to invoke in order to query the default account.
- *
- * @hide
- */
- public static final String QUERY_DEFAULT_ACCOUNT_FOR_NEW_CONTACTS_METHOD =
- "queryDefaultAccountForNewContacts";
-
- /**
* Get the account that is set as the default account for new contacts, which should be
* initially selected when creating a new contact on contact management apps.
* If the setting has not been set by any app, it will return null. Once the setting
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index d82af55..1a15d09 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2092,23 +2092,6 @@
public static final String ACTION_ZEN_MODE_SETTINGS = "android.settings.ZEN_MODE_SETTINGS";
/**
- * Activity Action: Show Zen Mode visual effects configuration settings.
- *
- * @hide
- */
- @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
- public static final String ZEN_MODE_BLOCKED_EFFECTS_SETTINGS =
- "android.settings.ZEN_MODE_BLOCKED_EFFECTS_SETTINGS";
-
- /**
- * Activity Action: Show Zen Mode onboarding activity.
- *
- * @hide
- */
- @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
- public static final String ZEN_MODE_ONBOARDING = "android.settings.ZEN_MODE_ONBOARDING";
-
- /**
* Activity Action: Show Zen Mode (aka Do Not Disturb) priority configuration settings.
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
@@ -2367,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.
@@ -8748,35 +8746,6 @@
/** @hide */ public static final int ZEN_DURATION_FOREVER = 0;
/**
- * If nonzero, will show the zen upgrade notification when the user toggles DND on/off.
- * @hide
- */
- @Readable
- public static final String SHOW_ZEN_UPGRADE_NOTIFICATION = "show_zen_upgrade_notification";
-
- /**
- * If nonzero, will show the zen update settings suggestion.
- * @hide
- */
- @Readable
- public static final String SHOW_ZEN_SETTINGS_SUGGESTION = "show_zen_settings_suggestion";
-
- /**
- * If nonzero, zen has not been updated to reflect new changes.
- * @hide
- */
- @Readable
- public static final String ZEN_SETTINGS_UPDATED = "zen_settings_updated";
-
- /**
- * If nonzero, zen setting suggestion has been viewed by user
- * @hide
- */
- @Readable
- public static final String ZEN_SETTINGS_SUGGESTION_VIEWED =
- "zen_settings_suggestion_viewed";
-
- /**
* Whether the in call notification is enabled to play sound during calls. The value is
* boolean (1 or 0).
* @hide
@@ -17844,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.
@@ -18072,10 +18035,6 @@
MOVED_TO_SECURE = new HashSet<>(8);
MOVED_TO_SECURE.add(Global.INSTALL_NON_MARKET_APPS);
MOVED_TO_SECURE.add(Global.ZEN_DURATION);
- MOVED_TO_SECURE.add(Global.SHOW_ZEN_UPGRADE_NOTIFICATION);
- MOVED_TO_SECURE.add(Global.SHOW_ZEN_SETTINGS_SUGGESTION);
- MOVED_TO_SECURE.add(Global.ZEN_SETTINGS_UPDATED);
- MOVED_TO_SECURE.add(Global.ZEN_SETTINGS_SUGGESTION_VIEWED);
MOVED_TO_SECURE.add(Global.CHARGING_SOUNDS_ENABLED);
MOVED_TO_SECURE.add(Global.CHARGING_VIBRATION_ENABLED);
MOVED_TO_SECURE.add(Global.NOTIFICATION_BUBBLES);
@@ -18910,40 +18869,6 @@
@Readable
public static final String SHOW_MUTE_IN_CRASH_DIALOG = "show_mute_in_crash_dialog";
-
- /**
- * If nonzero, will show the zen upgrade notification when the user toggles DND on/off.
- * @hide
- * @deprecated - Use {@link android.provider.Settings.Secure#SHOW_ZEN_UPGRADE_NOTIFICATION}
- */
- @Deprecated
- public static final String SHOW_ZEN_UPGRADE_NOTIFICATION = "show_zen_upgrade_notification";
-
- /**
- * If nonzero, will show the zen update settings suggestion.
- * @hide
- * @deprecated - Use {@link android.provider.Settings.Secure#SHOW_ZEN_SETTINGS_SUGGESTION}
- */
- @Deprecated
- public static final String SHOW_ZEN_SETTINGS_SUGGESTION = "show_zen_settings_suggestion";
-
- /**
- * If nonzero, zen has not been updated to reflect new changes.
- * @deprecated - Use {@link android.provider.Settings.Secure#ZEN_SETTINGS_UPDATED}
- * @hide
- */
- @Deprecated
- public static final String ZEN_SETTINGS_UPDATED = "zen_settings_updated";
-
- /**
- * If nonzero, zen setting suggestion has been viewed by user
- * @hide
- * @deprecated - Use {@link android.provider.Settings.Secure#ZEN_SETTINGS_SUGGESTION_VIEWED}
- */
- @Deprecated
- public static final String ZEN_SETTINGS_SUGGESTION_VIEWED =
- "zen_settings_suggestion_viewed";
-
/**
* Backup and restore agent timeout parameters.
* These parameters are represented by a comma-delimited key-value list.
@@ -20233,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/tracing/TEST_MAPPING b/core/java/android/tracing/TEST_MAPPING
new file mode 100644
index 0000000..b51d19d
--- /dev/null
+++ b/core/java/android/tracing/TEST_MAPPING
@@ -0,0 +1,10 @@
+{
+ "postsubmit": [
+ {
+ "name": "TracingTests"
+ },
+ {
+ "name": "ProtologPerfTests"
+ }
+ ]
+}
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/Window.java b/core/java/android/view/Window.java
index 0582afe..381006c 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -1345,7 +1345,7 @@
}
/**
- * <p>Sets the desired about of HDR headroom to be used when rendering as a ratio of
+ * <p>Sets the desired amount of HDR headroom to be used when rendering as a ratio of
* targetHdrPeakBrightnessInNits / targetSdrWhitePointInNits. Only applies when
* {@link #setColorMode(int)} is {@link ActivityInfo#COLOR_MODE_HDR}</p>
*
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/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 70ac12f..b22aa22 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -162,6 +162,13 @@
}
flag {
+ name: "enable_a11y_metrics"
+ namespace: "lse_desktop_experience"
+ description: "Whether to enable log collection for a11y actions in desktop windowing mode"
+ bug: "341319597"
+}
+
+flag {
name: "enable_caption_compat_inset_force_consumption"
namespace: "lse_desktop_experience"
description: "Enables force-consumption of caption bar insets for immersive apps in freeform"
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/java/com/android/internal/notification/SystemNotificationChannels.java b/core/java/com/android/internal/notification/SystemNotificationChannels.java
index fef5e83..4aebde5 100644
--- a/core/java/com/android/internal/notification/SystemNotificationChannels.java
+++ b/core/java/com/android/internal/notification/SystemNotificationChannels.java
@@ -26,6 +26,7 @@
import android.os.RemoteException;
import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
import java.util.Arrays;
@@ -37,38 +38,40 @@
* @deprecated Legacy system channel, which is no longer used,
*/
@Deprecated public static String VIRTUAL_KEYBOARD = "VIRTUAL_KEYBOARD";
- public static String PHYSICAL_KEYBOARD = "PHYSICAL_KEYBOARD";
- public static String SECURITY = "SECURITY";
- public static String CAR_MODE = "CAR_MODE";
- public static String ACCOUNT = "ACCOUNT";
- public static String DEVELOPER = "DEVELOPER";
- public static String DEVELOPER_IMPORTANT = "DEVELOPER_IMPORTANT";
- public static String UPDATES = "UPDATES";
- public static String NETWORK_STATUS = "NETWORK_STATUS";
- public static String NETWORK_ALERTS = "NETWORK_ALERTS";
- public static String NETWORK_AVAILABLE = "NETWORK_AVAILABLE";
- public static String VPN = "VPN";
+ public static final String PHYSICAL_KEYBOARD = "PHYSICAL_KEYBOARD";
+ public static final String SECURITY = "SECURITY";
+ public static final String CAR_MODE = "CAR_MODE";
+ public static final String ACCOUNT = "ACCOUNT";
+ public static final String DEVELOPER = "DEVELOPER";
+ public static final String DEVELOPER_IMPORTANT = "DEVELOPER_IMPORTANT";
+ public static final String UPDATES = "UPDATES";
+ public static final String NETWORK_STATUS = "NETWORK_STATUS";
+ public static final String NETWORK_ALERTS = "NETWORK_ALERTS";
+ public static final String NETWORK_AVAILABLE = "NETWORK_AVAILABLE";
+ public static final String VPN = "VPN";
/**
* @deprecated Legacy device admin channel with low importance which is no longer used,
* Use the high importance {@link #DEVICE_ADMIN} channel instead.
*/
- @Deprecated public static String DEVICE_ADMIN_DEPRECATED = "DEVICE_ADMIN";
- public static String DEVICE_ADMIN = "DEVICE_ADMIN_ALERTS";
- public static String ALERTS = "ALERTS";
- public static String RETAIL_MODE = "RETAIL_MODE";
- public static String USB = "USB";
- public static String FOREGROUND_SERVICE = "FOREGROUND_SERVICE";
- public static String HEAVY_WEIGHT_APP = "HEAVY_WEIGHT_APP";
+ @Deprecated public static final String DEVICE_ADMIN_DEPRECATED = "DEVICE_ADMIN";
+ public static final String DEVICE_ADMIN = "DEVICE_ADMIN_ALERTS";
+ public static final String ALERTS = "ALERTS";
+ public static final String RETAIL_MODE = "RETAIL_MODE";
+ public static final String USB = "USB";
+ public static final String FOREGROUND_SERVICE = "FOREGROUND_SERVICE";
+ public static final String HEAVY_WEIGHT_APP = "HEAVY_WEIGHT_APP";
/**
* @deprecated Legacy system changes channel with low importance which is no longer used,
* Use the default importance {@link #SYSTEM_CHANGES} channel instead.
*/
- @Deprecated public static String SYSTEM_CHANGES_DEPRECATED = "SYSTEM_CHANGES";
- public static String SYSTEM_CHANGES = "SYSTEM_CHANGES_ALERTS";
- public static String DO_NOT_DISTURB = "DO_NOT_DISTURB";
- public static String ACCESSIBILITY_MAGNIFICATION = "ACCESSIBILITY_MAGNIFICATION";
- public static String ACCESSIBILITY_SECURITY_POLICY = "ACCESSIBILITY_SECURITY_POLICY";
- public static String ABUSIVE_BACKGROUND_APPS = "ABUSIVE_BACKGROUND_APPS";
+ @Deprecated public static final String SYSTEM_CHANGES_DEPRECATED = "SYSTEM_CHANGES";
+ public static final String SYSTEM_CHANGES = "SYSTEM_CHANGES_ALERTS";
+ public static final String ACCESSIBILITY_MAGNIFICATION = "ACCESSIBILITY_MAGNIFICATION";
+ public static final String ACCESSIBILITY_SECURITY_POLICY = "ACCESSIBILITY_SECURITY_POLICY";
+ public static final String ABUSIVE_BACKGROUND_APPS = "ABUSIVE_BACKGROUND_APPS";
+
+ @VisibleForTesting
+ static final String OBSOLETE_DO_NOT_DISTURB = "DO_NOT_DISTURB";
public static void createAll(Context context) {
final NotificationManager nm = context.getSystemService(NotificationManager.class);
@@ -193,11 +196,6 @@
.build());
channelsList.add(systemChanges);
- NotificationChannel dndChanges = new NotificationChannel(DO_NOT_DISTURB,
- context.getString(R.string.notification_channel_do_not_disturb),
- NotificationManager.IMPORTANCE_LOW);
- channelsList.add(dndChanges);
-
final NotificationChannel newFeaturePrompt = new NotificationChannel(
ACCESSIBILITY_MAGNIFICATION,
context.getString(R.string.notification_channel_accessibility_magnification),
@@ -218,6 +216,9 @@
channelsList.add(abusiveBackgroundAppsChannel);
nm.createNotificationChannels(channelsList);
+
+ // Delete channels created by previous Android versions that are no longer used.
+ nm.deleteNotificationChannel(OBSOLETE_DO_NOT_DISTURB);
}
private static String getDeviceAdminNotificationChannelName(Context context) {
diff --git a/core/java/com/android/internal/protolog/TEST_MAPPING b/core/java/com/android/internal/protolog/TEST_MAPPING
index 37d57ee..b51d19d 100644
--- a/core/java/com/android/internal/protolog/TEST_MAPPING
+++ b/core/java/com/android/internal/protolog/TEST_MAPPING
@@ -1,6 +1,9 @@
{
"postsubmit": [
{
+ "name": "TracingTests"
+ },
+ {
"name": "ProtologPerfTests"
}
]
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/jni_wrappers.h b/core/jni/jni_wrappers.h
index 21b5b13..e3e17ee 100644
--- a/core/jni/jni_wrappers.h
+++ b/core/jni/jni_wrappers.h
@@ -79,13 +79,14 @@
jniMethodFormat = value;
}
-// Potentially translates the given JNINativeMethods if setJniMethodFormat has been set.
-// Has no effect otherwise
-inline const JNINativeMethod* maybeRenameJniMethods(const JNINativeMethod* gMethods,
- int numMethods) {
+// Register the native methods, potenially applying the jniMethodFormat if it has been set.
+static inline int jniRegisterMaybeRenamedNativeMethods(JNIEnv* env, const char* className,
+ const JNINativeMethod* gMethods,
+ int numMethods) {
if (jniMethodFormat.empty()) {
- return gMethods;
+ return jniRegisterNativeMethods(env, className, gMethods, numMethods);
}
+
// Make a copy of gMethods with reformatted method names.
JNINativeMethod* modifiedMethods = new JNINativeMethod[numMethods];
LOG_ALWAYS_FATAL_IF(!modifiedMethods, "Failed to allocate a copy of the JNI methods");
@@ -103,13 +104,17 @@
std::strcpy(modifiedNameChars, modifiedName.c_str());
modifiedMethods[i].name = modifiedNameChars;
}
- return modifiedMethods;
+ int res = jniRegisterNativeMethods(env, className, modifiedMethods, numMethods);
+ for (int i = 0; i < numMethods; i++) {
+ delete[] modifiedMethods[i].name;
+ }
+ delete[] modifiedMethods;
+ return res;
}
static inline int RegisterMethodsOrDie(JNIEnv* env, const char* className,
const JNINativeMethod* gMethods, int numMethods) {
- const JNINativeMethod* modifiedMethods = maybeRenameJniMethods(gMethods, numMethods);
- int res = jniRegisterNativeMethods(env, className, modifiedMethods, numMethods);
+ int res = jniRegisterMaybeRenamedNativeMethods(env, className, gMethods, numMethods);
LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods.");
return res;
}
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/strings.xml b/core/res/res/values/strings.xml
index d634210..7aca535 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5818,16 +5818,6 @@
<!-- Title for the notification channel notifying user of settings system changes. [CHAR LIMIT=NONE] -->
<string name="notification_channel_system_changes">System changes</string>
- <!-- Title for the notification channel notifying user of do not disturb system changes (i.e. Do Not Disturb has changed). [CHAR LIMIT=NONE] -->
- <string name="notification_channel_do_not_disturb">Do Not Disturb</string>
- <!-- Title of notification indicating do not disturb visual interruption settings have changed when upgrading to P -->
- <string name="zen_upgrade_notification_visd_title">New: Do Not Disturb is hiding notifications</string>
- <!-- Content of notification indicating users can tap on the notification to go to dnd behavior settings -->
- <string name="zen_upgrade_notification_visd_content">Tap to learn more and change.</string>
- <!-- Title of notification indicating do not disturb settings have changed when upgrading to P -->
- <string name="zen_upgrade_notification_title">Do Not Disturb has changed</string>
- <!-- Content of notification indicating users can tap on the notification to go to dnd behavior settings -->
- <string name="zen_upgrade_notification_content">Tap to check what\'s blocked.</string>
<!-- Notification permission informational notification text -->
<!-- Title for notification inviting users to review their notification settings [CHAR LIMIT=NONE] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 0ccef91..c50c336 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3937,7 +3937,6 @@
<java-symbol type="string" name="notification_channel_usb" />
<java-symbol type="string" name="notification_channel_heavy_weight_app" />
<java-symbol type="string" name="notification_channel_system_changes" />
- <java-symbol type="string" name="notification_channel_do_not_disturb" />
<java-symbol type="string" name="notification_channel_accessibility_magnification" />
<java-symbol type="string" name="notification_channel_accessibility_security_policy" />
<java-symbol type="string" name="notification_channel_display" />
@@ -3989,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" />
@@ -4164,11 +4164,6 @@
<!-- For Wear devices -->
<java-symbol type="array" name="config_wearActivityModeRadios" />
- <java-symbol type="string" name="zen_upgrade_notification_title" />
- <java-symbol type="string" name="zen_upgrade_notification_content" />
- <java-symbol type="string" name="zen_upgrade_notification_visd_title" />
- <java-symbol type="string" name="zen_upgrade_notification_visd_content" />
-
<java-symbol type="string" name="review_notification_settings_title" />
<java-symbol type="string" name="review_notification_settings_text" />
<java-symbol type="string" name="review_notification_settings_remind_me_action" />
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index e9b137c..48e2620 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -2301,13 +2301,13 @@
@Test
@EnableFlags(Flags.FLAG_API_RICH_ONGOING)
- public void progressStyle_onProgressStepChange_visiblyDifferent() {
+ public void progressStyle_onProgressPointChange_visiblyDifferent() {
final Notification.Builder nProgress1 = new Notification.Builder(mContext, "test")
.setStyle(new Notification.ProgressStyle()
- .addProgressStep(new Notification.ProgressStyle.Step(10)));
+ .addProgressPoint(new Notification.ProgressStyle.Point(10)));
final Notification.Builder nProgress2 = new Notification.Builder(mContext, "test")
.setStyle(new Notification.ProgressStyle()
- .addProgressStep(new Notification.ProgressStyle.Step(12)));
+ .addProgressPoint(new Notification.ProgressStyle.Point(12)));
assertThat(Notification.areStyledNotificationsVisiblyDifferent(nProgress1, nProgress2))
.isTrue();
@@ -2315,13 +2315,13 @@
@Test
@EnableFlags(Flags.FLAG_API_RICH_ONGOING)
- public void indeterminateProgressStyle_onProgressStepChange_visiblyNotDifferent() {
+ public void indeterminateProgressStyle_onProgressPointChange_visiblyNotDifferent() {
final Notification.Builder nProgress1 = new Notification.Builder(mContext, "test")
.setStyle(new Notification.ProgressStyle().setProgressIndeterminate(true)
- .addProgressStep(new Notification.ProgressStyle.Step(10)));
+ .addProgressPoint(new Notification.ProgressStyle.Point(10)));
final Notification.Builder nProgress2 = new Notification.Builder(mContext, "test")
.setStyle(new Notification.ProgressStyle().setProgressIndeterminate(true)
- .addProgressStep(new Notification.ProgressStyle.Step(12)));
+ .addProgressPoint(new Notification.ProgressStyle.Point(12)));
assertThat(Notification.areStyledNotificationsVisiblyDifferent(nProgress1, nProgress2))
.isFalse();
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/core/tests/coretests/src/com/android/internal/notification/SystemNotificationChannelsTest.java b/core/tests/coretests/src/com/android/internal/notification/SystemNotificationChannelsTest.java
new file mode 100644
index 0000000..0bf406c
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/notification/SystemNotificationChannelsTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.notification;
+
+import static com.android.internal.notification.SystemNotificationChannels.ABUSIVE_BACKGROUND_APPS;
+import static com.android.internal.notification.SystemNotificationChannels.ACCESSIBILITY_MAGNIFICATION;
+import static com.android.internal.notification.SystemNotificationChannels.ACCESSIBILITY_SECURITY_POLICY;
+import static com.android.internal.notification.SystemNotificationChannels.ACCOUNT;
+import static com.android.internal.notification.SystemNotificationChannels.ALERTS;
+import static com.android.internal.notification.SystemNotificationChannels.CAR_MODE;
+import static com.android.internal.notification.SystemNotificationChannels.DEVELOPER;
+import static com.android.internal.notification.SystemNotificationChannels.DEVELOPER_IMPORTANT;
+import static com.android.internal.notification.SystemNotificationChannels.DEVICE_ADMIN;
+import static com.android.internal.notification.SystemNotificationChannels.FOREGROUND_SERVICE;
+import static com.android.internal.notification.SystemNotificationChannels.HEAVY_WEIGHT_APP;
+import static com.android.internal.notification.SystemNotificationChannels.NETWORK_ALERTS;
+import static com.android.internal.notification.SystemNotificationChannels.NETWORK_AVAILABLE;
+import static com.android.internal.notification.SystemNotificationChannels.NETWORK_STATUS;
+import static com.android.internal.notification.SystemNotificationChannels.OBSOLETE_DO_NOT_DISTURB;
+import static com.android.internal.notification.SystemNotificationChannels.PHYSICAL_KEYBOARD;
+import static com.android.internal.notification.SystemNotificationChannels.RETAIL_MODE;
+import static com.android.internal.notification.SystemNotificationChannels.SECURITY;
+import static com.android.internal.notification.SystemNotificationChannels.SYSTEM_CHANGES;
+import static com.android.internal.notification.SystemNotificationChannels.UPDATES;
+import static com.android.internal.notification.SystemNotificationChannels.USB;
+import static com.android.internal.notification.SystemNotificationChannels.VPN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.verify;
+
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.testing.TestableContext;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class SystemNotificationChannelsTest {
+
+ @Rule public TestableContext mContext = new TestableContext(
+ ApplicationProvider.getApplicationContext());
+
+ @Mock private NotificationManager mNm;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext.addMockSystemService(NotificationManager.class, mNm);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void createAll_createsExpectedChannels() {
+ ArgumentCaptor<List<NotificationChannel>> createdChannelsCaptor =
+ ArgumentCaptor.forClass(List.class);
+
+ SystemNotificationChannels.createAll(mContext);
+
+ verify(mNm).createNotificationChannels(createdChannelsCaptor.capture());
+ List<NotificationChannel> createdChannels = createdChannelsCaptor.getValue();
+ assertThat(createdChannels.stream().map(NotificationChannel::getId).toList())
+ .containsExactly(PHYSICAL_KEYBOARD, SECURITY, CAR_MODE, ACCOUNT, DEVELOPER,
+ DEVELOPER_IMPORTANT, UPDATES, NETWORK_STATUS, NETWORK_ALERTS,
+ NETWORK_AVAILABLE, VPN, DEVICE_ADMIN, ALERTS, RETAIL_MODE, USB,
+ FOREGROUND_SERVICE, HEAVY_WEIGHT_APP, SYSTEM_CHANGES,
+ ACCESSIBILITY_MAGNIFICATION, ACCESSIBILITY_SECURITY_POLICY,
+ ABUSIVE_BACKGROUND_APPS);
+ }
+
+ @Test
+ public void createAll_deletesObsoleteChannels() {
+ ArgumentCaptor<String> deletedChannelCaptor = ArgumentCaptor.forClass(String.class);
+
+ SystemNotificationChannels.createAll(mContext);
+
+ verify(mNm, atLeastOnce()).deleteNotificationChannel(deletedChannelCaptor.capture());
+ List<String> deletedChannels = deletedChannelCaptor.getAllValues();
+ assertThat(deletedChannels).containsExactly(OBSOLETE_DO_NOT_DISTURB);
+ }
+}
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/Android.bp b/libs/WindowManager/Shell/Android.bp
index a796ecc..e493ed1 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -197,6 +197,7 @@
android_library {
name: "WindowManager-Shell",
srcs: [
+ "src/com/android/wm/shell/EventLogTags.logtags",
":wm_shell_protolog_src",
// TODO(b/168581922) protologtool do not support kotlin(*.kt)
":wm_shell-sources-kt",
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/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/EventLogTags.logtags b/libs/WindowManager/Shell/src/com/android/wm/shell/EventLogTags.logtags
new file mode 100644
index 0000000..db960d1
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/EventLogTags.logtags
@@ -0,0 +1,11 @@
+# See system/logging/logcat/event.logtags for a description of the format of this file.
+
+option java_package com.android.wm.shell
+
+# Do not change these names without updating the checkin_events setting in
+# google3/googledata/wireless/android/provisioning/gservices.config !!
+#
+
+38500 wm_shell_enter_desktop_mode (EnterReason|1|5),(SessionId|1|5)
+38501 wm_shell_exit_desktop_mode (ExitReason|1|5),(SessionId|1|5)
+38502 wm_shell_desktop_mode_task_update (TaskEvent|1|5),(InstanceId|1|5),(uid|1|5),(TaskHeight|1),(TaskWidth|1),(TaskX|1),(TaskY|1),(SessionId|1|5),(MinimiseReason|1|5),(UnminimiseReason|1|5),(VisibleTaskCount|1)
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 af4a0c5..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);
}
@@ -2001,6 +2010,10 @@
// in bubble bar mode, let the request to show the expanded view come from launcher.
// only collapse here if we're collapsing.
if (mLayerView != null && !isExpanded) {
+ if (mBubblePositioner.isImeVisible()) {
+ // If we're collapsing, hide the IME
+ hideCurrentInputMethod();
+ }
mLayerView.collapse();
}
}
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/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
index ec235a5..2a90017 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
@@ -353,6 +353,11 @@
if (isDragging != mIsDragging) {
mIsDragging = isDragging;
updateSamplingState();
+
+ if (isDragging && mPositioner.isImeVisible()) {
+ // Hide the IME when dragging begins
+ mManager.hideCurrentInputMethod();
+ }
}
}
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 8ce3884..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,
@@ -851,10 +884,11 @@
static DesktopWindowingEducationTooltipController
provideDesktopWindowingEducationTooltipController(
Context context,
- AdditionalSystemViewContainer.Factory additionalSystemViewContainerFactory
+ AdditionalSystemViewContainer.Factory additionalSystemViewContainerFactory,
+ DisplayController displayController
) {
return new DesktopWindowingEducationTooltipController(context,
- additionalSystemViewContainerFactory);
+ additionalSystemViewContainerFactory, displayController);
}
@OptIn(markerClass = ExperimentalCoroutinesApi.class)
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 b27cad8..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
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/DesktopModeEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
index 02cbe01..5a277316f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
@@ -19,6 +19,8 @@
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.protolog.ProtoLog
import com.android.internal.util.FrameworkStatsLog
+import com.android.window.flags.Flags
+import com.android.wm.shell.EventLogTags
import com.android.wm.shell.protolog.ShellProtoLogGroup
/** Event logger for logging desktop mode session events */
@@ -41,6 +43,7 @@
/* exitReason */ 0,
/* session_id */ sessionId
)
+ EventLogTags.writeWmShellEnterDesktopMode(enterReason.reason, sessionId)
}
/**
@@ -61,6 +64,7 @@
/* exitReason */ exitReason.reason,
/* session_id */ sessionId
)
+ EventLogTags.writeWmShellExitDesktopMode(exitReason.reason, sessionId)
}
/**
@@ -76,7 +80,8 @@
)
logTaskUpdate(
FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_ADDED,
- sessionId, taskUpdate)
+ sessionId, taskUpdate
+ )
}
/**
@@ -92,7 +97,8 @@
)
logTaskUpdate(
FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_REMOVED,
- sessionId, taskUpdate)
+ sessionId, taskUpdate
+ )
}
/**
@@ -108,7 +114,46 @@
)
logTaskUpdate(
FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED,
- sessionId, taskUpdate)
+ sessionId, taskUpdate
+ )
+ }
+
+ /**
+ * Logs that a task resize event is starting with [taskSizeUpdate] within a
+ * Desktop mode [sessionId].
+ */
+ fun logTaskResizingStarted(sessionId: Int, taskSizeUpdate: TaskSizeUpdate) {
+ if (!Flags.enableResizingMetrics()) return
+
+ ProtoLog.v(
+ ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+ "DesktopModeLogger: Logging task resize is starting, session: %s taskId: %s",
+ sessionId,
+ taskSizeUpdate.instanceId
+ )
+ logTaskSizeUpdated(
+ FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZING_STAGE__START_RESIZING_STAGE,
+ sessionId, taskSizeUpdate
+ )
+ }
+
+ /**
+ * Logs that a task resize event is ending with [taskSizeUpdate] within a
+ * Desktop mode [sessionId].
+ */
+ fun logTaskResizingEnded(sessionId: Int, taskSizeUpdate: TaskSizeUpdate) {
+ if (!Flags.enableResizingMetrics()) return
+
+ ProtoLog.v(
+ ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+ "DesktopModeLogger: Logging task resize is ending, session: %s taskId: %s",
+ sessionId,
+ taskSizeUpdate.instanceId
+ )
+ logTaskSizeUpdated(
+ FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZING_STAGE__END_RESIZING_STAGE,
+ sessionId, taskSizeUpdate
+ )
}
private fun logTaskUpdate(taskEvent: Int, sessionId: Int, taskUpdate: TaskUpdate) {
@@ -135,6 +180,56 @@
/* visible_task_count */
taskUpdate.visibleTaskCount
)
+ EventLogTags.writeWmShellDesktopModeTaskUpdate(
+ /* task_event */
+ taskEvent,
+ /* instance_id */
+ taskUpdate.instanceId,
+ /* uid */
+ taskUpdate.uid,
+ /* task_height */
+ taskUpdate.taskHeight,
+ /* task_width */
+ taskUpdate.taskWidth,
+ /* task_x */
+ taskUpdate.taskX,
+ /* task_y */
+ taskUpdate.taskY,
+ /* session_id */
+ sessionId,
+ taskUpdate.minimizeReason?.reason ?: UNSET_MINIMIZE_REASON,
+ taskUpdate.unminimizeReason?.reason ?: UNSET_UNMINIMIZE_REASON,
+ /* visible_task_count */
+ taskUpdate.visibleTaskCount
+ )
+ }
+
+ private fun logTaskSizeUpdated(
+ resizingStage: Int,
+ sessionId: Int,
+ taskSizeUpdate: TaskSizeUpdate
+ ) {
+ FrameworkStatsLog.write(
+ DESKTOP_MODE_TASK_SIZE_UPDATED_ATOM_ID,
+ /* resize_trigger */
+ taskSizeUpdate.resizeTrigger?.trigger ?: ResizeTrigger.UNKNOWN_RESIZE_TRIGGER.trigger,
+ /* resizing_stage */
+ resizingStage,
+ /* input_method */
+ taskSizeUpdate.inputMethod?.method ?: InputMethod.UNKNOWN_INPUT_METHOD.method,
+ /* desktop_mode_session_id */
+ sessionId,
+ /* instance_id */
+ taskSizeUpdate.instanceId,
+ /* uid */
+ taskSizeUpdate.uid,
+ /* task_height */
+ taskSizeUpdate.taskHeight,
+ /* task_width */
+ taskSizeUpdate.taskWidth,
+ /* display_area */
+ taskSizeUpdate.displayArea
+ )
}
companion object {
@@ -163,13 +258,35 @@
val visibleTaskCount: Int,
)
+ /**
+ * Describes a task size update (resizing, snapping or maximizing to
+ * stable bounds).
+ *
+ * @property resizeTrigger the trigger for task resize
+ * @property inputMethod the input method for resizing this task
+ * @property instanceId instance id of the task
+ * @property uid uid of the app associated with the task
+ * @property taskHeight height of the task in dp
+ * @property taskWidth width of the task in dp
+ * @property displayArea the display size of the screen in dp
+ */
+ data class TaskSizeUpdate(
+ val resizeTrigger: ResizeTrigger? = null,
+ val inputMethod: InputMethod? = null,
+ val instanceId: Int,
+ val uid: Int,
+ val taskHeight: Int,
+ val taskWidth: Int,
+ val displayArea: Int,
+ )
+
// Default value used when the task was not minimized.
@VisibleForTesting
const val UNSET_MINIMIZE_REASON =
FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__MINIMIZE_REASON__UNSET_MINIMIZE
/** The reason a task was minimized. */
- enum class MinimizeReason (val reason: Int) {
+ enum class MinimizeReason(val reason: Int) {
TASK_LIMIT(
FrameworkStatsLog
.DESKTOP_MODE_SESSION_TASK_UPDATE__MINIMIZE_REASON__MINIMIZE_TASK_LIMIT
@@ -186,7 +303,7 @@
FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__UNMINIMIZE_REASON__UNSET_UNMINIMIZE
/** The reason a task was unminimized. */
- enum class UnminimizeReason (val reason: Int) {
+ enum class UnminimizeReason(val reason: Int) {
UNKNOWN(
FrameworkStatsLog
.DESKTOP_MODE_SESSION_TASK_UPDATE__UNMINIMIZE_REASON__UNMINIMIZE_UNKNOWN
@@ -250,8 +367,88 @@
SCREEN_OFF(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EXIT_REASON__SCREEN_OFF)
}
+ /**
+ * Enum ResizeTrigger mapped to the ResizeTrigger definition in
+ * stats/atoms/desktopmode/desktopmode_extensions_atoms.proto
+ */
+ enum class ResizeTrigger(val trigger: Int) {
+ UNKNOWN_RESIZE_TRIGGER(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__UNKNOWN_RESIZE_TRIGGER
+ ),
+ CORNER(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__CORNER_RESIZE_TRIGGER
+ ),
+ EDGE(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__EDGE_RESIZE_TRIGGER
+ ),
+ TILING_DIVIDER(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__TILING_DIVIDER_RESIZE_TRIGGER
+ ),
+ MAXIMIZE_BUTTON(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__MAXIMIZE_BUTTON_RESIZE_TRIGGER
+ ),
+ DOUBLE_TAP_APP_HEADER(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__DOUBLE_TAP_APP_HEADER_RESIZE_TRIGGER
+ ),
+ DRAG_LEFT(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__DRAG_LEFT_RESIZE_TRIGGER
+ ),
+ DRAG_RIGHT(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__DRAG_RIGHT_RESIZE_TRIGGER
+ ),
+ SNAP_LEFT_MENU(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__SNAP_LEFT_MENU_RESIZE_TRIGGER
+ ),
+ SNAP_RIGHT_MENU(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__SNAP_RIGHT_MENU_RESIZE_TRIGGER
+ ),
+ }
+
+ /**
+ * Enum InputMethod mapped to the InputMethod definition in
+ * stats/atoms/desktopmode/desktopmode_extensions_atoms.proto
+ */
+ enum class InputMethod(val method: Int) {
+ UNKNOWN_INPUT_METHOD(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__UNKNOWN_INPUT_METHOD
+ ),
+ TOUCH(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__TOUCH_INPUT_METHOD
+ ),
+ STYLUS(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__STYLUS_INPUT_METHOD
+ ),
+ MOUSE(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__MOUSE_INPUT_METHOD
+ ),
+ TOUCHPAD(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__TOUCHPAD_INPUT_METHOD
+ ),
+ KEYBOARD(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__KEYBOARD_INPUT_METHOD
+ ),
+ }
+
private const val DESKTOP_MODE_ATOM_ID = FrameworkStatsLog.DESKTOP_MODE_UI_CHANGED
private const val DESKTOP_MODE_TASK_UPDATE_ATOM_ID =
FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE
+ private const val DESKTOP_MODE_TASK_SIZE_UPDATED_ATOM_ID =
+ FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED
}
}
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 b8bb73b..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) {
@@ -406,7 +409,7 @@
interactionJankMonitor.begin(taskSurface, context, handler,
CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD)
dragToDesktopTransitionHandler.startDragToDesktopTransition(
- taskInfo.taskId,
+ taskInfo,
dragToDesktopValueAnimator
)
}
@@ -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 7e0741f..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
@@ -24,6 +24,7 @@
import android.view.WindowManager.TRANSIT_TO_BACK
import android.window.TransitionInfo
import android.window.WindowContainerTransaction
+import android.window.flags.DesktopModeFlags
import androidx.annotation.VisibleForTesting
import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_MINIMIZE_WINDOW
import com.android.internal.jank.InteractionJankMonitor
@@ -43,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,
@@ -159,8 +160,10 @@
}
@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
val wct = WindowContainerTransaction()
removeLeftoverMinimizedTasks(displayId, wct)
shellTaskOrganizer.applyTransaction(wct)
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 4796c4d..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
@@ -29,18 +29,19 @@
import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+import com.android.wm.shell.shared.TransitionUtil
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
/**
* 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
@@ -67,9 +68,29 @@
) {
// TODO: b/332682201 Update repository state
updateWallpaperToken(info)
-
if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) {
handleBackNavigation(info)
+ removeTaskIfNeeded(info)
+ }
+ }
+
+ private fun removeTaskIfNeeded(info: TransitionInfo) {
+ // Since we are no longer removing all the tasks [onTaskVanished], we need to remove them by
+ // checking the transitions.
+ if (!TransitionUtil.isOpeningType(info.type)) return
+ // Remove a task from the repository if the app is launched outside of desktop.
+ for (change in info.changes) {
+ val taskInfo = change.taskInfo
+ if (taskInfo == null || taskInfo.taskId == -1) continue
+
+ if (desktopRepository.isActiveTask(taskInfo.taskId)
+ && taskInfo.windowingMode != WINDOWING_MODE_FREEFORM
+ ) {
+ desktopRepository.removeFreeformTask(
+ taskInfo.displayId,
+ taskInfo.taskId
+ )
+ }
}
}
@@ -83,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)
}
}
}
@@ -114,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.
@@ -124,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/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
index 2bc01b2..8e264b2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
@@ -109,8 +109,8 @@
* after one of the "end" or "cancel" transitions is merged into this transition.
*/
fun startDragToDesktopTransition(
- taskId: Int,
- dragToDesktopAnimator: MoveToDesktopAnimator,
+ taskInfo: RunningTaskInfo,
+ dragToDesktopAnimator: MoveToDesktopAnimator
) {
if (inProgress) {
ProtoLog.v(
@@ -137,23 +137,26 @@
)
val wct = WindowContainerTransaction()
wct.sendPendingIntent(pendingIntent, launchHomeIntent, Bundle())
+ // The home launch done above will result in an attempt to move the task to pip if
+ // applicable, resulting in a broken state. Prevent that here.
+ wct.setDoNotPip(taskInfo.token)
val startTransitionToken =
transitions.startTransition(TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP, wct, this)
transitionState =
- if (isSplitTask(taskId)) {
+ if (isSplitTask(taskInfo.taskId)) {
val otherTask =
- getOtherSplitTask(taskId)
+ getOtherSplitTask(taskInfo.taskId)
?: throw IllegalStateException("Expected split task to have a counterpart.")
TransitionState.FromSplit(
- draggedTaskId = taskId,
+ draggedTaskId = taskInfo.taskId,
dragAnimator = dragToDesktopAnimator,
startTransitionToken = startTransitionToken,
otherSplitTask = otherTask
)
} else {
TransitionState.FromFullscreen(
- draggedTaskId = taskId,
+ draggedTaskId = taskInfo.taskId,
dragAnimator = dragToDesktopAnimator,
startTransitionToken = startTransitionToken
)
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..d6b920e 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()) {
@@ -120,29 +136,39 @@
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);
}
@@ -179,4 +205,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/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/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 03ff1aa..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
@@ -32,6 +32,7 @@
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
+import android.graphics.Point;
import android.os.Bundle;
import android.os.RemoteException;
import android.util.Slog;
@@ -46,13 +47,14 @@
import androidx.annotation.VisibleForTesting;
import com.android.internal.protolog.ProtoLog;
+import com.android.window.flags.Flags;
import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
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;
@@ -79,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();
@@ -119,7 +121,7 @@
ShellCommandHandler shellCommandHandler,
TaskStackListenerImpl taskStackListener,
ActivityTaskManager activityTaskManager,
- Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
+ Optional<DesktopRepository> desktopRepository,
TaskStackTransitionObserver taskStackTransitionObserver,
@ShellMainThread ShellExecutor mainExecutor
) {
@@ -127,7 +129,7 @@
return null;
}
return new RecentTasksController(context, shellInit, shellController, shellCommandHandler,
- taskStackListener, activityTaskManager, desktopModeTaskRepository,
+ taskStackListener, activityTaskManager, desktopRepository,
taskStackTransitionObserver, mainExecutor);
}
@@ -137,7 +139,7 @@
ShellCommandHandler shellCommandHandler,
TaskStackListenerImpl taskStackListener,
ActivityTaskManager activityTaskManager,
- Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
+ Optional<DesktopRepository> desktopRepository,
TaskStackTransitionObserver taskStackTransitionObserver,
ShellExecutor mainExecutor) {
mContext = context;
@@ -146,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);
@@ -166,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);
@@ -415,14 +417,24 @@
}
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();
}
+ // If task has their app bounds set to null which happens after reboot, set the
+ // app bounds to persisted lastFullscreenBounds. Also set the position in parent
+ // to the top left of the bounds.
+ if (Flags.enableDesktopWindowingPersistence()
+ && taskInfo.configuration.windowConfiguration.getAppBounds() == null) {
+ taskInfo.configuration.windowConfiguration.setAppBounds(
+ taskInfo.lastNonFullscreenBounds);
+ taskInfo.positionInParent = new Point(taskInfo.lastNonFullscreenBounds.left,
+ 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 5daa3ee..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()) {
@@ -824,6 +837,7 @@
WindowInsets.Type.systemBars() & ~WindowInsets.Type.captionBar(),
false /* ignoreVisibility */);
relayoutParams.mCaptionTopPadding = systemBarInsets.top;
+ relayoutParams.mIsInsetSource = false;
}
// Report occluding elements as bounding rects to the insets system so that apps can
// draw in the empty space in the center:
@@ -939,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);
}
@@ -1235,6 +1276,12 @@
onCapturedLinkExpired();
return Unit.INSTANCE;
},
+ /* onOpenByDefaultClickListener= */ () -> {
+ if (!isOpenByDefaultDialogActive()) {
+ createOpenByDefaultDialog();
+ }
+ return Unit.INSTANCE;
+ },
/* onCloseMenuClickListener= */ () -> {
closeHandleMenu();
return Unit.INSTANCE;
@@ -1505,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);
@@ -1590,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/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 000beba1..f8aed41 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -346,7 +346,7 @@
private void updateCaptionInsets(RelayoutParams params, WindowContainerTransaction wct,
RelayoutResult<T> outResult, Rect taskBounds) {
- if (!mIsCaptionVisible) {
+ if (!mIsCaptionVisible || !params.mIsInsetSource) {
if (mWindowDecorationInsets != null) {
mWindowDecorationInsets.remove(wct);
mWindowDecorationInsets = null;
@@ -724,6 +724,7 @@
int mCaptionWidthId;
final List<OccludingCaptionElement> mOccludingCaptionElements = new ArrayList<>();
int mInputFeatures;
+ boolean mIsInsetSource = true;
@InsetsSource.Flags int mInsetSourceFlags;
int mShadowRadiusId;
@@ -743,6 +744,7 @@
mCaptionWidthId = Resources.ID_NULL;
mOccludingCaptionElements.clear();
mInputFeatures = 0;
+ mIsInsetSource = true;
mInsetSourceFlags = 0;
mShadowRadiusId = Resources.ID_NULL;
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 98413ee..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
@@ -30,9 +31,14 @@
import android.widget.ImageView
import android.widget.LinearLayout
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
+import com.android.wm.shell.common.DisplayChangeController.OnDisplayChangingListener
+import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.shared.animation.PhysicsAnimator
import com.android.wm.shell.windowdecor.WindowManagerWrapper
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
@@ -44,7 +50,8 @@
class DesktopWindowingEducationTooltipController(
private val context: Context,
private val additionalSystemViewContainerFactory: AdditionalSystemViewContainer.Factory,
-) {
+ private val displayController: DisplayController,
+) : OnDisplayChangingListener {
// TODO: b/369384567 - Set tooltip color scheme to match LT/DT of app theme
private var tooltipView: View? = null
private var animator: PhysicsAnimator<View>? = null
@@ -53,6 +60,20 @@
}
private var popupWindow: AdditionalSystemViewContainer? = null
+ override fun onDisplayChange(
+ displayId: Int,
+ fromRotation: Int,
+ toRotation: Int,
+ newDisplayAreaInfo: DisplayAreaInfo?,
+ t: WindowContainerTransaction?
+ ) {
+ // Exit if the rotation hasn't changed or is changed by 180 degrees. [fromRotation] and
+ // [toRotation] can be one of the [@Surface.Rotation] values.
+ if ((fromRotation % 2 == toRotation % 2)) return
+ hideEducationTooltip()
+ // TODO: b/370820018 - Update tooltip position on orientation change instead of dismissing
+ }
+
/**
* Shows education tooltip.
*
@@ -64,6 +85,7 @@
tooltipView = createEducationTooltipView(tooltipViewConfig, taskId)
animator = createAnimator()
animateShowTooltipTransition()
+ displayController.addDisplayChangingController(this)
}
/** Hide the current education view if visible */
@@ -100,6 +122,7 @@
hideEducationTooltip()
tooltipViewConfig.onEducationClickAction()
}
+ setTooltipColorScheme(tooltipViewConfig.tooltipColorScheme)
}
val tooltipDimens = tooltipDimens(tooltipView = tooltipView, tooltipViewConfig.arrowDirection)
@@ -145,6 +168,7 @@
animator = null
popupWindow?.releaseView()
popupWindow = null
+ displayController.removeDisplayChangingController(this)
}
private fun createTooltipPopupWindow(
@@ -168,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,
@@ -234,6 +273,7 @@
*/
data class EducationViewConfig(
@LayoutRes val tooltipViewLayout: Int,
+ val tooltipColorScheme: TooltipColorScheme,
val tooltipViewGlobalCoordinates: Point,
val tooltipText: String,
val arrowDirection: TooltipArrowDirection,
@@ -241,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/DesktopModeEventLoggerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
index ca97229..d7a132d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
@@ -16,13 +16,22 @@
package com.android.wm.shell.desktopmode
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
import com.android.dx.mockito.inline.extended.ExtendedMockito.verify
import com.android.internal.util.FrameworkStatsLog
import com.android.modules.utils.testing.ExtendedMockitoRule
+import com.android.window.flags.Flags
+import com.android.wm.shell.EventLogTags
+import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.TaskUpdate
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.TaskSizeUpdate
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UnminimizeReason
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UNSET_MINIMIZE_REASON
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UNSET_UNMINIMIZE_REASON
@@ -34,14 +43,19 @@
/**
* Tests for [DesktopModeEventLogger].
*/
-class DesktopModeEventLoggerTest {
+class DesktopModeEventLoggerTest : ShellTestCase() {
private val desktopModeEventLogger = DesktopModeEventLogger()
@JvmField
- @Rule
+ @Rule(order = 0)
val extendedMockitoRule = ExtendedMockitoRule.Builder(this)
- .mockStatic(FrameworkStatsLog::class.java).build()!!
+ .mockStatic(FrameworkStatsLog::class.java)
+ .mockStatic(EventLogTags::class.java).build()!!
+
+ @JvmField
+ @Rule(order = 1)
+ val setFlagsRule = SetFlagsRule()
@Test
fun logSessionEnter_enterReason() = runBlocking {
@@ -60,6 +74,11 @@
eq(SESSION_ID)
)
}
+ verify {
+ EventLogTags.writeWmShellEnterDesktopMode(
+ eq(EnterReason.UNKNOWN_ENTER.reason),
+ eq(SESSION_ID))
+ }
}
@Test
@@ -79,6 +98,11 @@
eq(SESSION_ID)
)
}
+ verify {
+ EventLogTags.writeWmShellExitDesktopMode(
+ eq(ExitReason.UNKNOWN_EXIT.reason),
+ eq(SESSION_ID))
+ }
}
@Test
@@ -108,6 +132,22 @@
/* visible_task_count */
eq(TASK_COUNT))
}
+
+ verify {
+ EventLogTags.writeWmShellDesktopModeTaskUpdate(
+ eq(FrameworkStatsLog
+ .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_ADDED),
+ eq(TASK_UPDATE.instanceId),
+ eq(TASK_UPDATE.uid),
+ eq(TASK_UPDATE.taskHeight),
+ eq(TASK_UPDATE.taskWidth),
+ eq(TASK_UPDATE.taskX),
+ eq(TASK_UPDATE.taskY),
+ eq(SESSION_ID),
+ eq(UNSET_MINIMIZE_REASON),
+ eq(UNSET_UNMINIMIZE_REASON),
+ eq(TASK_COUNT))
+ }
}
@Test
@@ -137,6 +177,22 @@
/* visible_task_count */
eq(TASK_COUNT))
}
+
+ verify {
+ EventLogTags.writeWmShellDesktopModeTaskUpdate(
+ eq(FrameworkStatsLog
+ .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_REMOVED),
+ eq(TASK_UPDATE.instanceId),
+ eq(TASK_UPDATE.uid),
+ eq(TASK_UPDATE.taskHeight),
+ eq(TASK_UPDATE.taskWidth),
+ eq(TASK_UPDATE.taskX),
+ eq(TASK_UPDATE.taskY),
+ eq(SESSION_ID),
+ eq(UNSET_MINIMIZE_REASON),
+ eq(UNSET_UNMINIMIZE_REASON),
+ eq(TASK_COUNT))
+ }
}
@Test
@@ -167,6 +223,22 @@
/* visible_task_count */
eq(TASK_COUNT))
}
+
+ verify {
+ EventLogTags.writeWmShellDesktopModeTaskUpdate(
+ eq(FrameworkStatsLog
+ .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED),
+ eq(TASK_UPDATE.instanceId),
+ eq(TASK_UPDATE.uid),
+ eq(TASK_UPDATE.taskHeight),
+ eq(TASK_UPDATE.taskWidth),
+ eq(TASK_UPDATE.taskX),
+ eq(TASK_UPDATE.taskY),
+ eq(SESSION_ID),
+ eq(UNSET_MINIMIZE_REASON),
+ eq(UNSET_UNMINIMIZE_REASON),
+ eq(TASK_COUNT))
+ }
}
@Test
@@ -200,6 +272,22 @@
/* visible_task_count */
eq(TASK_COUNT))
}
+
+ verify {
+ EventLogTags.writeWmShellDesktopModeTaskUpdate(
+ eq(FrameworkStatsLog
+ .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED),
+ eq(TASK_UPDATE.instanceId),
+ eq(TASK_UPDATE.uid),
+ eq(TASK_UPDATE.taskHeight),
+ eq(TASK_UPDATE.taskWidth),
+ eq(TASK_UPDATE.taskX),
+ eq(TASK_UPDATE.taskY),
+ eq(SESSION_ID),
+ eq(MinimizeReason.TASK_LIMIT.reason),
+ eq(UNSET_UNMINIMIZE_REASON),
+ eq(TASK_COUNT))
+ }
}
@Test
@@ -233,9 +321,83 @@
/* visible_task_count */
eq(TASK_COUNT))
}
+
+ verify {
+ EventLogTags.writeWmShellDesktopModeTaskUpdate(
+ eq(FrameworkStatsLog
+ .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED),
+ eq(TASK_UPDATE.instanceId),
+ eq(TASK_UPDATE.uid),
+ eq(TASK_UPDATE.taskHeight),
+ eq(TASK_UPDATE.taskWidth),
+ eq(TASK_UPDATE.taskX),
+ eq(TASK_UPDATE.taskY),
+ eq(SESSION_ID),
+ eq(UNSET_MINIMIZE_REASON),
+ eq(UnminimizeReason.TASKBAR_TAP.reason),
+ eq(TASK_COUNT))
+ }
}
- companion object {
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_RESIZING_METRICS)
+ fun logTaskResizingStarted_logsTaskSizeUpdatedWithStartResizingStage() = runBlocking {
+ desktopModeEventLogger.logTaskResizingStarted(sessionId = SESSION_ID, createTaskSizeUpdate())
+
+ verify {
+ FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED),
+ /* resize_trigger */
+ eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__UNKNOWN_RESIZE_TRIGGER),
+ /* resizing_stage */
+ eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZING_STAGE__START_RESIZING_STAGE),
+ /* input_method */
+ eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__UNKNOWN_INPUT_METHOD),
+ /* desktop_mode_session_id */
+ eq(SESSION_ID),
+ /* instance_id */
+ eq(TASK_SIZE_UPDATE.instanceId),
+ /* uid */
+ eq(TASK_SIZE_UPDATE.uid),
+ /* task_height */
+ eq(TASK_SIZE_UPDATE.taskHeight),
+ /* task_width */
+ eq(TASK_SIZE_UPDATE.taskWidth),
+ /* display_area */
+ eq(TASK_SIZE_UPDATE.displayArea),
+ )
+ }
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_RESIZING_METRICS)
+ fun logTaskResizingEnded_logsTaskSizeUpdatedWithEndResizingStage() = runBlocking {
+ desktopModeEventLogger.logTaskResizingEnded(sessionId = SESSION_ID, createTaskSizeUpdate())
+
+ verify {
+ FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED),
+ /* resize_trigger */
+ eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__UNKNOWN_RESIZE_TRIGGER),
+ /* resizing_stage */
+ eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZING_STAGE__END_RESIZING_STAGE),
+ /* input_method */
+ eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__UNKNOWN_INPUT_METHOD),
+ /* desktop_mode_session_id */
+ eq(SESSION_ID),
+ /* instance_id */
+ eq(TASK_SIZE_UPDATE.instanceId),
+ /* uid */
+ eq(TASK_SIZE_UPDATE.uid),
+ /* task_height */
+ eq(TASK_SIZE_UPDATE.taskHeight),
+ /* task_width */
+ eq(TASK_SIZE_UPDATE.taskWidth),
+ /* display_area */
+ eq(TASK_SIZE_UPDATE.displayArea),
+ )
+ }
+ }
+
+ private companion object {
private const val SESSION_ID = 1
private const val TASK_ID = 1
private const val TASK_UID = 1
@@ -244,16 +406,40 @@
private const val TASK_HEIGHT = 100
private const val TASK_WIDTH = 100
private const val TASK_COUNT = 1
+ private const val DISPLAY_AREA = 1000
private val TASK_UPDATE = TaskUpdate(
TASK_ID, TASK_UID, TASK_HEIGHT, TASK_WIDTH, TASK_X, TASK_Y,
visibleTaskCount = TASK_COUNT,
)
+ private val TASK_SIZE_UPDATE = TaskSizeUpdate(
+ resizeTrigger = ResizeTrigger.UNKNOWN_RESIZE_TRIGGER,
+ inputMethod = InputMethod.UNKNOWN_INPUT_METHOD,
+ TASK_ID,
+ TASK_UID,
+ TASK_HEIGHT,
+ TASK_WIDTH,
+ DISPLAY_AREA,
+ )
+
+ private fun createTaskSizeUpdate(
+ resizeTrigger: ResizeTrigger = ResizeTrigger.UNKNOWN_RESIZE_TRIGGER,
+ inputMethod: InputMethod = InputMethod.UNKNOWN_INPUT_METHOD,
+ ) = TaskSizeUpdate(
+ resizeTrigger,
+ inputMethod,
+ TASK_ID,
+ TASK_UID,
+ TASK_HEIGHT,
+ TASK_WIDTH,
+ DISPLAY_AREA,
+ )
+
private fun createTaskUpdate(
minimizeReason: MinimizeReason? = null,
unminimizeReason: UnminimizeReason? = null,
) = TaskUpdate(TASK_ID, TASK_UID, TASK_HEIGHT, TASK_WIDTH, TASK_X, TASK_Y, minimizeReason,
unminimizeReason, TASK_COUNT)
}
-}
\ No newline at end of file
+}
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 bc5ae97..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
@@ -19,6 +19,8 @@
import android.app.ActivityManager.RunningTaskInfo
import android.os.Binder
import android.os.Handler
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
import android.testing.AndroidTestingRunner
import android.view.Display.DEFAULT_DISPLAY
@@ -33,6 +35,7 @@
import com.android.dx.mockito.inline.extended.StaticMockitoSession
import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_MINIMIZE_WINDOW
import com.android.internal.jank.InteractionJankMonitor
+import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.common.ShellExecutor
@@ -89,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
@@ -103,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)
@@ -243,6 +246,7 @@
}
@Test
+ @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
fun removeLeftoverMinimizedTasks_activeNonMinimizedTasksStillAround_doesNothing() {
desktopTaskRepo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 1)
desktopTaskRepo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 2)
@@ -256,6 +260,7 @@
}
@Test
+ @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
fun removeLeftoverMinimizedTasks_noMinimizedTasks_doesNothing() {
val wct = WindowContainerTransaction()
desktopTasksLimiter.leftoverMinimizedTasksRemover.removeLeftoverMinimizedTasks(
@@ -265,6 +270,7 @@
}
@Test
+ @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
fun removeLeftoverMinimizedTasks_onlyMinimizedTasksLeft_removesAllMinimizedTasks() {
val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
@@ -283,6 +289,20 @@
}
@Test
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun removeLeftoverMinimizedTasks_onlyMinimizedTasksLeft_backNavEnabled_doesNothing() {
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ desktopTaskRepo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task1.taskId)
+ desktopTaskRepo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
+
+ val wct = WindowContainerTransaction()
+ desktopTasksLimiter.leftoverMinimizedTasksRemover.onActiveTasksChanged(DEFAULT_DISPLAY)
+
+ assertThat(wct.hierarchyOps).isEmpty()
+ }
+
+ @Test
fun addAndGetMinimizeTaskChangesIfNeeded_tasksWithinLimit_noTaskMinimized() {
(1..<MAX_TASK_LIMIT).forEach { _ -> setUpFreeformTask() }
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 c989d16..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
@@ -18,11 +18,13 @@
import android.app.ActivityManager.RunningTaskInfo
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.platform.test.annotations.EnableFlags
import android.view.Display.DEFAULT_DISPLAY
+import android.view.WindowManager.TRANSIT_OPEN
import android.view.WindowManager.TRANSIT_TO_BACK
import android.window.IWindowContainerToken
import android.window.TransitionInfo
@@ -60,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
@@ -110,6 +112,24 @@
verify(taskRepository, never()).minimizeTask(task.displayId, task.taskId)
}
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun removeTasks_onTaskFullscreenLaunch_taskRemovedFromRepo() {
+ val task = createTaskInfo(1, WINDOWING_MODE_FULLSCREEN)
+ whenever(taskRepository.getVisibleTaskCount(any())).thenReturn(1)
+ whenever(taskRepository.isActiveTask(task.taskId)).thenReturn(true)
+
+ transitionObserver.onTransitionReady(
+ transition = mock(),
+ info = createOpenTransition(task),
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ )
+
+ verify(taskRepository, never()).minimizeTask(task.displayId, task.taskId)
+ verify(taskRepository).removeFreeformTask(task.displayId, task.taskId)
+ }
+
private fun createBackNavigationTransition(
task: RunningTaskInfo?
): TransitionInfo {
@@ -125,11 +145,26 @@
}
}
- private fun createTaskInfo(id: Int) =
+ private fun createOpenTransition(
+ task: RunningTaskInfo?
+ ): TransitionInfo {
+ return TransitionInfo(TRANSIT_OPEN, 0 /* flags */).apply {
+ addChange(
+ Change(mock(), mock()).apply {
+ mode = TRANSIT_OPEN
+ parent = null
+ taskInfo = task
+ flags = flags
+ }
+ )
+ }
+ }
+
+ private fun createTaskInfo(id: Int, windowingMode: Int = WINDOWING_MODE_FREEFORM) =
RunningTaskInfo().apply {
taskId = id
displayId = DEFAULT_DISPLAY
- configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
+ configuration.windowConfiguration.windowingMode = windowingMode
token = WindowContainerToken(Mockito.mock(IWindowContainerToken::class.java))
baseIntent = Intent().apply {
component = ComponentName("package", "component.name")
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
index d9387d2..230f7e6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
@@ -581,7 +581,7 @@
)
)
.thenReturn(token)
- handler.startDragToDesktopTransition(task.taskId, dragAnimator)
+ handler.startDragToDesktopTransition(task, dragAnimator)
return token
}
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..86a8502 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,11 @@
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_FRONT;
+import static android.view.WindowManager.TRANSIT_CHANGE;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -30,6 +33,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 +42,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 +87,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 +107,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 +129,55 @@
}
@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 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 +194,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 +226,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 +245,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 +276,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 +307,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 753d4cd..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
@@ -22,6 +22,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
import static org.junit.Assert.assertEquals;
@@ -50,6 +51,7 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.graphics.Point;
import android.graphics.Rect;
import android.os.Bundle;
import android.platform.test.annotations.DisableFlags;
@@ -68,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;
@@ -107,7 +109,7 @@
@Mock
private ShellCommandHandler mShellCommandHandler;
@Mock
- private DesktopModeTaskRepository mDesktopModeTaskRepository;
+ private DesktopRepository mDesktopRepository;
@Mock
private ActivityTaskManager mActivityTaskManager;
@Mock
@@ -144,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,
@@ -303,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);
@@ -342,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);
@@ -382,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);
@@ -410,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);
@@ -441,6 +443,40 @@
}
@Test
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE)
+ public void testGetRecentTasks_hasDesktopTasks_persistenceEnabled_freeformTaskHaveBoundsSet() {
+ ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
+ ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
+
+ t1.lastNonFullscreenBounds = new Rect(100, 200, 300, 400);
+ t2.lastNonFullscreenBounds = new Rect(150, 250, 350, 450);
+ setRawList(t1, t2);
+
+ when(mDesktopRepository.isActiveTask(1)).thenReturn(true);
+ when(mDesktopRepository.isActiveTask(2)).thenReturn(true);
+
+ ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(
+ MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
+
+ assertEquals(1, recentTasks.size());
+ GroupedRecentTaskInfo freeformGroup = recentTasks.get(0);
+
+ // Check bounds
+ assertEquals(t1.lastNonFullscreenBounds, freeformGroup.getTaskInfoList().get(
+ 0).configuration.windowConfiguration.getAppBounds());
+ assertEquals(t2.lastNonFullscreenBounds, freeformGroup.getTaskInfoList().get(
+ 1).configuration.windowConfiguration.getAppBounds());
+
+ // Check position in parent
+ assertEquals(new Point(t1.lastNonFullscreenBounds.left,
+ t1.lastNonFullscreenBounds.top),
+ freeformGroup.getTaskInfoList().get(0).positionInParent);
+ assertEquals(new Point(t2.lastNonFullscreenBounds.left,
+ t2.lastNonFullscreenBounds.top),
+ freeformGroup.getTaskInfoList().get(1).positionInParent);
+ }
+
+ @Test
public void testRemovedTaskRemovesSplit() {
ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
@@ -623,6 +659,7 @@
private ActivityManager.RecentTaskInfo makeTaskInfo(int taskId) {
ActivityManager.RecentTaskInfo info = new ActivityManager.RecentTaskInfo();
info.taskId = taskId;
+ info.lastNonFullscreenBounds = new Rect();
return info;
}
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 3e7f3bd..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
@@ -619,6 +619,27 @@
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ public void updateRelayoutParams_header_notAnInsetsSourceInFullyImmersive() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ final RelayoutParams relayoutParams = new RelayoutParams();
+
+ DesktopModeWindowDecoration.updateRelayoutParams(
+ relayoutParams,
+ mTestableContext,
+ taskInfo,
+ /* applyStartTransactionOnDraw= */ true,
+ /* shouldSetTaskPositionAndCrop */ false,
+ /* isStatusBarVisible */ true,
+ /* isKeyguardVisibleAndOccluded */ false,
+ /* inFullImmersiveMode */ true,
+ new InsetsState());
+
+ assertThat(relayoutParams.mIsInsetSource).isFalse();
+ }
+
+ @Test
@DisableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
public void updateRelayoutParams_header_statusBarInvisible_captionVisible() {
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
@@ -1025,6 +1046,7 @@
any(),
openInBrowserCaptor.capture(),
any(),
+ any(),
any()
);
openInBrowserCaptor.getValue().invoke(new Intent(Intent.ACTION_MAIN, TEST_URI1));
@@ -1053,6 +1075,7 @@
any(),
openInBrowserCaptor.capture(),
any(),
+ any(),
any()
);
@@ -1103,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/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index 94cabc4..54dd15ba 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -650,6 +650,57 @@
}
@Test
+ public void testRelayout_notAnInsetsSource_doesNotAddInsets() {
+ final Display defaultDisplay = mock(Display.class);
+ doReturn(defaultDisplay).when(mMockDisplayController)
+ .getDisplay(Display.DEFAULT_DISPLAY);
+
+ final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setDisplayId(Display.DEFAULT_DISPLAY)
+ .setVisible(true)
+ .setBounds(new Rect(0, 0, 1000, 1000))
+ .build();
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
+
+ mRelayoutParams.mIsInsetSource = false;
+ windowDecor.relayout(taskInfo);
+
+ // Never added.
+ verify(mMockWindowContainerTransaction, never()).addInsetsSource(eq(taskInfo.token), any(),
+ eq(0) /* index */, eq(captionBar()), any(), any(), anyInt());
+ verify(mMockWindowContainerTransaction, never()).addInsetsSource(eq(taskInfo.token), any(),
+ eq(0) /* index */, eq(mandatorySystemGestures()), any(), any(), anyInt());
+ }
+
+ @Test
+ public void testRelayout_notAnInsetsSource_hadInsetsBefore_removesInsets() {
+ final Display defaultDisplay = mock(Display.class);
+ doReturn(defaultDisplay).when(mMockDisplayController)
+ .getDisplay(Display.DEFAULT_DISPLAY);
+
+ final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setDisplayId(Display.DEFAULT_DISPLAY)
+ .setVisible(true)
+ .setBounds(new Rect(0, 0, 1000, 1000))
+ .build();
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
+
+ mRelayoutParams.mIsCaptionVisible = true;
+ mRelayoutParams.mIsInsetSource = true;
+ windowDecor.relayout(taskInfo);
+
+ mRelayoutParams.mIsCaptionVisible = true;
+ mRelayoutParams.mIsInsetSource = false;
+ windowDecor.relayout(taskInfo);
+
+ // Insets should be removed.
+ verify(mMockWindowContainerTransaction).removeInsetsSource(eq(taskInfo.token), any(),
+ eq(0) /* index */, eq(captionBar()));
+ verify(mMockWindowContainerTransaction).removeInsetsSource(eq(taskInfo.token), any(),
+ eq(0) /* index */, eq(mandatorySystemGestures()));
+ }
+
+ @Test
public void testClose_withExistingInsets_insetsRemoved() {
final Display defaultDisplay = mock(Display.class);
doReturn(defaultDisplay).when(mMockDisplayController)
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 5594981..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
@@ -24,14 +24,21 @@
import android.testing.TestableLooper
import android.testing.TestableResources
import android.view.MotionEvent
+import android.view.Surface.ROTATION_180
+import android.view.Surface.ROTATION_90
import android.view.View
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
@@ -42,9 +49,11 @@
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.atLeastOnce
import org.mockito.kotlin.mock
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
@SmallTest
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -52,6 +61,8 @@
class DesktopWindowingEducationTooltipControllerTest : ShellTestCase() {
@Mock private lateinit var mockWindowManager: WindowManager
@Mock private lateinit var mockViewContainerFactory: AdditionalSystemViewContainer.Factory
+ @Mock private lateinit var mockDisplayController: DisplayController
+ @Mock private lateinit var mockPopupWindow: AdditionalSystemViewContainer
private lateinit var testableResources: TestableResources
private lateinit var testableContext: TestableContext
private lateinit var tooltipController: DesktopWindowingEducationTooltipController
@@ -69,7 +80,8 @@
Context.LAYOUT_INFLATER_SERVICE, context.getSystemService(Context.LAYOUT_INFLATER_SERVICE))
testableContext.addMockSystemService(WindowManager::class.java, mockWindowManager)
tooltipController =
- DesktopWindowingEducationTooltipController(testableContext, mockViewContainerFactory)
+ DesktopWindowingEducationTooltipController(
+ testableContext, mockViewContainerFactory, mockDisplayController)
}
@Test
@@ -218,8 +230,55 @@
verify(mockLambda).invoke()
}
+ @Test
+ fun showEducationTooltip_displayRotationChanged_hidesTooltip() {
+ whenever(
+ mockViewContainerFactory.create(any(), any(), any(), any(), any(), any(), any(), any()))
+ .thenReturn(mockPopupWindow)
+ val tooltipViewConfig = createTooltipConfig()
+
+ tooltipController.showEducationTooltip(tooltipViewConfig = tooltipViewConfig, taskId = 123)
+ tooltipController.onDisplayChange(
+ /* displayId= */ 123,
+ /* fromRotation= */ ROTATION_90,
+ /* toRotation= */ ROTATION_180,
+ /* newDisplayAreaInfo= */ null,
+ 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,
@@ -228,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/appfunctions/api/current.txt b/libs/appfunctions/api/current.txt
index bb0fc41..bc269fe 100644
--- a/libs/appfunctions/api/current.txt
+++ b/libs/appfunctions/api/current.txt
@@ -16,7 +16,7 @@
ctor public AppFunctionService();
method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent);
method @MainThread public void onExecuteFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>);
- method @Deprecated @MainThread public abstract void onExecuteFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>);
+ method @Deprecated @MainThread public void onExecuteFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>);
field @NonNull public static final String BIND_APP_FUNCTION_SERVICE = "android.permission.BIND_APP_FUNCTION_SERVICE";
field @NonNull public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService";
}
@@ -45,12 +45,12 @@
method @NonNull public static com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse newSuccess(@NonNull android.app.appsearch.GenericDocument, @Nullable android.os.Bundle);
field public static final String PROPERTY_RETURN_VALUE = "returnValue";
field public static final int RESULT_APP_UNKNOWN_ERROR = 2; // 0x2
+ field public static final int RESULT_CANCELLED = 6; // 0x6
field public static final int RESULT_DENIED = 1; // 0x1
- field public static final int RESULT_DISABLED = 6; // 0x6
+ field public static final int RESULT_DISABLED = 5; // 0x5
field public static final int RESULT_INTERNAL_ERROR = 3; // 0x3
field public static final int RESULT_INVALID_ARGUMENT = 4; // 0x4
field public static final int RESULT_OK = 0; // 0x0
- field public static final int RESULT_TIMED_OUT = 5; // 0x5
}
}
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java
index 6023c97..6e91de6 100644
--- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java
+++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java
@@ -26,6 +26,7 @@
import android.os.Binder;
import android.os.IBinder;
import android.os.CancellationSignal;
+import android.util.Log;
import java.util.function.Consumer;
@@ -143,7 +144,11 @@
*/
@MainThread
@Deprecated
- public abstract void onExecuteFunction(
+ public void onExecuteFunction(
@NonNull ExecuteAppFunctionRequest request,
- @NonNull Consumer<ExecuteAppFunctionResponse> callback);
+ @NonNull Consumer<ExecuteAppFunctionResponse> callback) {
+ Log.w(
+ "AppFunctionService",
+ "Calling deprecated default implementation of onExecuteFunction");
+ }
}
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java
index c7ce95b..d87fec79 100644
--- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java
+++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java
@@ -73,11 +73,14 @@
*/
public static final int RESULT_INVALID_ARGUMENT = 4;
- /** The operation was timed out. */
- public static final int RESULT_TIMED_OUT = 5;
-
/** The caller tried to execute a disabled app function. */
- public static final int RESULT_DISABLED = 6;
+ public static final int RESULT_DISABLED = 5;
+
+ /**
+ * The operation was cancelled. Use this error code to report that a cancellation is done after
+ * receiving a cancellation signal.
+ */
+ public static final int RESULT_CANCELLED = 6;
/** The result code of the app function execution. */
@ResultCode private final int mResultCode;
@@ -236,7 +239,6 @@
RESULT_APP_UNKNOWN_ERROR,
RESULT_INTERNAL_ERROR,
RESULT_INVALID_ARGUMENT,
- RESULT_TIMED_OUT,
RESULT_DISABLED
})
@Retention(RetentionPolicy.SOURCE)
diff --git a/libs/hwui/AutoBackendTextureRelease.cpp b/libs/hwui/AutoBackendTextureRelease.cpp
index 27add35..4305196 100644
--- a/libs/hwui/AutoBackendTextureRelease.cpp
+++ b/libs/hwui/AutoBackendTextureRelease.cpp
@@ -141,6 +141,13 @@
return;
}
+ if (!RenderThread::isCurrent()) {
+ // releaseQueueOwnership needs to run on RenderThread to prevent multithread calling
+ // setBackendTextureState will operate skia resource cache which need single owner
+ RenderThread::getInstance().queue().post([this, context]() { releaseQueueOwnership(context); });
+ return;
+ }
+
LOG_ALWAYS_FATAL_IF(Properties::getRenderPipelineType() != RenderPipelineType::SkiaVulkan);
if (mBackendTexture.isValid()) {
// Passing in VK_IMAGE_LAYOUT_UNDEFINED means we keep the old layout.
diff --git a/libs/hwui/Gainmap.cpp b/libs/hwui/Gainmap.cpp
index 30f401e..ea955e2 100644
--- a/libs/hwui/Gainmap.cpp
+++ b/libs/hwui/Gainmap.cpp
@@ -15,12 +15,37 @@
*/
#include "Gainmap.h"
+#include <SkBitmap.h>
+#include <SkCanvas.h>
+#include <SkColorFilter.h>
+#include <SkImagePriv.h>
+#include <SkPaint.h>
+
+#include "HardwareBitmapUploader.h"
+
namespace android::uirenderer {
sp<Gainmap> Gainmap::allocateHardwareGainmap(const sp<Gainmap>& srcGainmap) {
auto gainmap = sp<Gainmap>::make();
gainmap->info = srcGainmap->info;
- const SkBitmap skSrcBitmap = srcGainmap->bitmap->getSkBitmap();
+ SkBitmap skSrcBitmap = srcGainmap->bitmap->getSkBitmap();
+ if (skSrcBitmap.info().colorType() == kAlpha_8_SkColorType &&
+ !HardwareBitmapUploader::hasAlpha8Support()) {
+ // The regular Bitmap::allocateHardwareBitmap will do a conversion that preserves channels,
+ // so alpha8 maps to the alpha channel of rgba. However, for gainmaps we will interpret
+ // the data of an rgba buffer differently as we'll only look at the rgb channels
+ // So we need to map alpha8 to rgbx_8888 essentially
+ SkBitmap bitmap;
+ bitmap.allocPixels(skSrcBitmap.info().makeColorType(kN32_SkColorType));
+ SkCanvas canvas(bitmap);
+ SkPaint paint;
+ const float alphaToOpaque[] = {0, 0, 0, 1, 0, 0, 0, 0, 1, 0,
+ 0, 0, 0, 1, 0, 0, 0, 0, 0, 255};
+ paint.setColorFilter(SkColorFilters::Matrix(alphaToOpaque, SkColorFilters::Clamp::kNo));
+ canvas.drawImage(SkMakeImageFromRasterBitmap(skSrcBitmap, kNever_SkCopyPixelsMode), 0, 0,
+ SkSamplingOptions{}, &paint);
+ skSrcBitmap = bitmap;
+ }
sk_sp<Bitmap> skBitmap(Bitmap::allocateHardwareBitmap(skSrcBitmap));
if (!skBitmap.get()) {
return nullptr;
diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp
index 588463c..e074a27 100644
--- a/libs/hwui/hwui/ImageDecoder.cpp
+++ b/libs/hwui/hwui/ImageDecoder.cpp
@@ -501,18 +501,13 @@
SkCodec::Result ImageDecoder::extractGainmap(Bitmap* destination, bool isShared) {
ATRACE_CALL();
SkGainmapInfo gainmapInfo;
- std::unique_ptr<SkStream> gainmapStream;
+ std::unique_ptr<SkAndroidCodec> gainmapCodec;
{
- ATRACE_NAME("getAndroidGainmap");
- if (!mCodec->getAndroidGainmap(&gainmapInfo, &gainmapStream)) {
+ ATRACE_NAME("getGainmapAndroidCodec");
+ if (!mCodec->getGainmapAndroidCodec(&gainmapInfo, &gainmapCodec)) {
return SkCodec::kSuccess;
}
}
- auto gainmapCodec = SkAndroidCodec::MakeFromStream(std::move(gainmapStream));
- if (!gainmapCodec) {
- ALOGW("Failed to create codec for gainmap stream");
- return SkCodec::kInvalidInput;
- }
ImageDecoder decoder{std::move(gainmapCodec)};
// Gainmap inherits the origin of the containing image
decoder.mOverrideOrigin.emplace(getOrigin());
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/BitmapFactory.cpp b/libs/hwui/jni/BitmapFactory.cpp
index 785aef3..49a7f73 100644
--- a/libs/hwui/jni/BitmapFactory.cpp
+++ b/libs/hwui/jni/BitmapFactory.cpp
@@ -183,14 +183,8 @@
needsFineScale(fullSize.height(), decodedSize.height(), sampleSize);
}
-static bool decodeGainmap(std::unique_ptr<SkStream> gainmapStream, const SkGainmapInfo& gainmapInfo,
+static bool decodeGainmap(std::unique_ptr<SkAndroidCodec> codec, const SkGainmapInfo& gainmapInfo,
sp<uirenderer::Gainmap>* outGainmap, const int sampleSize, float scale) {
- std::unique_ptr<SkAndroidCodec> codec;
- codec = SkAndroidCodec::MakeFromStream(std::move(gainmapStream), nullptr);
- if (!codec) {
- ALOGE("Can not create a codec for Gainmap.");
- return false;
- }
SkColorType decodeColorType = kN32_SkColorType;
if (codec->getInfo().colorType() == kGray_8_SkColorType) {
decodeColorType = kGray_8_SkColorType;
@@ -613,15 +607,15 @@
bool hasGainmap = false;
SkGainmapInfo gainmapInfo;
- std::unique_ptr<SkStream> gainmapStream = nullptr;
+ std::unique_ptr<SkAndroidCodec> gainmapCodec;
sp<uirenderer::Gainmap> gainmap = nullptr;
if (result == SkCodec::kSuccess) {
- hasGainmap = codec->getAndroidGainmap(&gainmapInfo, &gainmapStream);
+ hasGainmap = codec->getGainmapAndroidCodec(&gainmapInfo, &gainmapCodec);
}
if (hasGainmap) {
hasGainmap =
- decodeGainmap(std::move(gainmapStream), gainmapInfo, &gainmap, sampleSize, scale);
+ decodeGainmap(std::move(gainmapCodec), gainmapInfo, &gainmap, sampleSize, scale);
}
if (!isMutable && javaBitmap == NULL) {
diff --git a/libs/hwui/jni/BitmapRegionDecoder.cpp b/libs/hwui/jni/BitmapRegionDecoder.cpp
index 6a65b82..f7e8e07 100644
--- a/libs/hwui/jni/BitmapRegionDecoder.cpp
+++ b/libs/hwui/jni/BitmapRegionDecoder.cpp
@@ -48,25 +48,14 @@
}
SkGainmapInfo gainmapInfo;
- std::unique_ptr<SkStream> gainmapStream;
+ std::unique_ptr<SkAndroidCodec> gainmapCodec;
std::unique_ptr<skia::BitmapRegionDecoder> gainmapBRD = nullptr;
- if (mainImageBRD->getAndroidGainmap(&gainmapInfo, &gainmapStream)) {
- sk_sp<SkData> data = nullptr;
- if (gainmapStream->getMemoryBase()) {
- // It is safe to make without copy because we'll hold onto the stream.
- data = SkData::MakeWithoutCopy(gainmapStream->getMemoryBase(),
- gainmapStream->getLength());
- } else {
- data = SkCopyStreamToData(gainmapStream.get());
- // We don't need to hold the stream anymore
- gainmapStream = nullptr;
- }
- gainmapBRD = skia::BitmapRegionDecoder::Make(std::move(data));
+ if (!mainImageBRD->getGainmapBitmapRegionDecoder(&gainmapInfo, &gainmapBRD)) {
+ gainmapBRD = nullptr;
}
- return std::unique_ptr<BitmapRegionDecoderWrapper>(
- new BitmapRegionDecoderWrapper(std::move(mainImageBRD), std::move(gainmapBRD),
- gainmapInfo, std::move(gainmapStream)));
+ return std::unique_ptr<BitmapRegionDecoderWrapper>(new BitmapRegionDecoderWrapper(
+ std::move(mainImageBRD), std::move(gainmapBRD), gainmapInfo));
}
SkEncodedImageFormat getEncodedFormat() { return mMainImageBRD->getEncodedFormat(); }
@@ -191,16 +180,14 @@
private:
BitmapRegionDecoderWrapper(std::unique_ptr<skia::BitmapRegionDecoder> mainImageBRD,
std::unique_ptr<skia::BitmapRegionDecoder> gainmapBRD,
- SkGainmapInfo info, std::unique_ptr<SkStream> stream)
+ SkGainmapInfo info)
: mMainImageBRD(std::move(mainImageBRD))
, mGainmapBRD(std::move(gainmapBRD))
- , mGainmapInfo(info)
- , mGainmapStream(std::move(stream)) {}
+ , mGainmapInfo(info) {}
std::unique_ptr<skia::BitmapRegionDecoder> mMainImageBRD;
std::unique_ptr<skia::BitmapRegionDecoder> mGainmapBRD;
SkGainmapInfo mGainmapInfo;
- std::unique_ptr<SkStream> mGainmapStream;
};
} // namespace android
diff --git a/libs/hwui/jni/graphics_jni_helpers.h b/libs/hwui/jni/graphics_jni_helpers.h
index 78db54a..91db134 100644
--- a/libs/hwui/jni/graphics_jni_helpers.h
+++ b/libs/hwui/jni/graphics_jni_helpers.h
@@ -80,9 +80,52 @@
return static_cast<T>(res);
}
+// Inline variable that specifies the method binding format.
+// The expected format is 'XX${method}XX', where ${method} represents the original method name.
+// This variable is shared across all translation units. This is treated as a global variable as
+// per C++ 17.
+inline std::string jniMethodFormat;
+
+inline static void setJniMethodFormat(std::string value) {
+ jniMethodFormat = value;
+}
+
+// Register the native methods, potenially applying the jniMethodFormat if it has been set.
+static inline int jniRegisterMaybeRenamedNativeMethods(JNIEnv* env, const char* className,
+ const JNINativeMethod* gMethods,
+ int numMethods) {
+ if (jniMethodFormat.empty()) {
+ return jniRegisterNativeMethods(env, className, gMethods, numMethods);
+ }
+
+ // Make a copy of gMethods with reformatted method names.
+ JNINativeMethod* modifiedMethods = new JNINativeMethod[numMethods];
+ LOG_ALWAYS_FATAL_IF(!modifiedMethods, "Failed to allocate a copy of the JNI methods");
+
+ size_t methodNamePos = jniMethodFormat.find("${method}");
+ LOG_ALWAYS_FATAL_IF(methodNamePos == std::string::npos,
+ "Invalid jniMethodFormat: could not find '${method}' in pattern");
+
+ for (int i = 0; i < numMethods; i++) {
+ modifiedMethods[i] = gMethods[i];
+ std::string modifiedName = jniMethodFormat;
+ modifiedName.replace(methodNamePos, 9, gMethods[i].name);
+ char* modifiedNameChars = new char[modifiedName.length() + 1];
+ LOG_ALWAYS_FATAL_IF(!modifiedNameChars, "Failed to allocate the new method name");
+ std::strcpy(modifiedNameChars, modifiedName.c_str());
+ modifiedMethods[i].name = modifiedNameChars;
+ }
+ int res = jniRegisterNativeMethods(env, className, modifiedMethods, numMethods);
+ for (int i = 0; i < numMethods; i++) {
+ delete[] modifiedMethods[i].name;
+ }
+ delete[] modifiedMethods;
+ return res;
+}
+
static inline int RegisterMethodsOrDie(JNIEnv* env, const char* className,
const JNINativeMethod* gMethods, int numMethods) {
- int res = jniRegisterNativeMethods(env, className, gMethods, numMethods);
+ int res = jniRegisterMaybeRenamedNativeMethods(env, className, gMethods, numMethods);
LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods.");
return res;
}
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/api/current.txt b/nfc/api/current.txt
index e7cb76c..96b7c13 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -223,6 +223,7 @@
field public static final String CATEGORY_PAYMENT = "payment";
field public static final String EXTRA_CATEGORY = "category";
field public static final String EXTRA_SERVICE_COMPONENT = "component";
+ field @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public static final int PROTOCOL_AND_TECHNOLOGY_ROUTE_DEFAULT = 3; // 0x3
field @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public static final int PROTOCOL_AND_TECHNOLOGY_ROUTE_DH = 0; // 0x0
field @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public static final int PROTOCOL_AND_TECHNOLOGY_ROUTE_ESE = 1; // 0x1
field @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public static final int PROTOCOL_AND_TECHNOLOGY_ROUTE_UICC = 2; // 0x2
diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt
index 2db90fe..4428ade 100644
--- a/nfc/api/system-current.txt
+++ b/nfc/api/system-current.txt
@@ -58,12 +58,16 @@
@FlaggedApi("android.nfc.nfc_oem_extension") public final class NfcOemExtension {
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void clearPreference();
method @FlaggedApi("android.nfc.nfc_oem_extension") @NonNull public java.util.List<java.lang.String> getActiveNfceeList();
+ method @FlaggedApi("android.nfc.nfc_oem_extension") @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public android.nfc.RoutingStatus getRoutingStatus();
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean hasUserEnabledNfc();
+ method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean isAutoChangeEnabled();
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean isTagPresent();
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void maybeTriggerFirmwareUpdate();
+ method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void overwriteRoutingTable(int, int, int);
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void pausePolling(int);
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcOemExtension.Callback);
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void resumePolling();
+ method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setAutoChangeEnabled(boolean);
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void setControllerAlwaysOnMode(int);
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void synchronizeScreenState();
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void triggerInitialization();
@@ -105,6 +109,12 @@
method public void onTagDispatch(@NonNull java.util.function.Consumer<java.lang.Boolean>);
}
+ @FlaggedApi("android.nfc.nfc_oem_extension") public class RoutingStatus {
+ method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int getDefaultIsoDepRoute();
+ method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int getDefaultOffHostRoute();
+ method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int getDefaultRoute();
+ }
+
}
package android.nfc.cardemulation {
diff --git a/nfc/java/android/nfc/INfcCardEmulation.aidl b/nfc/java/android/nfc/INfcCardEmulation.aidl
index 19b9e0f..1eae3c6 100644
--- a/nfc/java/android/nfc/INfcCardEmulation.aidl
+++ b/nfc/java/android/nfc/INfcCardEmulation.aidl
@@ -51,4 +51,8 @@
void overrideRoutingTable(int userHandle, String protocol, String technology, in String pkg);
void recoverRoutingTable(int userHandle);
boolean isEuiccSupported();
+ void setAutoChangeStatus(boolean state);
+ boolean isAutoChangeEnabled();
+ List<String> getRoutingStatus();
+ void overwriteRoutingTable(int userHandle, String emptyAid, String protocol, String tech);
}
diff --git a/nfc/java/android/nfc/NfcOemExtension.java b/nfc/java/android/nfc/NfcOemExtension.java
index 8484dca..fb63b5c 100644
--- a/nfc/java/android/nfc/NfcOemExtension.java
+++ b/nfc/java/android/nfc/NfcOemExtension.java
@@ -16,6 +16,12 @@
package android.nfc;
+import static android.nfc.cardemulation.CardEmulation.PROTOCOL_AND_TECHNOLOGY_ROUTE_DH;
+import static android.nfc.cardemulation.CardEmulation.PROTOCOL_AND_TECHNOLOGY_ROUTE_ESE;
+import static android.nfc.cardemulation.CardEmulation.PROTOCOL_AND_TECHNOLOGY_ROUTE_UICC;
+import static android.nfc.cardemulation.CardEmulation.routeIntToString;
+
+import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
@@ -27,6 +33,8 @@
import android.content.Context;
import android.content.Intent;
import android.nfc.cardemulation.ApduServiceInfo;
+import android.nfc.cardemulation.CardEmulation;
+import android.nfc.cardemulation.CardEmulation.ProtocolAndTechnologyRoute;
import android.os.Binder;
import android.os.Bundle;
import android.os.RemoteException;
@@ -581,6 +589,85 @@
NfcAdapter.callService(() -> NfcAdapter.sService.resumePolling());
}
+ /**
+ * Set whether to enable auto routing change or not (enabled by default).
+ * If disabled, routing targets are limited to a single off-host destination.
+ *
+ * @param state status of auto routing change, true if enable, otherwise false
+ */
+ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
+ @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ public void setAutoChangeEnabled(boolean state) {
+ NfcAdapter.callService(() ->
+ NfcAdapter.sCardEmulationService.setAutoChangeStatus(state));
+ }
+
+ /**
+ * Check if auto routing change is enabled or not.
+ *
+ * @return true if enabled, otherwise false
+ */
+ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
+ @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ public boolean isAutoChangeEnabled() {
+ return NfcAdapter.callServiceReturn(() ->
+ NfcAdapter.sCardEmulationService.isAutoChangeEnabled(), false);
+ }
+
+ /**
+ * Get current routing status
+ *
+ * @return {@link RoutingStatus} indicating the default route, default ISO-DEP
+ * route and default off-host route.
+ */
+ @NonNull
+ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
+ @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ public RoutingStatus getRoutingStatus() {
+ List<String> status = NfcAdapter.callServiceReturn(() ->
+ NfcAdapter.sCardEmulationService.getRoutingStatus(), new ArrayList<>());
+ return new RoutingStatus(routeStringToInt(status.get(0)),
+ routeStringToInt(status.get(1)),
+ routeStringToInt(status.get(2)));
+ }
+
+ /**
+ * Overwrites NFC controller routing table, which includes Protocol Route, Technology Route,
+ * and Empty AID Route.
+ *
+ * The parameter set to
+ * {@link ProtocolAndTechnologyRoute#PROTOCOL_AND_TECHNOLOGY_ROUTE_UNSET}
+ * can be used to keep current values for that entry. At least one route should be overridden
+ * when calling this API, otherwise throw {@link IllegalArgumentException}.
+ *
+ * @param protocol ISO-DEP route destination, where the possible inputs are defined in
+ * {@link ProtocolAndTechnologyRoute}.
+ * @param technology Tech-A, Tech-B and Tech-F route destination, where the possible inputs
+ * are defined in
+ * {@link ProtocolAndTechnologyRoute}
+ * @param emptyAid Zero-length AID route destination, where the possible inputs are defined in
+ * {@link ProtocolAndTechnologyRoute}
+ */
+ @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
+ public void overwriteRoutingTable(
+ @CardEmulation.ProtocolAndTechnologyRoute int protocol,
+ @CardEmulation.ProtocolAndTechnologyRoute int technology,
+ @CardEmulation.ProtocolAndTechnologyRoute int emptyAid) {
+
+ String protocolRoute = routeIntToString(protocol);
+ String technologyRoute = routeIntToString(technology);
+ String emptyAidRoute = routeIntToString(emptyAid);
+
+ NfcAdapter.callService(() ->
+ NfcAdapter.sCardEmulationService.overwriteRoutingTable(
+ mContext.getUser().getIdentifier(),
+ emptyAidRoute,
+ protocolRoute,
+ technologyRoute
+ ));
+ }
+
private final class NfcOemExtensionCallback extends INfcOemExtensionCallback.Stub {
@Override
@@ -829,6 +916,15 @@
}
}
+ private @CardEmulation.ProtocolAndTechnologyRoute int routeStringToInt(String route) {
+ return switch (route) {
+ case "DH" -> PROTOCOL_AND_TECHNOLOGY_ROUTE_DH;
+ case "eSE" -> PROTOCOL_AND_TECHNOLOGY_ROUTE_ESE;
+ case "SIM" -> PROTOCOL_AND_TECHNOLOGY_ROUTE_UICC;
+ default -> throw new IllegalStateException("Unexpected value: " + route);
+ };
+ }
+
private class ReceiverWrapper<T> implements Consumer<T> {
private final ResultReceiver mResultReceiver;
diff --git a/nfc/java/android/nfc/RoutingStatus.java b/nfc/java/android/nfc/RoutingStatus.java
new file mode 100644
index 0000000..4a1b1f3
--- /dev/null
+++ b/nfc/java/android/nfc/RoutingStatus.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.nfc;
+
+import android.annotation.FlaggedApi;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.nfc.cardemulation.CardEmulation;
+
+/**
+ * A class indicating default route, ISO-DEP route and off-host route.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
+public class RoutingStatus {
+ private final @CardEmulation.ProtocolAndTechnologyRoute int mDefaultRoute;
+ private final @CardEmulation.ProtocolAndTechnologyRoute int mDefaultIsoDepRoute;
+ private final @CardEmulation.ProtocolAndTechnologyRoute int mDefaultOffHostRoute;
+
+ RoutingStatus(@CardEmulation.ProtocolAndTechnologyRoute int mDefaultRoute,
+ @CardEmulation.ProtocolAndTechnologyRoute int mDefaultIsoDepRoute,
+ @CardEmulation.ProtocolAndTechnologyRoute int mDefaultOffHostRoute) {
+ this.mDefaultRoute = mDefaultRoute;
+ this.mDefaultIsoDepRoute = mDefaultIsoDepRoute;
+ this.mDefaultOffHostRoute = mDefaultOffHostRoute;
+ }
+
+ /**
+ * Getter of the default route.
+ * @return an integer defined in
+ * {@link android.nfc.cardemulation.CardEmulation.ProtocolAndTechnologyRoute}
+ */
+ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
+ @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ @CardEmulation.ProtocolAndTechnologyRoute
+ public int getDefaultRoute() {
+ return mDefaultRoute;
+ }
+
+ /**
+ * Getter of the default ISO-DEP route.
+ * @return an integer defined in
+ * {@link android.nfc.cardemulation.CardEmulation.ProtocolAndTechnologyRoute}
+ */
+ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
+ @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ @CardEmulation.ProtocolAndTechnologyRoute
+ public int getDefaultIsoDepRoute() {
+ return mDefaultIsoDepRoute;
+ }
+
+ /**
+ * Getter of the default off-host route.
+ * @return an integer defined in
+ * {@link android.nfc.cardemulation.CardEmulation.ProtocolAndTechnologyRoute}
+ */
+ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
+ @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ @CardEmulation.ProtocolAndTechnologyRoute
+ public int getDefaultOffHostRoute() {
+ return mDefaultOffHostRoute;
+ }
+
+}
diff --git a/nfc/java/android/nfc/cardemulation/CardEmulation.java b/nfc/java/android/nfc/cardemulation/CardEmulation.java
index 4be082c..d8f04c5 100644
--- a/nfc/java/android/nfc/cardemulation/CardEmulation.java
+++ b/nfc/java/android/nfc/cardemulation/CardEmulation.java
@@ -168,6 +168,12 @@
public static final int PROTOCOL_AND_TECHNOLOGY_ROUTE_UICC = 2;
/**
+ * Route to the default value in config file.
+ */
+ @FlaggedApi(Flags.FLAG_NFC_OVERRIDE_RECOVER_ROUTING_TABLE)
+ public static final int PROTOCOL_AND_TECHNOLOGY_ROUTE_DEFAULT = 3;
+
+ /**
* Route unset.
*/
@FlaggedApi(Flags.FLAG_NFC_OVERRIDE_RECOVER_ROUTING_TABLE)
@@ -895,45 +901,47 @@
PROTOCOL_AND_TECHNOLOGY_ROUTE_DH,
PROTOCOL_AND_TECHNOLOGY_ROUTE_ESE,
PROTOCOL_AND_TECHNOLOGY_ROUTE_UICC,
- PROTOCOL_AND_TECHNOLOGY_ROUTE_UNSET
+ PROTOCOL_AND_TECHNOLOGY_ROUTE_UNSET,
+ PROTOCOL_AND_TECHNOLOGY_ROUTE_DEFAULT
})
@Retention(RetentionPolicy.SOURCE)
public @interface ProtocolAndTechnologyRoute {}
- /**
- * Setting NFC controller routing table, which includes Protocol Route and Technology Route,
- * while this Activity is in the foreground.
- *
- * The parameter set to {@link #PROTOCOL_AND_TECHNOLOGY_ROUTE_UNSET}
- * can be used to keep current values for that entry. Either
- * Protocol Route or Technology Route should be override when calling this API, otherwise
- * throw {@link IllegalArgumentException}.
- * <p>
- * Example usage in an Activity that requires to set proto route to "ESE" and keep tech route:
- * <pre>
- * protected void onResume() {
- * mNfcAdapter.overrideRoutingTable(
- * this, {@link #PROTOCOL_AND_TECHNOLOGY_ROUTE_ESE}, null);
- * }</pre>
- * </p>
- * Also activities must call {@link #recoverRoutingTable(Activity)}
- * when it goes to the background. Only the package of the
- * currently preferred service (the service set as preferred by the current foreground
- * application via {@link CardEmulation#setPreferredService(Activity, ComponentName)} or the
- * current Default Wallet Role Holder {@link RoleManager#ROLE_WALLET}),
- * otherwise a call to this method will fail and throw {@link SecurityException}.
- * @param activity The Activity that requests NFC controller routing table to be changed.
- * @param protocol ISO-DEP route destination, where the possible inputs are defined
- * in {@link ProtocolAndTechnologyRoute}.
- * @param technology Tech-A, Tech-B and Tech-F route destination, where the possible inputs
- * are defined in {@link ProtocolAndTechnologyRoute}
- * @throws SecurityException if the caller is not the preferred NFC service
- * @throws IllegalArgumentException if the activity is not resumed or the caller is not in the
- * foreground.
- * <p>
- * This is a high risk API and only included to support mainline effort
- * @hide
- */
+ /**
+ * Setting NFC controller routing table, which includes Protocol Route and Technology Route,
+ * while this Activity is in the foreground.
+ *
+ * The parameter set to {@link #PROTOCOL_AND_TECHNOLOGY_ROUTE_UNSET}
+ * can be used to keep current values for that entry. Either
+ * Protocol Route or Technology Route should be override when calling this API, otherwise
+ * throw {@link IllegalArgumentException}.
+ * <p>
+ * Example usage in an Activity that requires to set proto route to "ESE" and keep tech route:
+ * <pre>
+ * protected void onResume() {
+ * mNfcAdapter.overrideRoutingTable(
+ * this, {@link #PROTOCOL_AND_TECHNOLOGY_ROUTE_ESE},
+ * {@link #PROTOCOL_AND_TECHNOLOGY_ROUTE_UNSET});
+ * }</pre>
+ * </p>
+ * Also activities must call {@link #recoverRoutingTable(Activity)}
+ * when it goes to the background. Only the package of the
+ * currently preferred service (the service set as preferred by the current foreground
+ * application via {@link CardEmulation#setPreferredService(Activity, ComponentName)} or the
+ * current Default Wallet Role Holder {@link RoleManager#ROLE_WALLET}),
+ * otherwise a call to this method will fail and throw {@link SecurityException}.
+ * @param activity The Activity that requests NFC controller routing table to be changed.
+ * @param protocol ISO-DEP route destination, where the possible inputs are defined
+ * in {@link ProtocolAndTechnologyRoute}.
+ * @param technology Tech-A, Tech-B and Tech-F route destination, where the possible inputs
+ * are defined in {@link ProtocolAndTechnologyRoute}
+ * @throws SecurityException if the caller is not the preferred NFC service
+ * @throws IllegalArgumentException if the activity is not resumed or the caller is not in the
+ * foreground.
+ * <p>
+ * This is a high risk API and only included to support mainline effort
+ * @hide
+ */
@SystemApi
@FlaggedApi(Flags.FLAG_NFC_OVERRIDE_RECOVER_ROUTING_TABLE)
public void overrideRoutingTable(
@@ -942,26 +950,14 @@
if (!activity.isResumed()) {
throw new IllegalArgumentException("Activity must be resumed.");
}
- String protocolRoute = switch (protocol) {
- case PROTOCOL_AND_TECHNOLOGY_ROUTE_DH -> "DH";
- case PROTOCOL_AND_TECHNOLOGY_ROUTE_ESE -> "ESE";
- case PROTOCOL_AND_TECHNOLOGY_ROUTE_UICC -> "UICC";
- case PROTOCOL_AND_TECHNOLOGY_ROUTE_UNSET -> null;
- default -> throw new IllegalStateException("Unexpected value: " + protocol);
- };
- String technologyRoute = switch (technology) {
- case PROTOCOL_AND_TECHNOLOGY_ROUTE_DH -> "DH";
- case PROTOCOL_AND_TECHNOLOGY_ROUTE_ESE -> "ESE";
- case PROTOCOL_AND_TECHNOLOGY_ROUTE_UICC -> "UICC";
- case PROTOCOL_AND_TECHNOLOGY_ROUTE_UNSET -> null;
- default -> throw new IllegalStateException("Unexpected value: " + protocol);
- };
+ String protocolRoute = routeIntToString(protocol);
+ String technologyRoute = routeIntToString(technology);
callService(() ->
sService.overrideRoutingTable(
- mContext.getUser().getIdentifier(),
- protocolRoute,
- technologyRoute,
- mContext.getPackageName()));
+ mContext.getUser().getIdentifier(),
+ protocolRoute,
+ technologyRoute,
+ mContext.getPackageName()));
}
/**
@@ -1068,4 +1064,16 @@
}
return defaultReturn;
}
+
+ /** @hide */
+ public static String routeIntToString(@ProtocolAndTechnologyRoute int route) {
+ return switch (route) {
+ case PROTOCOL_AND_TECHNOLOGY_ROUTE_DH -> "DH";
+ case PROTOCOL_AND_TECHNOLOGY_ROUTE_ESE -> "eSE";
+ case PROTOCOL_AND_TECHNOLOGY_ROUTE_UICC -> "SIM";
+ case PROTOCOL_AND_TECHNOLOGY_ROUTE_UNSET -> null;
+ case PROTOCOL_AND_TECHNOLOGY_ROUTE_DEFAULT -> "default";
+ default -> throw new IllegalStateException("Unexpected value: " + route);
+ };
+ }
}
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/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml
index a57d6eb..b266912 100644
--- a/packages/CompanionDeviceManager/res/values/strings.xml
+++ b/packages/CompanionDeviceManager/res/values/strings.xml
@@ -50,13 +50,13 @@
<!-- ================= DEVICE_PROFILE_APP_STREAMING ================= -->
<!-- Confirmation for associating an application with a companion device of APP_STREAMING profile (type) [CHAR LIMIT=NONE] -->
- <string name="title_app_streaming">Allow <strong><xliff:g id="app_name" example="Exo">%1$s</xliff:g></strong> to stream your <xliff:g id="device_type" example="phone">%2$s</xliff:g>\u2019s apps to <strong><xliff:g id="device_name" example="Chromebook">%3$s</xliff:g></strong>?</string>
+ <string name="title_app_streaming">Allow <strong><xliff:g id="app_name" example="Exo">%1$s</xliff:g></strong> to stream your <xliff:g id="device_type" example="phone">%2$s</xliff:g>\u2019s apps and system features to <strong><xliff:g id="device_name" example="Chromebook">%3$s</xliff:g></strong>?</string>
<!-- Summary for associating an application with a companion device of APP_STREAMING profile [CHAR LIMIT=NONE] -->
- <string name="summary_app_streaming"><xliff:g id="app_name" example="Exo">%1$s</xliff:g> will have access to anything that’s visible or played on the <xliff:g id="device_type" example="phone">%2$s</xliff:g>, including audio, photos, passwords, and messages.<br/><br/><xliff:g id="app_name" example="Exo">%1$s</xliff:g> will be able to stream apps to <xliff:g id="device_name" example="Chromebook">%3$s</xliff:g> until you remove access to this permission.</string>
+ <string name="summary_app_streaming"><xliff:g id="app_name" example="Exo">%1$s</xliff:g> will have access to anything that’s visible or played on your <xliff:g id="device_type" example="phone">%2$s</xliff:g>, including audio, photos, payment info, passwords, and messages.<br/><br/><xliff:g id="app_name" example="Exo">%1$s</xliff:g> will be able to stream apps to <xliff:g id="device_name" example="Chromebook">%3$s</xliff:g> until you remove access to this permission.</string>
<!-- Description of the helper dialog for APP_STREAMING profile. [CHAR LIMIT=NONE] -->
- <string name="helper_summary_app_streaming"><xliff:g id="app_name" example="GMS">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="device_name" example="Chromebook">%2$s</xliff:g> to display and stream apps between your devices</string>
+ <string name="helper_summary_app_streaming"><xliff:g id="app_name" example="Exo">%1$s</xliff:g> is requesting permission on behalf of <xliff:g id="device_name" example="Chromebook">%2$s</xliff:g> to stream apps and system features from your <xliff:g id="device_type" example="phone">%3$s</xliff:g></string>
<!-- ================= DEVICE_PROFILE_AUTOMOTIVE_PROJECTION ================= -->
@@ -80,13 +80,13 @@
<!-- ================= DEVICE_PROFILE_NEARBY_DEVICE_STREAMING ================= -->
<!-- Confirmation for associating an application with a companion device of NEARBY_DEVICE_STREAMING profile (type) [CHAR LIMIT=NONE] -->
- <string name="title_nearby_device_streaming">Allow <strong><xliff:g id="device_name" example="NearbyStreamer">%1$s</xliff:g></strong> to stream your <xliff:g id="device_type" example="phone">%2$s</xliff:g>\u2019s apps and system features to <strong><xliff:g id="device_name" example="Chromebook">%3$s</xliff:g></strong>?</string>
+ <string name="title_nearby_device_streaming">Allow <strong><xliff:g id="app_name" example="Exo">%1$s</xliff:g></strong> to stream your <xliff:g id="device_type" example="phone">%2$s</xliff:g>\u2019s apps to <strong><xliff:g id="device_name" example="Chromebook">%3$s</xliff:g></strong>?</string>
<!-- Summary for associating an application with a companion device of NEARBY_DEVICE_STREAMING profile [CHAR LIMIT=NONE] -->
- <string name="summary_nearby_device_streaming"><xliff:g id="app_name" example="Exo">%1$s</xliff:g> will have access to anything that’s visible or played on your <xliff:g id="device_type" example="phone">%2$s</xliff:g>, including audio, photos, payment info, passwords, and messages.<br/><br/><xliff:g id="app_name" example="Exo">%1$s</xliff:g> will be able to stream apps and system features to <xliff:g id="device_name" example="Chromebook">%3$s</xliff:g> until you remove access to this permission.</string>
+ <string name="summary_nearby_device_streaming"><xliff:g id="app_name" example="Exo">%1$s</xliff:g> will have access to anything that’s visible or played on <xliff:g id="device_name" example="Chromebook">%3$s</xliff:g>, including audio, photos, payment info, passwords, and messages.<br/><br/><xliff:g id="app_name" example="Exo">%1$s</xliff:g> will be able to stream apps to <xliff:g id="device_name" example="Chromebook">%3$s</xliff:g> until you remove access to this permission.</string>
<!-- Description of the helper dialog for NEARBY_DEVICE_STREAMING profile. [CHAR LIMIT=NONE] -->
- <string name="helper_summary_nearby_device_streaming"><xliff:g id="app_name" example="NearbyStreamerApp">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="device_name" example="NearbyDevice">%2$s</xliff:g> to stream apps and other system features between your devices</string>
+ <string name="helper_summary_nearby_device_streaming"><xliff:g id="app_name" example="Exo">%1$s</xliff:g> is requesting permission on behalf of <xliff:g id="device_name" example="Chromebook">%2$s</xliff:g> to stream apps from your <xliff:g id="device_type" example="phone">%3$s</xliff:g></string>
<!-- ================= null profile ================= -->
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/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index 9d56c77..744e97e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -30,9 +30,10 @@
import android.icu.text.NumberFormat;
import android.location.LocationManager;
import android.media.AudioManager;
+import android.net.ConnectivityManager;
import android.net.NetworkCapabilities;
import android.net.TetheringManager;
-import android.net.vcn.VcnTransportInfo;
+import android.net.vcn.VcnUtils;
import android.net.wifi.WifiInfo;
import android.os.BatteryManager;
import android.os.Build;
@@ -737,14 +738,9 @@
* @param networkCapabilities NetworkCapabilities of the network.
*/
@Nullable
- public static WifiInfo tryGetWifiInfoForVcn(NetworkCapabilities networkCapabilities) {
- if (networkCapabilities.getTransportInfo() == null
- || !(networkCapabilities.getTransportInfo() instanceof VcnTransportInfo)) {
- return null;
- }
- VcnTransportInfo vcnTransportInfo =
- (VcnTransportInfo) networkCapabilities.getTransportInfo();
- return vcnTransportInfo.getWifiInfo();
+ public static WifiInfo tryGetWifiInfoForVcn(
+ ConnectivityManager connectivityMgr, NetworkCapabilities networkCapabilities) {
+ return VcnUtils.getWifiInfoFromVcnCaps(connectivityMgr, networkCapabilities);
}
/** Whether there is any incompatible chargers in the current UsbPort? */
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/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
index 015356e..cea3d17 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
@@ -30,6 +30,7 @@
import android.net.ScoredNetwork;
import android.net.TransportInfo;
import android.net.vcn.VcnTransportInfo;
+import android.net.vcn.VcnUtils;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiNetworkScoreCache;
@@ -394,10 +395,7 @@
TransportInfo transportInfo = networkCapabilities.getTransportInfo();
if (transportInfo instanceof VcnTransportInfo) {
- // This VcnTransportInfo logic is copied from
- // [com.android.settingslib.Utils.tryGetWifiInfoForVcn]. It's copied instead of
- // re-used because it makes the logic here clearer.
- return ((VcnTransportInfo) transportInfo).getWifiInfo();
+ return VcnUtils.getWifiInfoFromVcnCaps(mConnectivityManager, networkCapabilities);
} else if (transportInfo instanceof WifiInfo) {
return (WifiInfo) transportInfo;
} else {
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/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 5e31da4..4dc8424 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -168,10 +168,6 @@
Settings.Secure.SHOW_NOTIFICATION_SNOOZE,
Settings.Secure.NOTIFICATION_HISTORY_ENABLED,
Settings.Secure.ZEN_DURATION,
- Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION,
- Settings.Secure.SHOW_ZEN_SETTINGS_SUGGESTION,
- Settings.Secure.ZEN_SETTINGS_UPDATED,
- Settings.Secure.ZEN_SETTINGS_SUGGESTION_VIEWED,
Settings.Secure.CHARGING_SOUNDS_ENABLED,
Settings.Secure.CHARGING_VIBRATION_ENABLED,
Settings.Secure.ACCESSIBILITY_NON_INTERACTIVE_UI_TIMEOUT_MS,
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/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index b3f7374..688676d 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -247,10 +247,6 @@
VALIDATORS.put(Secure.SHOW_NOTIFICATION_SNOOZE, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.NOTIFICATION_HISTORY_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ZEN_DURATION, ANY_INTEGER_VALIDATOR);
- VALIDATORS.put(Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, BOOLEAN_VALIDATOR);
- VALIDATORS.put(Secure.SHOW_ZEN_SETTINGS_SUGGESTION, BOOLEAN_VALIDATOR);
- VALIDATORS.put(Secure.ZEN_SETTINGS_UPDATED, BOOLEAN_VALIDATOR);
- VALIDATORS.put(Secure.ZEN_SETTINGS_SUGGESTION_VIEWED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.CHARGING_SOUNDS_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.CHARGING_VIBRATION_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 3c24f5c..2034f36 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -2734,18 +2734,6 @@
Settings.Secure.ZEN_DURATION,
SecureSettingsProto.Zen.DURATION);
dumpSetting(s, p,
- Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION,
- SecureSettingsProto.Zen.SHOW_ZEN_UPGRADE_NOTIFICATION);
- dumpSetting(s, p,
- Settings.Secure.SHOW_ZEN_SETTINGS_SUGGESTION,
- SecureSettingsProto.Zen.SHOW_ZEN_SETTINGS_SUGGESTION);
- dumpSetting(s, p,
- Settings.Secure.ZEN_SETTINGS_UPDATED,
- SecureSettingsProto.Zen.SETTINGS_UPDATED);
- dumpSetting(s, p,
- Settings.Secure.ZEN_SETTINGS_SUGGESTION_VIEWED,
- SecureSettingsProto.Zen.SETTINGS_SUGGESTION_VIEWED);
- dumpSetting(s, p,
Settings.Secure.CHARGE_OPTIMIZATION_MODE,
SecureSettingsProto.CHARGE_OPTIMIZATION_MODE);
p.end(zenToken);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 749ad0a..a8af43f5 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -4771,9 +4771,9 @@
}
if (currentVersion == 169) {
- // Version 169: Set the default value for Secure Settings ZEN_DURATION,
- // SHOW_ZEN_SETTINGS_SUGGESTION, ZEN_SETTINGS_UPDATE and
- // ZEN_SETTINGS_SUGGESTION_VIEWED
+ // Version 169: Set the default value for Secure Settings ZEN_DURATION.
+ // Also used to update SHOW_ZEN_SETTINGS_SUGGESTION, ZEN_SETTINGS_UPDATE and
+ // ZEN_SETTINGS_SUGGESTION_VIEWED, but those properties are gone now.
final SettingsState globalSettings = getGlobalSettingsLocked();
final Setting globalZenDuration = globalSettings.getSettingLocked(
@@ -4801,33 +4801,6 @@
SettingsState.SYSTEM_PACKAGE_NAME);
}
- // SHOW_ZEN_SETTINGS_SUGGESTION
- final Setting currentShowZenSettingSuggestion = secureSettings.getSettingLocked(
- Secure.SHOW_ZEN_SETTINGS_SUGGESTION);
- if (currentShowZenSettingSuggestion.isNull()) {
- secureSettings.insertSettingOverrideableByRestoreLocked(
- Secure.SHOW_ZEN_SETTINGS_SUGGESTION, "1",
- null, true, SettingsState.SYSTEM_PACKAGE_NAME);
- }
-
- // ZEN_SETTINGS_UPDATED
- final Setting currentUpdatedSetting = secureSettings.getSettingLocked(
- Secure.ZEN_SETTINGS_UPDATED);
- if (currentUpdatedSetting.isNull()) {
- secureSettings.insertSettingOverrideableByRestoreLocked(
- Secure.ZEN_SETTINGS_UPDATED, "0",
- null, true, SettingsState.SYSTEM_PACKAGE_NAME);
- }
-
- // ZEN_SETTINGS_SUGGESTION_VIEWED
- final Setting currentSettingSuggestionViewed = secureSettings.getSettingLocked(
- Secure.ZEN_SETTINGS_SUGGESTION_VIEWED);
- if (currentSettingSuggestionViewed.isNull()) {
- secureSettings.insertSettingOverrideableByRestoreLocked(
- Secure.ZEN_SETTINGS_SUGGESTION_VIEWED, "0",
- null, true, SettingsState.SYSTEM_PACKAGE_NAME);
- }
-
currentVersion = 170;
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 3c634f0..011ffbc 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -610,7 +610,7 @@
flag.getPackageName(),
flag.getFlagName(),
flag.getServerFlagValue(),
- false);
+ StorageRequestMessage.SERVER_ON_REBOOT);
}
if (flag.getHasLocalOverride()) {
@@ -619,7 +619,7 @@
flag.getPackageName(),
flag.getFlagName(),
flag.getLocalFlagValue(),
- true);
+ StorageRequestMessage.LOCAL_ON_REBOOT);
}
}
diff --git a/packages/SettingsProvider/test/AndroidTest.xml b/packages/SettingsProvider/test/AndroidTest.xml
index dccc2d3..541a294 100644
--- a/packages/SettingsProvider/test/AndroidTest.xml
+++ b/packages/SettingsProvider/test/AndroidTest.xml
@@ -32,7 +32,7 @@
<option name="package" value="com.android.providers.setting.test" />
<option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
<option name="hidden-api-checks" value="false"/>
- <option name="exclude-annotation" value="com.android.bedstead.harrier.annotations.RequireRunOnWorkProfile" />
- <option name="exclude-annotation" value="com.android.bedstead.harrier.annotations.RequireRunOnSecondaryUser" />
+ <option name="exclude-annotation" value="com.android.bedstead.enterprise.annotations.RequireRunOnWorkProfile" />
+ <option name="exclude-annotation" value="com.android.bedstead.multiuser.annotations.RequireRunOnSecondaryUser" />
</test>
</configuration>
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/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java
index e4898da..e86e727 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java
@@ -31,10 +31,10 @@
import com.android.bedstead.enterprise.annotations.EnsureHasWorkProfile;
import com.android.bedstead.harrier.BedsteadJUnit4;
import com.android.bedstead.harrier.DeviceState;
-import com.android.bedstead.harrier.annotations.EnsureHasSecondaryUser;
import com.android.bedstead.harrier.annotations.RequireFeature;
import com.android.bedstead.harrier.annotations.RequireRunOnInitialUser;
-import com.android.bedstead.harrier.annotations.RequireRunOnPrimaryUser;
+import com.android.bedstead.multiuser.annotations.EnsureHasSecondaryUser;
+import com.android.bedstead.multiuser.annotations.RequireRunOnPrimaryUser;
import com.android.bedstead.nene.TestApis;
import com.android.bedstead.nene.users.UserReference;
import com.android.bedstead.nene.users.UserType;
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 ec7012e..c6238e8 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1081,6 +1081,16 @@
}
flag {
+ name: "dream_overlay_updated_font"
+ namespace: "systemui"
+ description: "Flag to enable updated font settings for dream overlay"
+ bug: "349656117"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "app_clips_backlinks"
namespace: "systemui"
description: "Enables Backlinks improvement feature in App Clips"
@@ -1391,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 {
@@ -1460,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/scene/data/repository/SystemGestureExclusionRepositoryKosmos.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/DreamSceneModule.kt
similarity index 65%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepositoryKosmos.kt
copy to packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/DreamSceneModule.kt
index 15ed1b3..2f82369 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepositoryKosmos.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/DreamSceneModule.kt
@@ -14,12 +14,15 @@
* limitations under the License.
*/
-package com.android.systemui.scene.data.repository
+package com.android.systemui.scene
-import android.view.windowManagerService
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.Kosmos.Fixture
+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
-val Kosmos.systemGestureExclusionRepository by Fixture {
- SystemGestureExclusionRepository(windowManager = windowManagerService)
+@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/ResizeableItemFrame.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResizeableItemFrame.kt
new file mode 100644
index 0000000..fda46b8
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResizeableItemFrame.kt
@@ -0,0 +1,242 @@
+/*
+ * 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.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
+import kotlinx.coroutines.flow.filterNotNull
+
+@Composable
+private fun UpdateGridLayoutInfo(
+ viewModel: ResizeableItemFrameViewModel,
+ index: Int,
+ gridState: LazyGridState,
+ minItemSpan: Int,
+ gridContentPadding: PaddingValues,
+ verticalArrangement: Arrangement.Vertical,
+) {
+ val density = LocalDensity.current
+ LaunchedEffect(
+ density,
+ viewModel,
+ index,
+ 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.index == index }
+ }
+ .filterNotNull(),
+ ::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(
+ index: Int,
+ 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 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,
+ index,
+ gridState,
+ minItemSpan,
+ gridContentPadding,
+ verticalArrangement,
+ )
+ LaunchedEffect(viewModel) { viewModel.resizeInfo.collectLatest(onResize) }
+ }
+ }
+}
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 ac58ab5..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
@@ -1,5 +1,6 @@
package com.android.systemui.scene.ui.composable
+import androidx.compose.animation.core.spring
import androidx.compose.foundation.gestures.Orientation
import com.android.compose.animation.scene.ProgressConverter
import com.android.compose.animation.scene.TransitionKey
@@ -12,11 +13,15 @@
import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade
import com.android.systemui.scene.ui.composable.transitions.bouncerToGoneTransition
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
@@ -44,10 +49,12 @@
// Overscroll progress starts linearly with some resistance (3f) and slowly approaches 0.2f
defaultOverscrollProgressConverter = ProgressConverter.tanh(maxProgress = 0.2f, tilt = 3f)
+ defaultSwipeSpec = spring(stiffness = 300f, dampingRatio = 0.8f, visibilityThreshold = 0.5f)
// 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) {
@@ -68,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()
@@ -87,6 +95,8 @@
sharedElement(Notifications.Elements.NotificationStackPlaceholder, enabled = false)
sharedElement(Notifications.Elements.HeadsUpNotificationPlaceholder, enabled = false)
}
+ from(Scenes.Communal, to = Scenes.Shade) { communalToShadeTransition() }
+ from(Scenes.Communal, to = Scenes.Bouncer) { communalToBouncerTransition() }
// Overlay transitions
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepositoryKosmos.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromCommunalToBouncerTransition.kt
similarity index 65%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepositoryKosmos.kt
copy to packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromCommunalToBouncerTransition.kt
index 15ed1b3..d7173fd 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepositoryKosmos.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromCommunalToBouncerTransition.kt
@@ -14,12 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.scene.data.repository
+package com.android.systemui.scene.ui.composable.transitions
-import android.view.windowManagerService
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.compose.animation.scene.TransitionBuilder
-val Kosmos.systemGestureExclusionRepository by Fixture {
- SystemGestureExclusionRepository(windowManager = windowManagerService)
+fun TransitionBuilder.communalToBouncerTransition() {
+ toBouncerTransition()
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepositoryKosmos.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromCommunalToShadeTransition.kt
similarity index 65%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepositoryKosmos.kt
copy to packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromCommunalToShadeTransition.kt
index 15ed1b3..ba920ac 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepositoryKosmos.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromCommunalToShadeTransition.kt
@@ -14,12 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.scene.data.repository
+package com.android.systemui.scene.ui.composable.transitions
-import android.view.windowManagerService
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.compose.animation.scene.TransitionBuilder
-val Kosmos.systemGestureExclusionRepository by Fixture {
- SystemGestureExclusionRepository(windowManager = windowManagerService)
+fun TransitionBuilder.communalToShadeTransition() {
+ toShadeTransition()
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepositoryKosmos.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/scene/data/repository/SystemGestureExclusionRepositoryKosmos.kt
copy to packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromDreamToGoneTransition.kt
index 15ed1b3..60dc4c05 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepositoryKosmos.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromDreamToGoneTransition.kt
@@ -14,12 +14,14 @@
* limitations under the License.
*/
-package com.android.systemui.scene.data.repository
+package com.android.systemui.scene.ui.composable.transitions
-import android.view.windowManagerService
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.Kosmos.Fixture
+import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.scene.shared.model.Scenes
-val Kosmos.systemGestureExclusionRepository by Fixture {
- SystemGestureExclusionRepository(windowManager = windowManagerService)
+fun TransitionBuilder.dreamToGoneTransition() {
+ spec = tween(durationMillis = 1000)
+
+ fade(Scenes.Dream.rootElementKey)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToBouncerTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToBouncerTransition.kt
index 4c0efd2..dd37b53 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToBouncerTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToBouncerTransition.kt
@@ -1,29 +1,11 @@
package com.android.systemui.scene.ui.composable.transitions
import androidx.compose.animation.core.CubicBezierEasing
-import androidx.compose.animation.core.tween
-import androidx.compose.ui.unit.dp
import com.android.compose.animation.scene.TransitionBuilder
-import com.android.compose.animation.scene.UserActionDistance
import com.android.systemui.bouncer.ui.composable.Bouncer
-const val FROM_LOCK_SCREEN_TO_BOUNCER_FADE_FRACTION = 0.5f
-const val FROM_LOCK_SCREEN_TO_BOUNCER_SWIPE_DISTANCE_FRACTION = 0.5f
-
fun TransitionBuilder.lockscreenToBouncerTransition() {
- spec = tween(durationMillis = 500)
-
- distance = UserActionDistance { fromSceneSize, _ ->
- fromSceneSize.height * FROM_LOCK_SCREEN_TO_BOUNCER_SWIPE_DISTANCE_FRACTION
- }
-
- translate(Bouncer.Elements.Content, y = 300.dp)
- fractionRange(end = FROM_LOCK_SCREEN_TO_BOUNCER_FADE_FRACTION) {
- fade(Bouncer.Elements.Background)
- }
- fractionRange(start = FROM_LOCK_SCREEN_TO_BOUNCER_FADE_FRACTION) {
- fade(Bouncer.Elements.Content)
- }
+ toBouncerTransition()
}
fun TransitionBuilder.bouncerToLockscreenPreview() {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SystemGestureExclusionInteractorKosmos.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToDreamTransition.kt
similarity index 62%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SystemGestureExclusionInteractorKosmos.kt
rename to packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToDreamTransition.kt
index 3e46c3f..7092e3f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SystemGestureExclusionInteractorKosmos.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToDreamTransition.kt
@@ -14,12 +14,14 @@
* limitations under the License.
*/
-package com.android.systemui.scene.domain.interactor
+package com.android.systemui.scene.ui.composable.transitions
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.scene.data.repository.systemGestureExclusionRepository
+import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.scene.shared.model.Scenes
-val Kosmos.systemGestureExclusionInteractor by Fixture {
- SystemGestureExclusionInteractor(repository = systemGestureExclusionRepository)
+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/ToBouncerTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToBouncerTransition.kt
new file mode 100644
index 0000000..de76f70
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToBouncerTransition.kt
@@ -0,0 +1,38 @@
+/*
+ * 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.scene.ui.composable.transitions
+
+import androidx.compose.animation.core.tween
+import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.compose.animation.scene.UserActionDistance
+import com.android.systemui.bouncer.ui.composable.Bouncer
+
+const val TO_BOUNCER_FADE_FRACTION = 0.5f
+private const val TO_BOUNCER_SWIPE_DISTANCE_FRACTION = 0.5f
+
+fun TransitionBuilder.toBouncerTransition() {
+ spec = tween(durationMillis = 500)
+
+ distance = UserActionDistance { fromSceneSize, _ ->
+ fromSceneSize.height * TO_BOUNCER_SWIPE_DISTANCE_FRACTION
+ }
+
+ translate(Bouncer.Elements.Content, y = 300.dp)
+ fractionRange(end = TO_BOUNCER_FADE_FRACTION) { fade(Bouncer.Elements.Background) }
+ fractionRange(start = TO_BOUNCER_FADE_FRACTION) { fade(Bouncer.Elements.Content) }
+}
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/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
index 5fa5db8..085157a 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
@@ -226,7 +226,6 @@
val fromSource = resolveSwipeSource(startedPosition)
val upOrLeft = resolveSwipe(pointersDown, fromSource, isUpOrLeft = true)
val downOrRight = resolveSwipe(pointersDown, fromSource, isUpOrLeft = false)
-
return if (fromSource == null) {
Swipes(
upOrLeft = null,
@@ -366,10 +365,18 @@
return 0f
}
+ val currentTransitionIrreversible =
+ if (swipeAnimation.isUpOrLeft) {
+ swipes.upOrLeftResult?.isIrreversible ?: false
+ } else {
+ swipes.downOrRightResult?.isIrreversible ?: false
+ }
+
val needNewTransition =
- hasReachedToContent ||
- result.toContent(layoutState.currentScene) != swipeAnimation.toContent ||
- result.transitionKey != swipeAnimation.contentTransition.key
+ !currentTransitionIrreversible &&
+ (hasReachedToContent ||
+ result.toContent(layoutState.currentScene) != swipeAnimation.toContent ||
+ result.transitionKey != swipeAnimation.contentTransition.key)
if (needNewTransition) {
// Make sure the current transition will finish to the right current scene.
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 5ddc284..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
@@ -128,7 +128,7 @@
var onDragStarted:
(startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
var onFirstPointerDown: () -> Unit,
- var swipeDetector: SwipeDetector = DefaultSwipeDetector,
+ swipeDetector: SwipeDetector = DefaultSwipeDetector,
private val dispatcher: NestedScrollDispatcher,
) :
DelegatingNode(),
@@ -139,6 +139,14 @@
private val pointerInput = delegate(SuspendingPointerInputModifierNode { pointerInput() })
private val velocityTracker = VelocityTracker()
+ var swipeDetector: SwipeDetector = swipeDetector
+ set(value) {
+ if (value != field) {
+ field = value
+ pointerInput.resetPointerInputHandler()
+ }
+ }
+
private var converter = SpaceVectorConverter(orientation)
override fun Offset.toFloat(): Float = with(converter) { this@toFloat.toFloat() }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index cec8883..c9a4d58 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -498,6 +498,12 @@
* bigger than 100% when the user released their finger. `
*/
open val requiresFullDistanceSwipe: Boolean,
+
+ /**
+ * Whether swiping back in the opposite direction past the origin point of the swipe can replace
+ * the action with the action for the opposite direction.
+ */
+ open val isIrreversible: Boolean = false,
) {
internal abstract fun toContent(currentScene: SceneKey): ContentKey
@@ -507,6 +513,7 @@
val toScene: SceneKey,
override val transitionKey: TransitionKey? = null,
override val requiresFullDistanceSwipe: Boolean = false,
+ override val isIrreversible: Boolean = false,
) : UserActionResult(transitionKey, requiresFullDistanceSwipe) {
override fun toContent(currentScene: SceneKey): ContentKey = toScene
}
@@ -516,6 +523,7 @@
val overlay: OverlayKey,
override val transitionKey: TransitionKey? = null,
override val requiresFullDistanceSwipe: Boolean = false,
+ override val isIrreversible: Boolean = false,
) : UserActionResult(transitionKey, requiresFullDistanceSwipe) {
override fun toContent(currentScene: SceneKey): ContentKey = overlay
}
@@ -558,7 +566,14 @@
* the user released their finger.
*/
requiresFullDistanceSwipe: Boolean = false,
- ): UserActionResult = ChangeScene(toScene, transitionKey, requiresFullDistanceSwipe)
+
+ /**
+ * Whether swiping back in the opposite direction past the origin point of the swipe can
+ * replace the action with the action for the opposite direction.
+ */
+ isIrreversible: Boolean = false,
+ ): UserActionResult =
+ ChangeScene(toScene, transitionKey, requiresFullDistanceSwipe, isIrreversible)
/** A [UserActionResult] that shows [toOverlay]. */
operator fun invoke(
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
index b358faf..879dc54 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
@@ -152,6 +152,7 @@
internal val DefaultSwipeSpec =
spring(
stiffness = Spring.StiffnessMediumLow,
+ dampingRatio = Spring.DampingRatioLowBouncy,
visibilityThreshold = OffsetVisibilityThreshold,
)
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 d201be9..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
@@ -68,16 +68,35 @@
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()
@@ -93,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 =
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
index a6ebb0e..a3641e6 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
@@ -34,71 +34,76 @@
* Note: Call [reset] before destroying this object to make sure you always get a call to [onStop]
* after [onStart].
*
- * @sample com.android.compose.animation.scene.rememberSwipeToSceneNestedScrollConnection
+ * @sample LargeTopAppBarNestedScrollConnection
+ * @sample com.android.compose.animation.scene.NestedScrollHandlerImpl.nestedScrollConnection
*/
class PriorityNestedScrollConnection(
- private val canStartPreScroll: (offsetAvailable: Offset, offsetBeforeStart: Offset) -> Boolean,
- private val canStartPostScroll: (offsetAvailable: Offset, offsetBeforeStart: Offset) -> Boolean,
- private val canStartPostFling: (velocityAvailable: Velocity) -> Boolean,
+ orientation: Orientation,
+ private val canStartPreScroll: (offsetAvailable: Float, offsetBeforeStart: Float) -> Boolean,
+ private val canStartPostScroll: (offsetAvailable: Float, offsetBeforeStart: Float) -> Boolean,
+ private val canStartPostFling: (velocityAvailable: Float) -> Boolean,
private val canContinueScroll: (source: NestedScrollSource) -> Boolean,
private val canScrollOnFling: Boolean,
- private val onStart: (offsetAvailable: Offset) -> Unit,
- private val onScroll: (offsetAvailable: Offset) -> Offset,
- private val onStop: (velocityAvailable: Velocity) -> SuspendedValue<Velocity>,
-) : NestedScrollConnection {
+ private val onStart: (offsetAvailable: Float) -> Unit,
+ private val onScroll: (offsetAvailable: Float) -> Float,
+ private val onStop: (velocityAvailable: Float) -> SuspendedValue<Float>,
+) : NestedScrollConnection, SpaceVectorConverter by SpaceVectorConverter(orientation) {
/** In priority mode [onPreScroll] events are first consumed by the parent, via [onScroll]. */
private var isPriorityMode = false
- private var offsetScrolledBeforePriorityMode = Offset.Zero
+ private var offsetScrolledBeforePriorityMode = 0f
override fun onPostScroll(
consumed: Offset,
available: Offset,
source: NestedScrollSource,
): Offset {
+ val availableFloat = available.toFloat()
// The offset before the start takes into account the up and down movements, starting from
// the beginning or from the last fling gesture.
- val offsetBeforeStart = offsetScrolledBeforePriorityMode - available
+ val offsetBeforeStart = offsetScrolledBeforePriorityMode - availableFloat
if (
isPriorityMode ||
(source == NestedScrollSource.SideEffect && !canScrollOnFling) ||
- !canStartPostScroll(available, offsetBeforeStart)
+ !canStartPostScroll(availableFloat, offsetBeforeStart)
) {
// The priority mode cannot start so we won't consume the available offset.
return Offset.Zero
}
- return onPriorityStart(available)
+ return onPriorityStart(availableFloat).toOffset()
}
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
if (!isPriorityMode) {
if (source == NestedScrollSource.UserInput || canScrollOnFling) {
- if (canStartPreScroll(available, offsetScrolledBeforePriorityMode)) {
- return onPriorityStart(available)
+ val availableFloat = available.toFloat()
+ if (canStartPreScroll(availableFloat, offsetScrolledBeforePriorityMode)) {
+ return onPriorityStart(availableFloat).toOffset()
}
// We want to track the amount of offset consumed before entering priority mode
- offsetScrolledBeforePriorityMode += available
+ offsetScrolledBeforePriorityMode += availableFloat
}
return Offset.Zero
}
+ val availableFloat = available.toFloat()
if (!canContinueScroll(source)) {
// Step 3a: We have lost priority and we no longer need to intercept scroll events.
- onPriorityStop(velocity = Velocity.Zero)
+ onPriorityStop(velocity = 0f)
- // We've just reset offsetScrolledBeforePriorityMode to Offset.Zero
+ // We've just reset offsetScrolledBeforePriorityMode to 0f
// We want to track the amount of offset consumed before entering priority mode
- offsetScrolledBeforePriorityMode += available
+ offsetScrolledBeforePriorityMode += availableFloat
return Offset.Zero
}
// Step 2: We have the priority and can consume the scroll events.
- return onScroll(available)
+ return onScroll(availableFloat).toOffset()
}
override suspend fun onPreFling(available: Velocity): Velocity {
@@ -108,15 +113,16 @@
}
// Step 3b: The finger is lifted, we can stop intercepting scroll events and use the speed
// of the fling gesture.
- return onPriorityStop(velocity = available).invoke()
+ return onPriorityStop(velocity = available.toFloat()).invoke().toVelocity()
}
override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
+ val availableFloat = available.toFloat()
if (isPriorityMode) {
- return onPriorityStop(velocity = available).invoke()
+ return onPriorityStop(velocity = availableFloat).invoke().toVelocity()
}
- if (!canStartPostFling(available)) {
+ if (!canStartPostFling(availableFloat)) {
return Velocity.Zero
}
@@ -124,11 +130,11 @@
// given the available velocity.
// TODO(b/291053278): Remove canStartPostFling() and instead make it possible to define the
// overscroll behavior on the Scene level.
- val smallOffset = Offset(available.x.sign, available.y.sign)
- onPriorityStart(available = smallOffset)
+ val smallOffset = availableFloat.sign
+ onPriorityStart(availableOffset = smallOffset)
// This is the last event of a scroll gesture.
- return onPriorityStop(available).invoke()
+ return onPriorityStop(availableFloat).invoke().toVelocity()
}
/**
@@ -138,10 +144,10 @@
*/
fun reset() {
// Step 3c: To ensure that an onStop is always called for every onStart.
- onPriorityStop(velocity = Velocity.Zero)
+ onPriorityStop(velocity = 0f)
}
- private fun onPriorityStart(available: Offset): Offset {
+ private fun onPriorityStart(availableOffset: Float): Float {
if (isPriorityMode) {
error("This should never happen, onPriorityStart() was called when isPriorityMode")
}
@@ -152,17 +158,17 @@
// Note: onStop will be called if we cannot continue to scroll (step 3a), or the finger is
// lifted (step 3b), or this object has been destroyed (step 3c).
- onStart(available)
+ onStart(availableOffset)
- return onScroll(available)
+ return onScroll(availableOffset)
}
- private fun onPriorityStop(velocity: Velocity): SuspendedValue<Velocity> {
+ private fun onPriorityStop(velocity: Float): SuspendedValue<Float> {
// We can restart tracking the consumed offsets from scratch.
- offsetScrolledBeforePriorityMode = Offset.Zero
+ offsetScrolledBeforePriorityMode = 0f
if (!isPriorityMode) {
- return { Velocity.Zero }
+ return { 0f }
}
isPriorityMode = false
@@ -170,38 +176,3 @@
return onStop(velocity)
}
}
-
-fun PriorityNestedScrollConnection(
- orientation: Orientation,
- canStartPreScroll: (offsetAvailable: Float, offsetBeforeStart: Float) -> Boolean,
- canStartPostScroll: (offsetAvailable: Float, offsetBeforeStart: Float) -> Boolean,
- canStartPostFling: (velocityAvailable: Float) -> Boolean,
- canContinueScroll: (source: NestedScrollSource) -> Boolean,
- canScrollOnFling: Boolean,
- onStart: (offsetAvailable: Float) -> Unit,
- onScroll: (offsetAvailable: Float) -> Float,
- onStop: (velocityAvailable: Float) -> SuspendedValue<Float>,
-) =
- with(SpaceVectorConverter(orientation)) {
- PriorityNestedScrollConnection(
- canStartPreScroll = { offsetAvailable: Offset, offsetBeforeStart: Offset ->
- canStartPreScroll(offsetAvailable.toFloat(), offsetBeforeStart.toFloat())
- },
- canStartPostScroll = { offsetAvailable: Offset, offsetBeforeStart: Offset ->
- canStartPostScroll(offsetAvailable.toFloat(), offsetBeforeStart.toFloat())
- },
- canStartPostFling = { velocityAvailable: Velocity ->
- canStartPostFling(velocityAvailable.toFloat())
- },
- canContinueScroll = canContinueScroll,
- canScrollOnFling = canScrollOnFling,
- onStart = { offsetAvailable -> onStart(offsetAvailable.toFloat()) },
- onScroll = { offsetAvailable: Offset ->
- onScroll(offsetAvailable.toFloat()).toOffset()
- },
- onStop = { velocityAvailable: Velocity ->
- val consumedVelocity = onStop(velocityAvailable.toFloat())
- suspend { consumedVelocity.invoke().toVelocity() }
- },
- )
- }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
index dd4f99f..ecef6be 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
@@ -507,6 +507,54 @@
}
@Test
+ fun onDragWithActionsInBothDirections_dragToOppositeDirectionReplacesAction() = runGestureTest {
+ // We are on SceneA. UP -> B, DOWN-> C.
+ val dragController = onDragStarted(overSlop = up(fractionOfScreen = 0.2f))
+ assertTransition(
+ currentScene = SceneA,
+ fromScene = SceneA,
+ toScene = SceneB,
+ progress = 0.2f,
+ )
+
+ // Reverse drag direction, it will replace the previous transition
+ dragController.onDragDelta(pixels = down(fractionOfScreen = 0.5f))
+ assertTransition(
+ currentScene = SceneA,
+ fromScene = SceneA,
+ toScene = SceneC,
+ progress = 0.3f,
+ )
+ }
+
+ @Test
+ fun onDragWithActionsInBothDirections_dragToOppositeDirectionNotReplaceable() = runGestureTest {
+ // We are on SceneA. UP -> B, DOWN-> C. The up swipe is not replaceable though.
+ mutableUserActionsA =
+ mapOf(Swipe.Up to UserActionResult(SceneB, isIrreversible = true), Swipe.Down to SceneC)
+ val dragController =
+ onDragStarted(
+ startedPosition = Offset(SCREEN_SIZE * 0.5f, SCREEN_SIZE * 0.5f),
+ overSlop = up(fractionOfScreen = 0.2f),
+ )
+ assertTransition(
+ currentScene = SceneA,
+ fromScene = SceneA,
+ toScene = SceneB,
+ progress = 0.2f,
+ )
+
+ // Reverse drag direction, it cannot replace the previous transition
+ dragController.onDragDelta(pixels = down(fractionOfScreen = 0.5f))
+ assertTransition(
+ currentScene = SceneA,
+ fromScene = SceneA,
+ toScene = SceneB,
+ progress = -0.3f,
+ )
+ }
+
+ @Test
fun onDragFromEdge_startTransitionToEdgeAction() = runGestureTest {
navigateToSceneC()
@@ -1241,7 +1289,8 @@
fun overscroll_releaseBetween0And100Percent_up() = runGestureTest {
// Make scene B overscrollable.
layoutState.transitions = transitions {
- from(SceneA, to = SceneB) { spec = spring(dampingRatio = Spring.DampingRatioNoBouncy) }
+ defaultSwipeSpec = spring(dampingRatio = Spring.DampingRatioNoBouncy)
+ from(SceneA, to = SceneB) {}
overscroll(SceneB, Orientation.Vertical) { fade(TestElements.Foo) }
}
@@ -1272,7 +1321,8 @@
fun overscroll_releaseBetween0And100Percent_down() = runGestureTest {
// Make scene C overscrollable.
layoutState.transitions = transitions {
- from(SceneA, to = SceneC) { spec = spring(dampingRatio = Spring.DampingRatioNoBouncy) }
+ defaultSwipeSpec = spring(dampingRatio = Spring.DampingRatioNoBouncy)
+ from(SceneA, to = SceneC) {}
overscroll(SceneC, Orientation.Vertical) { fade(TestElements.Foo) }
}
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 ce64628..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
@@ -46,6 +46,8 @@
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
@@ -876,4 +878,37 @@
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/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt
index bde7699..badc43b 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt
@@ -18,8 +18,9 @@
package com.android.compose.nestedscroll
+import androidx.compose.foundation.gestures.Orientation
import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.input.nestedscroll.NestedScrollSource
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource.Companion.UserInput
import androidx.compose.ui.unit.Velocity
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
@@ -35,13 +36,14 @@
private var canStartPostFling = false
private var canContinueScroll = false
private var isStarted = false
- private var lastScroll: Offset? = null
- private var returnOnScroll = Offset.Zero
- private var lastStop: Velocity? = null
- private var returnOnStop = Velocity.Zero
+ private var lastScroll: Float? = null
+ private var returnOnScroll = 0f
+ private var lastStop: Float? = null
+ private var returnOnStop = 0f
private val scrollConnection =
PriorityNestedScrollConnection(
+ orientation = Orientation.Vertical,
canStartPreScroll = { _, _ -> canStartPreScroll },
canStartPostScroll = { _, _ -> canStartPostScroll },
canStartPostFling = { canStartPostFling },
@@ -58,11 +60,6 @@
},
)
- private val offset1 = Offset(1f, 1f)
- private val offset2 = Offset(2f, 2f)
- private val velocity1 = Velocity(1f, 1f)
- private val velocity2 = Velocity(2f, 2f)
-
@Test
fun step1_priorityModeShouldStartOnlyOnPreScroll() = runTest {
canStartPreScroll = true
@@ -70,7 +67,7 @@
scrollConnection.onPostScroll(
consumed = Offset.Zero,
available = Offset.Zero,
- source = NestedScrollSource.Drag,
+ source = UserInput,
)
assertThat(isStarted).isEqualTo(false)
@@ -80,7 +77,7 @@
scrollConnection.onPostFling(consumed = Velocity.Zero, available = Velocity.Zero)
assertThat(isStarted).isEqualTo(false)
- scrollConnection.onPreScroll(available = Offset.Zero, source = NestedScrollSource.Drag)
+ scrollConnection.onPreScroll(available = Offset.Zero, source = UserInput)
assertThat(isStarted).isEqualTo(true)
}
@@ -89,7 +86,7 @@
scrollConnection.onPostScroll(
consumed = Offset.Zero,
available = Offset.Zero,
- source = NestedScrollSource.Drag,
+ source = UserInput,
)
}
@@ -97,7 +94,7 @@
fun step1_priorityModeShouldStartOnlyOnPostScroll() = runTest {
canStartPostScroll = true
- scrollConnection.onPreScroll(available = Offset.Zero, source = NestedScrollSource.Drag)
+ scrollConnection.onPreScroll(available = Offset.Zero, source = UserInput)
assertThat(isStarted).isEqualTo(false)
scrollConnection.onPreFling(available = Velocity.Zero)
@@ -115,7 +112,7 @@
scrollConnection.onPostScroll(
consumed = Offset.Zero,
available = Offset.Zero,
- source = NestedScrollSource.Drag,
+ source = UserInput,
)
assertThat(isStarted).isEqualTo(false)
@@ -128,12 +125,12 @@
canStartPostScroll = true
scrollConnection.onPostScroll(
- consumed = offset1,
- available = offset2,
- source = NestedScrollSource.Drag,
+ consumed = Offset(1f, 1f),
+ available = Offset(2f, 2f),
+ source = UserInput,
)
- assertThat(lastScroll).isEqualTo(offset2)
+ assertThat(lastScroll).isEqualTo(2f)
}
@Test
@@ -141,13 +138,13 @@
startPriorityModePostScroll()
canContinueScroll = true
- scrollConnection.onPreScroll(available = offset1, source = NestedScrollSource.Drag)
- assertThat(lastScroll).isEqualTo(offset1)
+ scrollConnection.onPreScroll(available = Offset(1f, 1f), source = UserInput)
+ assertThat(lastScroll).isEqualTo(1f)
canContinueScroll = false
- scrollConnection.onPreScroll(available = offset2, source = NestedScrollSource.Drag)
- assertThat(lastScroll).isNotEqualTo(offset2)
- assertThat(lastScroll).isEqualTo(offset1)
+ scrollConnection.onPreScroll(available = Offset(2f, 2f), source = UserInput)
+ assertThat(lastScroll).isNotEqualTo(2f)
+ assertThat(lastScroll).isEqualTo(1f)
}
@Test
@@ -155,7 +152,7 @@
startPriorityModePostScroll()
canContinueScroll = false
- scrollConnection.onPreScroll(available = Offset.Zero, source = NestedScrollSource.Drag)
+ scrollConnection.onPreScroll(available = Offset.Zero, source = UserInput)
assertThat(lastStop).isNotNull()
}
@@ -184,22 +181,22 @@
fun receive_onPostFling() = runTest {
canStartPostFling = true
- scrollConnection.onPostFling(consumed = velocity1, available = velocity2)
+ scrollConnection.onPostFling(consumed = Velocity(1f, 1f), available = Velocity(2f, 2f))
- assertThat(lastStop).isEqualTo(velocity2)
+ assertThat(lastStop).isEqualTo(2f)
}
@Test
fun step1_priorityModeShouldStartOnlyOnPostFling() = runTest {
canStartPostFling = true
- scrollConnection.onPreScroll(available = Offset.Zero, source = NestedScrollSource.Drag)
+ scrollConnection.onPreScroll(available = Offset.Zero, source = UserInput)
assertThat(isStarted).isEqualTo(false)
scrollConnection.onPostScroll(
consumed = Offset.Zero,
available = Offset.Zero,
- source = NestedScrollSource.Drag,
+ source = UserInput,
)
assertThat(isStarted).isEqualTo(false)
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/bouncer/ui/composable b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/composable
index 0d369a3..97f2e56 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/composable
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/composable
@@ -67,7 +67,6 @@
import com.android.systemui.scene.shared.model.sceneDataSourceDelegator
import com.android.systemui.scene.ui.composable.Scene
import com.android.systemui.scene.ui.composable.SceneContainer
-import com.android.systemui.settings.displayTracker
import com.android.systemui.testKosmos
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.awaitCancellation
@@ -127,7 +126,7 @@
private val sceneContainerViewModel by lazy {
kosmos.sceneContainerViewModelFactory
- .create(view, kosmos.displayTracker.defaultDisplayId, {})
+ .create(view) {}
.apply { setTransitionState(transitionState) }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelTest.kt
index 58b59ff..755c4eb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelTest.kt
@@ -85,7 +85,8 @@
assertThat(actions).isNotEmpty()
assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home))
assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer))
- assertThat(actions?.get(Swipe.Down)).isEqualTo(UserActionResult(Scenes.Shade))
+ assertThat(actions?.get(Swipe.Down))
+ .isEqualTo(UserActionResult(Scenes.Shade, isIrreversible = true))
setUpState(
isShadeTouchable = false,
@@ -102,7 +103,8 @@
assertThat(actions).isNotEmpty()
assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home))
assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone))
- assertThat(actions?.get(Swipe.Down)).isEqualTo(UserActionResult(Scenes.Shade))
+ assertThat(actions?.get(Swipe.Down))
+ .isEqualTo(UserActionResult(Scenes.Shade, isIrreversible = true))
}
@Test
@@ -120,7 +122,7 @@
assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home))
assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer))
assertThat(actions?.get(Swipe.Down))
- .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade))
+ .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade, isIrreversible = true))
setUpState(
isShadeTouchable = false,
@@ -138,7 +140,7 @@
assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home))
assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone))
assertThat(actions?.get(Swipe.Down))
- .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade))
+ .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade, isIrreversible = true))
}
@Test
@@ -156,7 +158,9 @@
assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home))
assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer))
assertThat(actions?.get(Swipe.Down))
- .isEqualTo(UserActionResult(Overlays.NotificationsShade))
+ .isEqualTo(
+ UserActionResult.ShowOverlay(Overlays.NotificationsShade, isIrreversible = true)
+ )
setUpState(
isShadeTouchable = false,
@@ -170,7 +174,9 @@
assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home))
assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone))
assertThat(actions?.get(Swipe.Down))
- .isEqualTo(UserActionResult(Overlays.NotificationsShade))
+ .isEqualTo(
+ UserActionResult.ShowOverlay(Overlays.NotificationsShade, isIrreversible = true)
+ )
}
private fun TestScope.setUpState(
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..e1946fc
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModelTest.kt
@@ -0,0 +1,323 @@
+/*
+ * 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(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/domain/interactor/LightRevealScrimInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
index 073ed61..b6ec6a6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
@@ -21,16 +21,23 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectValues
import com.android.systemui.keyguard.data.fakeLightRevealScrimRepository
+import com.android.systemui.keyguard.data.repository.DEFAULT_REVEAL_DURATION
import com.android.systemui.keyguard.data.repository.DEFAULT_REVEAL_EFFECT
import com.android.systemui.keyguard.data.repository.FakeLightRevealScrimRepository
+import com.android.systemui.keyguard.data.repository.FakeLightRevealScrimRepository.RevealAnimatorRequest
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.data.repository.fakePowerRepository
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.power.shared.model.WakeSleepReason
+import com.android.systemui.power.shared.model.WakefulnessState
import com.android.systemui.statusbar.LightRevealEffect
import com.android.systemui.statusbar.LightRevealScrim
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
@@ -52,6 +59,8 @@
private val fakeKeyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ private val fakePowerRepository = kosmos.fakePowerRepository
+
private val underTest = kosmos.lightRevealScrimInteractor
private val reveal1 =
@@ -107,4 +116,50 @@
runCurrent()
assertEquals(listOf(DEFAULT_REVEAL_EFFECT, reveal1, reveal2), values)
}
+
+ @Test
+ fun transitionToAod_folding_doesNotAnimateTheScrim() =
+ kosmos.testScope.runTest {
+ updateWakefulness(goToSleepReason = WakeSleepReason.FOLD)
+ runCurrent()
+
+ // Transition to AOD
+ fakeKeyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(to = KeyguardState.AOD, transitionState = TransitionState.STARTED)
+ )
+ runCurrent()
+
+ assertThat(fakeLightRevealScrimRepository.revealAnimatorRequests.last())
+ .isEqualTo(RevealAnimatorRequest(reveal = false, duration = 0))
+ }
+
+ @Test
+ fun transitionToAod_powerButton_animatesTheScrim() =
+ kosmos.testScope.runTest {
+ updateWakefulness(goToSleepReason = WakeSleepReason.POWER_BUTTON)
+ runCurrent()
+
+ // Transition to AOD
+ fakeKeyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(to = KeyguardState.AOD, transitionState = TransitionState.STARTED)
+ )
+ runCurrent()
+
+ assertThat(fakeLightRevealScrimRepository.revealAnimatorRequests.last())
+ .isEqualTo(
+ RevealAnimatorRequest(
+ reveal = false,
+ duration = DEFAULT_REVEAL_DURATION
+ )
+ )
+ }
+
+ private fun updateWakefulness(goToSleepReason: WakeSleepReason) {
+ fakePowerRepository.updateWakefulness(
+ rawState = WakefulnessState.STARTING_TO_SLEEP,
+ lastWakeReason = WakeSleepReason.POWER_BUTTON,
+ lastSleepReason = goToSleepReason,
+ powerButtonLaunchGestureTriggered = false
+ )
+ }
}
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/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt
index fb1bf28..6397979 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt
@@ -303,7 +303,8 @@
// Top edge is not applicable in dual shade, as well as two-finger swipe.
assertThat(downDestination).isNull()
} else {
- assertThat(downDestination).isEqualTo(ShowOverlay(Overlays.NotificationsShade))
+ assertThat(downDestination)
+ .isEqualTo(ShowOverlay(Overlays.NotificationsShade, isIrreversible = true))
assertThat(downDestination?.transitionKey).isNull()
}
@@ -320,7 +321,7 @@
downWithTwoPointers -> assertThat(downFromTopRightDestination).isNull()
else -> {
assertThat(downFromTopRightDestination)
- .isEqualTo(ShowOverlay(Overlays.QuickSettingsShade))
+ .isEqualTo(ShowOverlay(Overlays.QuickSettingsShade, isIrreversible = true))
assertThat(downFromTopRightDestination?.transitionKey).isNull()
}
}
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/GoneUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModelTest.kt
index 5c47f55..47fae9f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModelTest.kt
@@ -106,7 +106,7 @@
runCurrent()
assertThat(userActions?.get(swipeDownFromTopWithTwoFingers()))
- .isEqualTo(UserActionResult(Scenes.QuickSettings))
+ .isEqualTo(UserActionResult(Scenes.QuickSettings, isIrreversible = true))
}
@Test
@@ -118,7 +118,7 @@
runCurrent()
assertThat(userActions?.get(swipeDownFromTopWithTwoFingers()))
- .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade))
+ .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade, isIrreversible = true))
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerGestureFilterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerGestureFilterTest.kt
deleted file mode 100644
index efde1ec..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerGestureFilterTest.kt
+++ /dev/null
@@ -1,112 +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.scene.ui.viewmodel
-
-import android.graphics.Region
-import android.view.setSystemGestureExclusionRegion
-import androidx.compose.ui.geometry.Offset
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.lifecycle.activateIn
-import com.android.systemui.scene.sceneContainerGestureFilterFactory
-import com.android.systemui.settings.displayTracker
-import com.android.systemui.testKosmos
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class SceneContainerGestureFilterTest : SysuiTestCase() {
-
- private val kosmos = testKosmos()
- private val testScope = kosmos.testScope
- private val displayId = kosmos.displayTracker.defaultDisplayId
-
- private val underTest = kosmos.sceneContainerGestureFilterFactory.create(displayId)
- private val activationJob = Job()
-
- @Test
- fun shouldFilterGesture_whenNoRegion_returnsFalse() =
- testScope.runTest {
- activate()
- setSystemGestureExclusionRegion(displayId, null)
- runCurrent()
-
- assertThat(underTest.shouldFilterGesture(Offset(100f, 100f))).isFalse()
- }
-
- @Test
- fun shouldFilterGesture_whenOutsideRegion_returnsFalse() =
- testScope.runTest {
- activate()
- setSystemGestureExclusionRegion(displayId, Region(0, 0, 200, 200))
- runCurrent()
-
- assertThat(underTest.shouldFilterGesture(Offset(300f, 100f))).isFalse()
- }
-
- @Test
- fun shouldFilterGesture_whenInsideRegion_returnsTrue() =
- testScope.runTest {
- activate()
- setSystemGestureExclusionRegion(displayId, Region(0, 0, 200, 200))
- runCurrent()
-
- assertThat(underTest.shouldFilterGesture(Offset(100f, 100f))).isTrue()
- }
-
- @Test(expected = IllegalStateException::class)
- fun shouldFilterGesture_beforeActivation_throws() =
- testScope.runTest {
- setSystemGestureExclusionRegion(displayId, Region(0, 0, 200, 200))
- runCurrent()
-
- underTest.shouldFilterGesture(Offset(100f, 100f))
- }
-
- @Test(expected = IllegalStateException::class)
- fun shouldFilterGesture_afterCancellation_throws() =
- testScope.runTest {
- activate()
- setSystemGestureExclusionRegion(displayId, Region(0, 0, 200, 200))
- runCurrent()
-
- cancel()
-
- underTest.shouldFilterGesture(Offset(100f, 100f))
- }
-
- private fun TestScope.activate() {
- underTest.activateIn(testScope, activationJob)
- runCurrent()
- }
-
- private fun TestScope.cancel() {
- activationJob.cancel()
- runCurrent()
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
index a37f511..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
@@ -39,7 +39,6 @@
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.fakeSceneDataSource
-import com.android.systemui.settings.displayTracker
import com.android.systemui.shade.data.repository.fakeShadeRepository
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.shared.flag.DualShade
@@ -82,7 +81,6 @@
underTest =
kosmos.sceneContainerViewModelFactory.create(
view,
- kosmos.displayTracker.defaultDisplayId,
{ motionEventHandler ->
this@SceneContainerViewModelTest.motionEventHandler = motionEventHandler
},
@@ -178,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/shade/ui/viewmodel/ShadeUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModelTest.kt
index 15d6881..fcb366b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModelTest.kt
@@ -29,6 +29,7 @@
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.flags.EnableSceneContainer
@@ -50,6 +51,7 @@
import com.android.systemui.shade.shared.flag.DualShade
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.TestScope
@@ -248,6 +250,27 @@
assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Communal))
}
+ @Test
+ fun upTransitionSceneKey_neverGoesBackToShadeScene() =
+ testScope.runTest {
+ val actions by collectValues(underTest.actions)
+ val currentScene by collectLastValue(kosmos.sceneInteractor.currentScene)
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+ kosmos.sceneInteractor.changeScene(Scenes.Shade, "")
+ assertThat(currentScene).isEqualTo(Scenes.Shade)
+
+ kosmos.sceneInteractor.changeScene(Scenes.QuickSettings, "")
+ assertThat(currentScene).isEqualTo(Scenes.QuickSettings)
+
+ actions.forEachIndexed { index, map ->
+ assertWithMessage(
+ "Actions on index $index is incorrectly mapping back to the Shade scene!"
+ )
+ .that((map[Swipe.Up] as? UserActionResult.ChangeScene)?.toScene)
+ .isNotEqualTo(Scenes.Shade)
+ }
+ }
+
private fun TestScope.setDeviceEntered(isEntered: Boolean) {
if (isEntered) {
// Unlock the device marking the device has entered.
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/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
index a0f6431..9d93a9c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
@@ -61,7 +61,7 @@
interactor,
kosmos.testDispatcher,
mockDialogDelegate,
- mockDialogEventLogger
+ mockDialogEventLogger,
)
@Test
@@ -97,7 +97,7 @@
assertThat(tiles?.size).isEqualTo(3)
with(tiles?.elementAt(0)!!) {
assertThat(this.text).isEqualTo("Disabled by other")
- assertThat(this.subtext).isEqualTo("Set up")
+ assertThat(this.subtext).isEqualTo("Not set")
assertThat(this.enabled).isEqualTo(false)
}
with(tiles?.elementAt(1)!!) {
@@ -323,10 +323,10 @@
assertThat(tiles!!).hasSize(6)
assertThat(tiles!![0].subtext).isEqualTo("When the going gets tough")
assertThat(tiles!![1].subtext).isEqualTo("On • When in Rome")
- assertThat(tiles!![2].subtext).isEqualTo("Set up")
+ assertThat(tiles!![2].subtext).isEqualTo("Not set")
assertThat(tiles!![3].subtext).isEqualTo("Off")
assertThat(tiles!![4].subtext).isEqualTo("On")
- assertThat(tiles!![5].subtext).isEqualTo("Set up")
+ assertThat(tiles!![5].subtext).isEqualTo("Not set")
}
@Test
@@ -387,7 +387,7 @@
}
with(tiles?.elementAt(2)!!) {
assertThat(this.stateDescription).isEqualTo("Off")
- assertThat(this.subtextDescription).isEqualTo("Set up")
+ assertThat(this.subtextDescription).isEqualTo("Not set")
}
with(tiles?.elementAt(3)!!) {
assertThat(this.stateDescription).isEqualTo("Off")
@@ -399,7 +399,7 @@
}
with(tiles?.elementAt(5)!!) {
assertThat(this.stateDescription).isEqualTo("Off")
- assertThat(this.subtextDescription).isEqualTo("Set up")
+ assertThat(this.subtextDescription).isEqualTo("Not set")
}
// All tiles have the same long click info
@@ -451,7 +451,7 @@
.setName("Active without manual")
.setActive(true)
.setManualInvocationAllowed(false)
- .build(),
+ .build()
)
)
runCurrent()
@@ -492,7 +492,7 @@
.setId("ID")
.setName("Disabled by other")
.setEnabled(false, /* byUser= */ false)
- .build(),
+ .build()
)
)
runCurrent()
@@ -500,7 +500,7 @@
assertThat(tiles?.size).isEqualTo(1)
with(tiles?.elementAt(0)!!) {
assertThat(this.text).isEqualTo("Disabled by other")
- assertThat(this.subtext).isEqualTo("Set up")
+ assertThat(this.subtext).isEqualTo("Not set")
assertThat(this.enabled).isEqualTo(false)
// Click the tile
@@ -519,7 +519,7 @@
// Check that nothing happened to the tile
with(tiles?.elementAt(0)!!) {
assertThat(this.text).isEqualTo("Disabled by other")
- assertThat(this.subtext).isEqualTo("Set up")
+ assertThat(this.subtext).isEqualTo("Not set")
assertThat(this.enabled).isEqualTo(false)
}
}
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 c76b35f..96a85d7 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1120,7 +1120,7 @@
<string name="zen_mode_off">Off</string>
<!-- Modes: label for a mode that needs to be set up [CHAR LIMIT=35] -->
- <string name="zen_mode_set_up">Set up</string>
+ <string name="zen_mode_set_up">Not set</string>
<!-- Modes: label for a mode that cannot be manually turned on [CHAR LIMIT=35] -->
<string name="zen_mode_no_manual_invocation">Manage in settings</string>
@@ -1827,6 +1827,12 @@
<!-- Name of the alarm status bar icon. -->
<string name="status_bar_alarm">Alarm</string>
+ <!-- Format string for the content description of the icon that indicates that a Mode is on.
+ For example, if the mode name is Bedtime, this will be "Bedtime is on". This content
+ description will be associated to the mode icon in status bar, smartspace, and everyone else
+ where it might be displayed without text. [CHAR LIMIT=NONE] -->
+ <string name="active_mode_content_description"><xliff:g id="modeName" example="Do Not Disturb">%1$s</xliff:g> is on</string>
+
<!-- Wallet strings -->
<!-- Wallet empty state, title [CHAR LIMIT=32] -->
<string name="wallet_title">Wallet</string>
@@ -3746,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] -->
@@ -3757,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] -->
@@ -3784,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/ui/viewmodel/ResizeableItemFrameViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModel.kt
new file mode 100644
index 0000000..7aad33d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModel.kt
@@ -0,0 +1,204 @@
+/*
+ * 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.dropWhile
+import kotlinx.coroutines.flow.filterNotNull
+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,
+)
+
+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) },
+ )
+ .dropWhile { 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,
+ ) {
+ 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 (!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
+ .filterNotNull()
+ .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/complication/dagger/DreamClockTimeComplicationComponent.kt b/packages/SystemUI/src/com/android/systemui/complication/dagger/DreamClockTimeComplicationComponent.kt
index 099e3fc..4b9ac1d 100644
--- a/packages/SystemUI/src/com/android/systemui/complication/dagger/DreamClockTimeComplicationComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/complication/dagger/DreamClockTimeComplicationComponent.kt
@@ -21,9 +21,10 @@
import android.view.View
import android.widget.TextClock
import com.android.internal.util.Preconditions
-import com.android.systemui.res.R
+import com.android.systemui.Flags
import com.android.systemui.complication.DreamClockTimeComplication
import com.android.systemui.complication.DreamClockTimeComplication.DreamClockTimeViewHolder
+import com.android.systemui.res.R
import dagger.Module
import dagger.Provides
import dagger.Subcomponent
@@ -71,9 +72,13 @@
/* root = */ null,
/* attachToRoot = */ false,
) as TextClock,
- "R.layout.dream_overlay_complication_clock_time did not properly inflate"
+ "R.layout.dream_overlay_complication_clock_time did not properly inflate",
)
- view.setFontVariationSettings(TAG_WEIGHT + WEIGHT)
+ if (Flags.dreamOverlayUpdatedFont()) {
+ view.setFontVariationSettings("'wght' 600, 'opsz' 96")
+ } else {
+ view.setFontVariationSettings(TAG_WEIGHT + WEIGHT)
+ }
return view
}
}
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/domain/interactor/LightRevealScrimInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
index 9443570..1497026 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
@@ -19,10 +19,12 @@
import com.android.keyguard.logging.ScrimLogger
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.DEFAULT_REVEAL_DURATION
import com.android.systemui.keyguard.data.repository.LightRevealScrimRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.power.shared.model.ScreenPowerState
+import com.android.systemui.power.shared.model.WakeSleepReason
import com.android.systemui.statusbar.LightRevealEffect
import com.android.systemui.util.kotlin.sample
import dagger.Lazy
@@ -50,11 +52,29 @@
scope.launch {
transitionInteractor.startedKeyguardTransitionStep.collect {
scrimLogger.d(TAG, "listenForStartedKeyguardTransitionStep", it)
- lightRevealScrimRepository.startRevealAmountAnimator(willBeRevealedInState(it.to))
+ val animationDuration =
+ if (it.to == KeyguardState.AOD && isLastSleepDueToFold) {
+ // Do not animate the scrim when folding as we want to cover the screen
+ // with the scrim immediately while displays are switching.
+ // This is needed to play the fold to AOD animation which starts with
+ // fully black screen (see FoldAodAnimationController)
+ 0L
+ } else {
+ DEFAULT_REVEAL_DURATION
+ }
+
+ lightRevealScrimRepository.startRevealAmountAnimator(
+ willBeRevealedInState(it.to),
+ duration = animationDuration
+ )
}
}
}
+ private val isLastSleepDueToFold: Boolean
+ get() = powerInteractor.get().detailedWakefulness.value
+ .lastSleepReason == WakeSleepReason.FOLD
+
/**
* Whenever a keyguard transition starts, sample the latest reveal effect from the repository
* and use that for the starting transition.
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/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
index c0b9efaa..914730e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
@@ -25,7 +25,7 @@
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.scene.ui.composable.transitions.FROM_LOCK_SCREEN_TO_BOUNCER_FADE_FRACTION
+import com.android.systemui.scene.ui.composable.transitions.TO_BOUNCER_FADE_FRACTION
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -49,14 +49,12 @@
duration = FromLockscreenTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION,
edge = Edge.create(from = LOCKSCREEN, to = Scenes.Bouncer),
)
- .setupWithoutSceneContainer(
- edge = Edge.create(from = LOCKSCREEN, to = PRIMARY_BOUNCER),
- )
+ .setupWithoutSceneContainer(edge = Edge.create(from = LOCKSCREEN, to = PRIMARY_BOUNCER))
private val alphaForAnimationStep: (Float) -> Float =
when {
SceneContainerFlag.isEnabled -> { step ->
- 1f - Math.min((step / FROM_LOCK_SCREEN_TO_BOUNCER_FADE_FRACTION), 1f)
+ 1f - Math.min((step / TO_BOUNCER_FADE_FRACTION), 1f)
}
else -> { step -> 1f - step }
}
@@ -64,7 +62,7 @@
val shortcutsAlpha: Flow<Float> =
transitionAnimation.sharedFlow(
duration = FromLockscreenTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION,
- onStep = alphaForAnimationStep
+ onStep = alphaForAnimationStep,
)
val lockscreenAlpha: Flow<Float> = shortcutsAlpha
@@ -76,8 +74,8 @@
duration = 250.milliseconds,
onStep = { 1f - it },
onCancel = { 0f },
- onFinish = { 0f }
+ onFinish = { 0f },
),
- flowWhenShadeIsExpanded = transitionAnimation.immediatelyTransitionTo(0f)
+ flowWhenShadeIsExpanded = transitionAnimation.immediatelyTransitionTo(0f),
)
}
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/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index a4fe4e3..ad76b4f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -169,50 +169,34 @@
private void enableZenMode(@Nullable Expandable expandable) {
int zenDuration = mSettingZenDuration.getValue();
- boolean showOnboarding = Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 0) != 0
- && Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.ZEN_SETTINGS_UPDATED, 0) != 1;
- if (showOnboarding) {
- // don't show on-boarding again or notification ever
- Settings.Secure.putInt(mContext.getContentResolver(),
- Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 0);
- // turn on DND
- mController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG);
- // show on-boarding screen
- Intent intent = new Intent(Settings.ZEN_MODE_ONBOARDING);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
- mActivityStarter.postStartActivityDismissingKeyguard(intent, 0);
- } else {
- switch (zenDuration) {
- case Settings.Secure.ZEN_DURATION_PROMPT:
- mUiHandler.post(() -> {
- Dialog dialog = makeZenModeDialog();
- if (expandable != null) {
- DialogTransitionAnimator.Controller controller =
- expandable.dialogTransitionController(new DialogCuj(
- InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
- INTERACTION_JANK_TAG));
- if (controller != null) {
- mDialogTransitionAnimator.show(dialog,
- controller, /* animateBackgroundBoundsChange= */ false);
- } else {
- dialog.show();
- }
+ switch (zenDuration) {
+ case Settings.Secure.ZEN_DURATION_PROMPT:
+ mUiHandler.post(() -> {
+ Dialog dialog = makeZenModeDialog();
+ if (expandable != null) {
+ DialogTransitionAnimator.Controller controller =
+ expandable.dialogTransitionController(new DialogCuj(
+ InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
+ INTERACTION_JANK_TAG));
+ if (controller != null) {
+ mDialogTransitionAnimator.show(dialog,
+ controller, /* animateBackgroundBoundsChange= */ false);
} else {
dialog.show();
}
- });
- break;
- case Settings.Secure.ZEN_DURATION_FOREVER:
- mController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG);
- break;
- default:
- Uri conditionId = ZenModeConfig.toTimeCondition(mContext, zenDuration,
- mHost.getUserId(), true).id;
- mController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
- conditionId, TAG);
- }
+ } else {
+ dialog.show();
+ }
+ });
+ break;
+ case Settings.Secure.ZEN_DURATION_FOREVER:
+ mController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG);
+ break;
+ default:
+ Uri conditionId = ZenModeConfig.toTimeCondition(mContext, zenDuration,
+ mHost.getUserId(), true).id;
+ mController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ conditionId, TAG);
}
}
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/data/repository/SystemGestureExclusionRepository.kt b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepository.kt
deleted file mode 100644
index a8d0777..0000000
--- a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepository.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.scene.data.repository
-
-import android.graphics.Region
-import android.view.ISystemGestureExclusionListener
-import android.view.IWindowManager
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
-import javax.inject.Inject
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
-
-@SysUISingleton
-class SystemGestureExclusionRepository
-@Inject
-constructor(private val windowManager: IWindowManager) {
-
- /**
- * Returns [Flow] of the [Region] in which system gestures should be excluded on the display
- * identified with [displayId].
- */
- fun exclusionRegion(displayId: Int): Flow<Region?> {
- return conflatedCallbackFlow {
- val listener =
- object : ISystemGestureExclusionListener.Stub() {
- override fun onSystemGestureExclusionChanged(
- displayId: Int,
- restrictedRegion: Region?,
- unrestrictedRegion: Region?,
- ) {
- trySend(restrictedRegion)
- }
- }
- windowManager.registerSystemGestureExclusionListener(listener, displayId)
-
- awaitClose {
- windowManager.unregisterSystemGestureExclusionListener(listener, displayId)
- }
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SystemGestureExclusionInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SystemGestureExclusionInteractor.kt
deleted file mode 100644
index 4cee874..0000000
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SystemGestureExclusionInteractor.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.scene.domain.interactor
-
-import android.graphics.Region
-import com.android.systemui.scene.data.repository.SystemGestureExclusionRepository
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-
-class SystemGestureExclusionInteractor
-@Inject
-constructor(private val repository: SystemGestureExclusionRepository) {
-
- /**
- * Returns [Flow] of the [Region] in which system gestures should be excluded on the display
- * identified with [displayId].
- */
- fun exclusionRegion(displayId: Int): Flow<Region?> {
- return repository.exclusionRegion(displayId)
- }
-}
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/SceneWindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
index a8be580..38f4e73 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
@@ -107,13 +107,7 @@
view.viewModel(
traceName = "SceneWindowRootViewBinder",
minWindowLifecycleState = WindowLifecycleState.ATTACHED,
- factory = {
- viewModelFactory.create(
- view,
- view.context.displayId,
- motionEventHandlerReceiver,
- )
- },
+ factory = { viewModelFactory.create(view, motionEventHandlerReceiver) },
) { viewModel ->
try {
view.setViewTreeOnBackPressedDispatcherOwner(
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/scene/ui/viewmodel/SceneContainerGestureFilter.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerGestureFilter.kt
deleted file mode 100644
index a1d915a..0000000
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerGestureFilter.kt
+++ /dev/null
@@ -1,65 +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.scene.ui.viewmodel
-
-import androidx.compose.runtime.getValue
-import androidx.compose.ui.geometry.Offset
-import com.android.systemui.lifecycle.ExclusiveActivatable
-import com.android.systemui.lifecycle.Hydrator
-import com.android.systemui.scene.domain.interactor.SystemGestureExclusionInteractor
-import dagger.assisted.Assisted
-import dagger.assisted.AssistedFactory
-import dagger.assisted.AssistedInject
-import kotlin.math.roundToInt
-
-/** Decides whether drag gestures should be filtered out in the scene container framework. */
-class SceneContainerGestureFilter
-@AssistedInject
-constructor(interactor: SystemGestureExclusionInteractor, @Assisted displayId: Int) :
- ExclusiveActivatable() {
-
- private val hydrator = Hydrator("SceneContainerGestureFilter.hydrator")
- private val exclusionRegion by
- hydrator.hydratedStateOf(
- traceName = "exclusionRegion",
- initialValue = null,
- source = interactor.exclusionRegion(displayId),
- )
-
- override suspend fun onActivated(): Nothing {
- hydrator.activate()
- }
-
- /**
- * Returns `true` if a drag gesture starting at [startPosition] should be filtered out (e.g.
- * ignored, `false` otherwise.
- *
- * Invoke this and pass in the position of the `ACTION_DOWN` pointer event that began the
- * gesture.
- */
- fun shouldFilterGesture(startPosition: Offset): Boolean {
- check(isActive) { "Must be activated to use!" }
-
- return exclusionRegion?.contains(startPosition.x.roundToInt(), startPosition.y.roundToInt())
- ?: false
- }
-
- @AssistedFactory
- interface Factory {
- fun create(displayId: Int): SceneContainerGestureFilter
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
index f505385..889380a 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
@@ -19,7 +19,6 @@
import android.view.MotionEvent
import android.view.View
import androidx.compose.runtime.getValue
-import androidx.compose.ui.geometry.Offset
import com.android.app.tracing.coroutines.launch
import com.android.compose.animation.scene.ContentKey
import com.android.compose.animation.scene.DefaultEdgeDetector
@@ -61,10 +60,8 @@
shadeInteractor: ShadeInteractor,
private val splitEdgeDetector: SplitEdgeDetector,
private val logger: SceneLogger,
- gestureFilterFactory: SceneContainerGestureFilter.Factory,
hapticsViewModelFactory: SceneContainerHapticsViewModel.Factory,
@Assisted view: View,
- @Assisted displayId: Int,
@Assisted private val motionEventHandlerReceiver: (MotionEventHandler?) -> Unit,
) : ExclusiveActivatable() {
@@ -92,8 +89,6 @@
},
)
- private val gestureFilter: SceneContainerGestureFilter = gestureFilterFactory.create(displayId)
-
override suspend fun onActivated(): Nothing {
try {
// Sends a MotionEventHandler to the owner of the view-model so they can report
@@ -112,7 +107,6 @@
coroutineScope {
launch { hydrator.activate() }
- launch { gestureFilter.activate() }
launch("SceneContainerHapticsViewModel") { hapticsViewModel.activate() }
}
awaitCancellation()
@@ -262,17 +256,6 @@
}
}
- /**
- * Returns `true` if a drag gesture starting at [startPosition] should be filtered out (e.g.
- * ignored, `false` otherwise.
- *
- * Invoke this and pass in the position of the `ACTION_DOWN` pointer event that began the
- * gesture.
- */
- fun shouldFilterGesture(startPosition: Offset): Boolean {
- return gestureFilter.shouldFilterGesture(startPosition)
- }
-
/** Defines interface for classes that can handle externally-reported [MotionEvent]s. */
interface MotionEventHandler {
/** Notifies that a [MotionEvent] has occurred. */
@@ -289,7 +272,6 @@
interface Factory {
fun create(
view: View,
- displayId: Int,
motionEventHandlerReceiver: (MotionEventHandler?) -> Unit,
): SceneContainerViewModel
}
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/shade/ui/viewmodel/ShadeUserActions.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActions.kt
index 65b6231..e5f6846 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActions.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActions.kt
@@ -30,41 +30,47 @@
fun singleShadeActions(
requireTwoPointersForTopEdgeForQs: Boolean = false
): Array<Pair<UserAction, UserActionResult>> {
+ val shadeUserActionResult = UserActionResult(Scenes.Shade, isIrreversible = true)
+ val qsSceneUserActionResult = UserActionResult(Scenes.QuickSettings, isIrreversible = true)
return arrayOf(
// Swiping down, not from the edge, always goes to shade.
- Swipe.Down to Scenes.Shade,
- swipeDown(pointerCount = 2) to Scenes.Shade,
+ Swipe.Down to shadeUserActionResult,
+ swipeDown(pointerCount = 2) to shadeUserActionResult,
// Swiping down from the top edge.
swipeDownFromTop(pointerCount = 1) to
if (requireTwoPointersForTopEdgeForQs) {
- Scenes.Shade
+ shadeUserActionResult
} else {
- Scenes.QuickSettings
+ qsSceneUserActionResult
},
- swipeDownFromTop(pointerCount = 2) to Scenes.QuickSettings,
+ swipeDownFromTop(pointerCount = 2) to qsSceneUserActionResult,
)
}
/** Returns collection of [UserAction] to [UserActionResult] pairs for opening the split shade. */
fun splitShadeActions(): Array<Pair<UserAction, UserActionResult>> {
- val splitShadeSceneKey = UserActionResult(Scenes.Shade, ToSplitShade)
+ val shadeUserActionResult = UserActionResult(Scenes.Shade, ToSplitShade, isIrreversible = true)
return arrayOf(
// Swiping down, not from the edge, always goes to shade.
- Swipe.Down to splitShadeSceneKey,
- swipeDown(pointerCount = 2) to splitShadeSceneKey,
+ Swipe.Down to shadeUserActionResult,
+ swipeDown(pointerCount = 2) to shadeUserActionResult,
// Swiping down from the top edge goes to QS.
- swipeDownFromTop(pointerCount = 1) to splitShadeSceneKey,
- swipeDownFromTop(pointerCount = 2) to splitShadeSceneKey,
+ swipeDownFromTop(pointerCount = 1) to shadeUserActionResult,
+ swipeDownFromTop(pointerCount = 2) to shadeUserActionResult,
)
}
/** Returns collection of [UserAction] to [UserActionResult] pairs for opening the dual shade. */
fun dualShadeActions(): Array<Pair<UserAction, UserActionResult>> {
+ val notifShadeUserActionResult =
+ UserActionResult.ShowOverlay(Overlays.NotificationsShade, isIrreversible = true)
+ val qsShadeuserActionResult =
+ UserActionResult.ShowOverlay(Overlays.QuickSettingsShade, isIrreversible = true)
return arrayOf(
- Swipe.Down to Overlays.NotificationsShade,
+ Swipe.Down to notifShadeUserActionResult,
Swipe(direction = SwipeDirection.Down, fromSource = SceneContainerEdge.TopRight) to
- Overlays.QuickSettingsShade,
+ qsShadeuserActionResult,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModel.kt
index cc6e8c2..3113dc4 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModel.kt
@@ -32,6 +32,7 @@
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filter
/**
* Models the UI state for the user actions that the user can perform to navigate to other scenes.
@@ -50,7 +51,9 @@
combine(
shadeInteractor.shadeMode,
qsSceneAdapter.isCustomizerShowing,
- sceneBackInteractor.backScene.map { it ?: SceneFamilies.Home },
+ sceneBackInteractor.backScene
+ .filter { it != Scenes.Shade }
+ .map { it ?: SceneFamilies.Home },
) { shadeMode, isCustomizerShowing, backScene ->
buildMap<UserAction, UserActionResult> {
if (!isCustomizerShowing) {
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/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
index e3c47a4..321593b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
@@ -596,7 +596,8 @@
// NetworkCapabilities, but we need to convert it into TRANSPORT_WIFI in order to
// distinguish it from VCN over Cellular.
if (transportTypes[i] == NetworkCapabilities.TRANSPORT_CELLULAR
- && Utils.tryGetWifiInfoForVcn(networkCapabilities) != null) {
+ && Utils.tryGetWifiInfoForVcn(mConnectivityManager, networkCapabilities)
+ != null) {
transportTypes[i] = NetworkCapabilities.TRANSPORT_WIFI;
break;
}
@@ -1112,7 +1113,9 @@
continue;
}
if (transportType == NetworkCapabilities.TRANSPORT_CELLULAR
- && Utils.tryGetWifiInfoForVcn(mLastDefaultNetworkCapabilities) != null) {
+ && Utils.tryGetWifiInfoForVcn(
+ mConnectivityManager, mLastDefaultNetworkCapabilities)
+ != null) {
mConnectedTransports.set(NetworkCapabilities.TRANSPORT_WIFI);
if (mLastDefaultNetworkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)) {
mValidatedTransports.set(NetworkCapabilities.TRANSPORT_WIFI);
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/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index 8c80fd4..36e3e92 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -203,13 +203,16 @@
messagingStyle = mConversationProcessor
.processNotification(entry, builder, mLogger);
}
- result.mInflatedSingleLineViewModel = SingleLineViewInflater
+ SingleLineViewModel viewModel = SingleLineViewInflater
.inflateSingleLineViewModel(
entry.getSbn().getNotification(),
messagingStyle,
builder,
row.getContext()
);
+ // If the messagingStyle is null, we want to inflate the normal view
+ isConversation = viewModel.isConversation();
+ result.mInflatedSingleLineViewModel = viewModel;
result.mInflatedSingleLineView =
SingleLineViewInflater.inflatePrivateSingleLineView(
isConversation,
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/SingleLineViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/SingleLineViewBinder.kt
index 3b0f1ee..a17197c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/SingleLineViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/SingleLineViewBinder.kt
@@ -24,14 +24,14 @@
object SingleLineViewBinder {
@JvmStatic
fun bind(viewModel: SingleLineViewModel?, view: HybridNotificationView?) {
- if (viewModel?.isConversation() == true && view is HybridConversationNotificationView) {
+ if (view is HybridConversationNotificationView) {
if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) return
- viewModel.conversationData?.avatar?.let { view.setAvatar(it) }
+ viewModel?.conversationData?.avatar?.let { view.setAvatar(it) }
view.setText(
- viewModel.titleText,
- viewModel.contentText,
- viewModel.conversationData?.conversationSenderName
+ viewModel?.titleText,
+ viewModel?.contentText,
+ viewModel?.conversationData?.conversationSenderName,
)
} else {
// bind the title and content text views
@@ -39,7 +39,7 @@
bind(
/* title = */ viewModel?.titleText,
/* text = */ viewModel?.contentText,
- /* contentView = */ null
+ /* contentView = */ null,
)
}
}
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/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 8c03538..f8eae36 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -411,7 +411,8 @@
mainActiveMode.getIcon().key().resPackage(),
mainActiveMode.getIcon().key().resId(),
mainActiveMode.getIcon().drawable(),
- mainActiveMode.getName(),
+ mResources.getString(R.string.active_mode_content_description,
+ mainActiveMode.getName()),
StatusBarIcon.Shape.FIXED_SPACE);
}
if (visible != mZenVisible) {
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/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
index 6764839c..4f595ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
@@ -76,7 +76,7 @@
// can be manually toggled on
mode.rule.isEnabled -> mode.isActive || mode.rule.isManualInvocationAllowed
// Mode was created as disabled, or disabled by the app that owns it ->
- // will be shown with a "Set up" text
+ // will be shown with a "Not set" text
!mode.rule.isEnabled -> mode.status == ZenMode.Status.DISABLED_BY_OTHER
else -> false
}
@@ -120,7 +120,7 @@
},
onLongClick = { openSettings(mode) },
onLongClickLabel =
- context.resources.getString(R.string.accessibility_long_click_tile)
+ context.resources.getString(R.string.accessibility_long_click_tile),
)
}
}
@@ -137,10 +137,10 @@
/**
* Returns a description of the mode, which is:
- * * a prompt to set up the mode if it is not enabled
- * * if it cannot be manually activated, text that says so
- * * otherwise, the trigger description of the mode if it exists...
- * * ...or null if it doesn't
+ * * a prompt to set up the mode if it is not enabled
+ * * if it cannot be manually activated, text that says so
+ * * otherwise, the trigger description of the mode if it exists...
+ * * ...or null if it doesn't
*
* This description is used directly for the content description of a mode tile for screen
* readers, and for the tile subtext will be augmented with the current status of the mode.
@@ -174,7 +174,7 @@
context,
R.style.Theme_SystemUI_Dialog,
/* cancelIsNeutral= */ true,
- zenDialogMetricsLogger
+ zenDialogMetricsLogger,
)
.createDialog()
SystemUIDialog.applyFlags(dialog)
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/qs/tiles/dialog/InternetDialogDelegateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java
index 2f8f45c..0b9c06f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java
@@ -901,7 +901,7 @@
// 1st time is onStart(), 2nd time is getActiveAutoSwitchNonDdsSubId()
verify(mTelephonyManager, times(2)).registerTelephonyCallback(any(), any());
- assertThat(mInternetDialogController.mSubIdTelephonyCallbackMap.size() == 2);
+ assertThat(mInternetDialogController.mSubIdTelephonyCallbackMap.size()).isEqualTo(2);
// Adds non DDS subId again
doReturn(SUB_ID2).when(info).getSubscriptionId();
@@ -912,7 +912,7 @@
// Does not add due to cached subInfo in mSubIdTelephonyCallbackMap.
verify(mTelephonyManager, times(2)).registerTelephonyCallback(any(), any());
- assertThat(mInternetDialogController.mSubIdTelephonyCallbackMap.size() == 2);
+ assertThat(mInternetDialogController.mSubIdTelephonyCallbackMap.size()).isEqualTo(2);
}
@Test
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/connectivity/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
index 98315d0c..83dbfa0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
@@ -95,6 +95,7 @@
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
public class NetworkControllerBaseTest extends SysuiTestCase {
@@ -332,10 +333,15 @@
}
public void setConnectivityViaCallbackInNetworkControllerForVcn(
- int networkType, boolean validated, boolean isConnected, VcnTransportInfo info) {
+ int networkType,
+ boolean validated,
+ boolean isConnected,
+ VcnTransportInfo info,
+ Network underlyingNetwork) {
final NetworkCapabilities.Builder builder =
new NetworkCapabilities.Builder(mNetCapabilities);
- builder.setTransportInfo(info);
+ builder.setTransportInfo(info)
+ .setUnderlyingNetworks(Collections.singletonList(underlyingNetwork));
setConnectivityCommon(builder, networkType, validated, isConnected);
mDefaultCallbackInNetworkController.onCapabilitiesChanged(
mock(Network.class), builder.build());
@@ -385,10 +391,15 @@
}
public void setConnectivityViaCallbackInWifiTrackerForVcn(
- int networkType, boolean validated, boolean isConnected, VcnTransportInfo info) {
+ int networkType,
+ boolean validated,
+ boolean isConnected,
+ VcnTransportInfo info,
+ Network underlyingNetwork) {
final NetworkCapabilities.Builder builder =
new NetworkCapabilities.Builder(mNetCapabilities);
- builder.setTransportInfo(info);
+ builder.setTransportInfo(info)
+ .setUnderlyingNetworks(Collections.singletonList(underlyingNetwork));
setConnectivityCommon(builder, networkType, validated, isConnected);
if (networkType == NetworkCapabilities.TRANSPORT_CELLULAR) {
if (isConnected) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java
index 6c80a97..6febb91 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java
@@ -18,6 +18,7 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static junit.framework.Assert.assertEquals;
@@ -250,6 +251,17 @@
assertEquals(testSsid, mNetworkController.mWifiSignalController.mCurrentState.ssid);
}
+ private Network newWifiNetwork(WifiInfo wifiInfo) {
+ final Network network = mock(Network.class);
+ final NetworkCapabilities wifiCaps =
+ new NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_WIFI)
+ .setTransportInfo(wifiInfo)
+ .build();
+ when(mMockCm.getNetworkCapabilities(network)).thenReturn(wifiCaps);
+ return network;
+ }
+
@Test
public void testVcnWithUnderlyingWifi() {
String testSsid = "Test VCN SSID";
@@ -266,11 +278,19 @@
setWifiLevelForVcn(testLevel);
setConnectivityViaCallbackInNetworkControllerForVcn(
- NetworkCapabilities.TRANSPORT_CELLULAR, true, true, mVcnTransportInfo);
+ NetworkCapabilities.TRANSPORT_CELLULAR,
+ true,
+ true,
+ mVcnTransportInfo,
+ newWifiNetwork(mWifiInfo));
verifyLastMobileDataIndicatorsForVcn(true, testLevel, TelephonyIcons.ICON_CWF, true);
setConnectivityViaCallbackInNetworkControllerForVcn(
- NetworkCapabilities.TRANSPORT_CELLULAR, false, true, mVcnTransportInfo);
+ NetworkCapabilities.TRANSPORT_CELLULAR,
+ false,
+ true,
+ mVcnTransportInfo,
+ newWifiNetwork(mWifiInfo));
verifyLastMobileDataIndicatorsForVcn(true, testLevel, TelephonyIcons.ICON_CWF, false);
}
}
@@ -391,13 +411,15 @@
}
protected void setWifiLevelForVcn(int level) {
- when(mVcnTransportInfo.getWifiInfo()).thenReturn(mWifiInfo);
- when(mVcnTransportInfo.makeCopy(anyLong())).thenReturn(mVcnTransportInfo);
when(mWifiInfo.getRssi()).thenReturn(calculateRssiForLevel(level));
when(mWifiInfo.isCarrierMerged()).thenReturn(true);
when(mWifiInfo.getSubscriptionId()).thenReturn(1);
setConnectivityViaCallbackInWifiTrackerForVcn(
- NetworkCapabilities.TRANSPORT_CELLULAR, false, true, mVcnTransportInfo);
+ NetworkCapabilities.TRANSPORT_CELLULAR,
+ false,
+ true,
+ mVcnTransportInfo,
+ newWifiNetwork(mWifiInfo));
}
private int calculateRssiForLevel(int level) {
@@ -409,13 +431,15 @@
}
protected void setWifiStateForVcn(boolean connected, String ssid) {
- when(mVcnTransportInfo.getWifiInfo()).thenReturn(mWifiInfo);
- when(mVcnTransportInfo.makeCopy(anyLong())).thenReturn(mVcnTransportInfo);
when(mWifiInfo.getSSID()).thenReturn(ssid);
when(mWifiInfo.isCarrierMerged()).thenReturn(true);
when(mWifiInfo.getSubscriptionId()).thenReturn(1);
setConnectivityViaCallbackInWifiTrackerForVcn(
- NetworkCapabilities.TRANSPORT_CELLULAR, false, connected, mVcnTransportInfo);
+ NetworkCapabilities.TRANSPORT_CELLULAR,
+ false,
+ connected,
+ mVcnTransportInfo,
+ newWifiNetwork(mWifiInfo));
}
protected void verifyLastQsDataDirection(boolean in, boolean out) {
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/notification/row/SingleLineViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt
index 6b3fb5b..503fa78 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt
@@ -29,12 +29,13 @@
import com.android.systemui.statusbar.notification.row.SingleLineViewInflater.inflatePublicSingleLineView
import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation
import com.android.systemui.statusbar.notification.row.ui.viewbinder.SingleLineViewBinder
-import com.android.systemui.util.mockito.mock
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
+import kotlin.test.assertNull
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -69,7 +70,7 @@
reinflateFlags = FLAG_CONTENT_VIEW_SINGLE_LINE,
entry = row.entry,
context = context,
- logger = mock()
+ logger = mock(),
)
val publicView =
@@ -78,7 +79,7 @@
reinflateFlags = FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE,
entry = row.entry,
context = context,
- logger = mock()
+ logger = mock(),
)
assertNotNull(publicView)
@@ -114,7 +115,7 @@
.addMessage(
"How about lunch?",
System.currentTimeMillis(),
- Person.Builder().setName("user2").build()
+ Person.Builder().setName("user2").build(),
)
.setGroupConversation(true)
notificationBuilder.setStyle(style).setShortcutId(SHORTCUT_ID)
@@ -127,7 +128,7 @@
reinflateFlags = FLAG_CONTENT_VIEW_SINGLE_LINE,
entry = row.entry,
context = context,
- logger = mock()
+ logger = mock(),
)
as HybridConversationNotificationView
@@ -137,7 +138,7 @@
reinflateFlags = FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE,
entry = row.entry,
context = context,
- logger = mock()
+ logger = mock(),
)
as HybridConversationNotificationView
assertNotNull(publicView)
@@ -150,10 +151,7 @@
systemUiContext = context,
)
// WHEN: binds the view
- SingleLineViewBinder.bind(
- viewModel,
- view,
- )
+ SingleLineViewBinder.bind(viewModel, view)
// THEN: the single-line conversation view should be bound with view model's corresponding
// fields
@@ -161,10 +159,55 @@
assertEquals(viewModel.contentText, view.textView.text)
assertEquals(
viewModel.conversationData?.conversationSenderName,
- view.conversationSenderNameView.text
+ view.conversationSenderNameView.text,
)
}
+ @Test
+ @EnableFlags(AsyncHybridViewInflation.FLAG_NAME)
+ fun bindConversationSingleLineView_nonConversationViewModel() {
+ // GIVEN: a ConversationSingleLineView, and a nonConversationViewModel
+ val style = Notification.BigTextStyle().bigText(CONTENT_TEXT)
+ notificationBuilder.setStyle(style)
+ val notification = notificationBuilder.build()
+ val row: ExpandableNotificationRow = helper.createRow(notification)
+
+ val view =
+ inflatePrivateSingleLineView(
+ isConversation = true,
+ reinflateFlags = FLAG_CONTENT_VIEW_SINGLE_LINE,
+ entry = row.entry,
+ context = context,
+ logger = mock(),
+ )
+
+ val publicView =
+ inflatePublicSingleLineView(
+ isConversation = true,
+ reinflateFlags = FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE,
+ entry = row.entry,
+ context = context,
+ logger = mock(),
+ )
+ assertNotNull(publicView)
+
+ val viewModel =
+ SingleLineViewInflater.inflateSingleLineViewModel(
+ notification = notification,
+ messagingStyle = null,
+ builder = notificationBuilder,
+ systemUiContext = context,
+ )
+ // WHEN: binds the view with the view model
+ SingleLineViewBinder.bind(viewModel, view)
+
+ // THEN: the single-line view should be bound with view model's corresponding
+ // fields as a normal non-conversation single-line view
+ assertEquals(viewModel.titleText, view?.titleView?.text)
+ assertEquals(viewModel.contentText, view?.textView?.text)
+ assertNull(viewModel.conversationData)
+ }
+
private companion object {
const val CHANNEL_ID = "CHANNEL_ID"
const val CONTENT_TITLE = "A Cool New Feature"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
index 2ed3473..8360042 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
@@ -160,11 +160,11 @@
testableLooper = TestableLooper.get(this)
context.orCreateTestableResources.addOverride(
com.android.internal.R.string.status_bar_alarm_clock,
- ALARM_SLOT
+ ALARM_SLOT,
)
context.orCreateTestableResources.addOverride(
com.android.internal.R.string.status_bar_managed_profile,
- MANAGED_PROFILE_SLOT
+ MANAGED_PROFILE_SLOT,
)
whenever(devicePolicyManager.resources).thenReturn(devicePolicyManagerResources)
whenever(devicePolicyManagerResources.getString(anyString(), any())).thenReturn("")
@@ -430,8 +430,8 @@
eq(mContext.packageName),
eq(android.R.drawable.ic_lock_lock),
any(), // non-null
- eq("Bedtime Mode"),
- eq(StatusBarIcon.Shape.FIXED_SPACE)
+ eq("Bedtime Mode is on"),
+ eq(StatusBarIcon.Shape.FIXED_SPACE),
)
zenModeRepository.deactivateMode("bedtime")
@@ -443,8 +443,8 @@
eq(null),
eq(android.R.drawable.ic_media_play),
any(), // non-null
- eq("Other Mode"),
- eq(StatusBarIcon.Shape.FIXED_SPACE)
+ eq("Other Mode is on"),
+ eq(StatusBarIcon.Shape.FIXED_SPACE),
)
zenModeRepository.deactivateMode("other")
@@ -538,7 +538,7 @@
privacyLogger,
fakeConnectedDisplayStateProvider,
kosmos.zenModeInteractor,
- JavaAdapter(testScope.backgroundScope)
+ JavaAdapter(testScope.backgroundScope),
)
}
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/scene/data/repository/SystemGestureExclusionRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModelKosmos.kt
similarity index 70%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepositoryKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModelKosmos.kt
index 15ed1b3..8422942 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModelKosmos.kt
@@ -14,12 +14,8 @@
* limitations under the License.
*/
-package com.android.systemui.scene.data.repository
+package com.android.systemui.communal.ui.viewmodel
-import android.view.windowManagerService
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.Kosmos.Fixture
-val Kosmos.systemGestureExclusionRepository by Fixture {
- SystemGestureExclusionRepository(windowManager = windowManagerService)
-}
+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/FakeLightRevealScrimRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeLightRevealScrimRepository.kt
index f26bb83..805a710 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeLightRevealScrimRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeLightRevealScrimRepository.kt
@@ -35,6 +35,8 @@
private val _revealAmount: MutableStateFlow<Float> = MutableStateFlow(0.0f)
override val revealAmount: Flow<Float> = _revealAmount
+ val revealAnimatorRequests: MutableList<RevealAnimatorRequest> = arrayListOf()
+
override val isAnimating: Boolean
get() = false
@@ -44,5 +46,12 @@
} else {
_revealAmount.value = 0.0f
}
+
+ revealAnimatorRequests.add(RevealAnimatorRequest(reveal, duration))
}
+
+ data class RevealAnimatorRequest(
+ val reveal: Boolean,
+ val duration: Long
+ )
}
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/scene/data/repository/SystemGestureExclusionRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/VolumeDialogControllerKosmos.kt
similarity index 70%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepositoryKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/plugins/VolumeDialogControllerKosmos.kt
index 15ed1b3..2f6d4fa 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/VolumeDialogControllerKosmos.kt
@@ -14,12 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.scene.data.repository
+package com.android.systemui.plugins
-import android.view.windowManagerService
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.Kosmos.Fixture
+import org.mockito.kotlin.mock
-val Kosmos.systemGestureExclusionRepository by Fixture {
- SystemGestureExclusionRepository(windowManager = windowManagerService)
-}
+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 f842db4..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
@@ -8,17 +8,14 @@
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.domain.interactor.systemGestureExclusionInteractor
import com.android.systemui.scene.shared.logger.sceneLogger
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.FakeOverlay
-import com.android.systemui.scene.ui.viewmodel.SceneContainerGestureFilter
import com.android.systemui.scene.ui.viewmodel.SceneContainerHapticsViewModel
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
import com.android.systemui.scene.ui.viewmodel.splitEdgeDetector
-import com.android.systemui.settings.displayTracker
import com.android.systemui.shade.domain.interactor.shadeInteractor
import kotlinx.coroutines.flow.MutableStateFlow
import org.mockito.kotlin.mock
@@ -31,6 +28,7 @@
Scenes.Bouncer,
Scenes.Gone,
Scenes.Communal,
+ Scenes.Dream,
)
}
@@ -52,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(
@@ -72,16 +71,15 @@
}
val Kosmos.sceneContainerViewModel by Fixture {
- sceneContainerViewModelFactory.create(mock<View>(), displayTracker.defaultDisplayId, {}).apply {
- setTransitionState(transitionState)
- }
+ sceneContainerViewModelFactory
+ .create(mock<View>()) {}
+ .apply { setTransitionState(transitionState) }
}
val Kosmos.sceneContainerViewModelFactory by Fixture {
object : SceneContainerViewModel.Factory {
override fun create(
view: View,
- displayId: Int,
motionEventHandlerReceiver: (SceneContainerViewModel.MotionEventHandler?) -> Unit,
): SceneContainerViewModel =
SceneContainerViewModel(
@@ -91,26 +89,13 @@
shadeInteractor = shadeInteractor,
splitEdgeDetector = splitEdgeDetector,
logger = sceneLogger,
- gestureFilterFactory = sceneContainerGestureFilterFactory,
hapticsViewModelFactory = sceneContainerHapticsViewModelFactory,
view = view,
- displayId = displayId,
motionEventHandlerReceiver = motionEventHandlerReceiver,
)
}
}
-val Kosmos.sceneContainerGestureFilterFactory by Fixture {
- object : SceneContainerGestureFilter.Factory {
- override fun create(displayId: Int): SceneContainerGestureFilter {
- return SceneContainerGestureFilter(
- interactor = systemGestureExclusionInteractor,
- displayId = displayId,
- )
- }
- }
-}
-
val Kosmos.sceneContainerHapticsViewModelFactory by Fixture {
object : SceneContainerHapticsViewModel.Factory {
override fun create(view: View): SceneContainerHapticsViewModel {
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/domain/interactor/NotificationRowInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractorKosmos.kt
deleted file mode 100644
index 3a7d7ba..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractorKosmos.kt
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.row.domain.interactor
-
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.statusbar.notification.row.data.repository.NotificationRowRepository
-
-fun Kosmos.getNotificationRowInteractor(repository: NotificationRowRepository) =
- NotificationRowInteractor(repository = repository)
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/scene/data/repository/SystemGestureExclusionRepositoryKosmos.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/scene/data/repository/SystemGestureExclusionRepositoryKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorKosmos.kt
index 15ed1b3..e73539e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorKosmos.kt
@@ -14,12 +14,12 @@
* limitations under the License.
*/
-package com.android.systemui.scene.data.repository
+package com.android.systemui.volume.dialog.domain.interactor
-import android.view.windowManagerService
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.applicationCoroutineScope
-val Kosmos.systemGestureExclusionRepository by Fixture {
- SystemGestureExclusionRepository(windowManager = windowManagerService)
-}
+val Kosmos.volumeDialogVisibilityInteractor by
+ Kosmos.Fixture {
+ VolumeDialogVisibilityInteractor(applicationCoroutineScope, volumeDialogCallbacksInteractor)
+ }
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index 994bdb5..6489905 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -206,7 +206,7 @@
// Inform that DND settings have changed on OS upgrade
// Package: android
- NOTE_ZEN_UPGRADE = 48;
+ NOTE_ZEN_UPGRADE = 48 [deprecated = true];
// Notification to suggest automatic battery saver.
// Package: android
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 ab9cc20..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()) {
@@ -438,62 +424,17 @@
targetUser,
mServiceConfig.getExecuteAppFunctionCancellationTimeoutMillis(),
cancellationSignal,
- new RunServiceCallCallback<IAppFunctionService>() {
- @Override
- public void onServiceConnected(
- @NonNull IAppFunctionService service,
- @NonNull
- ServiceUsageCompleteListener
- serviceUsageCompleteListener) {
- try {
- service.executeAppFunction(
- requestInternal.getClientRequest(),
- cancellationCallback,
- new IExecuteAppFunctionCallback.Stub() {
- @Override
- public void onResult(
- ExecuteAppFunctionResponse response) {
- safeExecuteAppFunctionCallback.onResult(
- response);
- serviceUsageCompleteListener.onCompleted();
- }
- });
- } catch (Exception e) {
- safeExecuteAppFunctionCallback.onResult(
- ExecuteAppFunctionResponse.newFailure(
- ExecuteAppFunctionResponse
- .RESULT_APP_UNKNOWN_ERROR,
- e.getMessage(),
- /* extras= */ null));
- serviceUsageCompleteListener.onCompleted();
- }
- }
-
- @Override
- public void onFailedToConnect() {
- Slog.e(TAG, "Failed to connect to service");
- safeExecuteAppFunctionCallback.onResult(
- ExecuteAppFunctionResponse.newFailure(
- ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR,
- "Failed to connect to AppFunctionService",
- /* extras= */ null));
- }
-
- @Override
- public void onCancelled() {
- // Do not forward the result back to the caller once it has been
- // canceled. The caller does not need a notification and should
- // proceed after initiating a cancellation.
- safeExecuteAppFunctionCallback.disable();
- }
- },
+ RunAppFunctionServiceCallback.create(
+ requestInternal,
+ cancellationCallback,
+ safeExecuteAppFunctionCallback),
callerBinder);
if (!bindServiceResult) {
Slog.e(TAG, "Failed to bind to the AppFunctionService");
safeExecuteAppFunctionCallback.onResult(
ExecuteAppFunctionResponse.newFailure(
- ExecuteAppFunctionResponse.RESULT_TIMED_OUT,
+ ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR,
"Failed to bind the AppFunctionService.",
/* extras= */ null));
}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.java b/services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.java
new file mode 100644
index 0000000..7820390
--- /dev/null
+++ b/services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.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 com.android.server.appfunctions;
+
+import android.annotation.NonNull;
+import android.app.appfunctions.ExecuteAppFunctionAidlRequest;
+import android.app.appfunctions.ExecuteAppFunctionResponse;
+import android.app.appfunctions.IAppFunctionService;
+import android.app.appfunctions.ICancellationCallback;
+import android.app.appfunctions.IExecuteAppFunctionCallback;
+import android.app.appfunctions.SafeOneTimeExecuteAppFunctionCallback;
+import android.util.Slog;
+
+import com.android.server.appfunctions.RemoteServiceCaller.RunServiceCallCallback;
+import com.android.server.appfunctions.RemoteServiceCaller.ServiceUsageCompleteListener;
+
+
+/**
+ * A callback to forward a request to the {@link IAppFunctionService} and report back the result.
+ */
+public class RunAppFunctionServiceCallback implements RunServiceCallCallback<IAppFunctionService> {
+
+ private final ExecuteAppFunctionAidlRequest mRequestInternal;
+ private final SafeOneTimeExecuteAppFunctionCallback mSafeExecuteAppFunctionCallback;
+ private final ICancellationCallback mCancellationCallback;
+
+ private RunAppFunctionServiceCallback(
+ ExecuteAppFunctionAidlRequest requestInternal,
+ ICancellationCallback cancellationCallback,
+ SafeOneTimeExecuteAppFunctionCallback safeExecuteAppFunctionCallback) {
+ this.mRequestInternal = requestInternal;
+ this.mSafeExecuteAppFunctionCallback = safeExecuteAppFunctionCallback;
+ this.mCancellationCallback = cancellationCallback;
+ }
+
+ /**
+ * Creates a new instance of {@link RunAppFunctionServiceCallback}.
+ *
+ * @param requestInternal a request to send to the service.
+ * @param cancellationCallback a callback to forward cancellation signal to the service.
+ * @param safeExecuteAppFunctionCallback a callback to report back the result of the operation.
+ */
+ public static RunAppFunctionServiceCallback create(
+ ExecuteAppFunctionAidlRequest requestInternal,
+ ICancellationCallback cancellationCallback,
+ SafeOneTimeExecuteAppFunctionCallback safeExecuteAppFunctionCallback) {
+ return new RunAppFunctionServiceCallback(
+ requestInternal, cancellationCallback, safeExecuteAppFunctionCallback);
+ }
+
+ @Override
+ public void onServiceConnected(
+ @NonNull IAppFunctionService service,
+ @NonNull ServiceUsageCompleteListener serviceUsageCompleteListener) {
+ try {
+ service.executeAppFunction(
+ mRequestInternal.getClientRequest(),
+ mCancellationCallback,
+ new IExecuteAppFunctionCallback.Stub() {
+ @Override
+ public void onResult(ExecuteAppFunctionResponse response) {
+ mSafeExecuteAppFunctionCallback.onResult(response);
+ serviceUsageCompleteListener.onCompleted();
+ }
+ });
+ } catch (Exception e) {
+ mSafeExecuteAppFunctionCallback.onResult(
+ ExecuteAppFunctionResponse.newFailure(
+ ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR,
+ e.getMessage(),
+ /* extras= */ null));
+ serviceUsageCompleteListener.onCompleted();
+ }
+ }
+
+ @Override
+ public void onFailedToConnect() {
+ Slog.e("AppFunctionManagerServiceImpl", "Failed to connect to service");
+ mSafeExecuteAppFunctionCallback.onResult(
+ ExecuteAppFunctionResponse.newFailure(
+ ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR,
+ "Failed to connect to AppFunctionService",
+ /* extras= */ null));
+ }
+
+ @Override
+ public void onCancelled() {
+ mSafeExecuteAppFunctionCallback.disable();
+ }
+}
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 cd2dd3a..3bcca1c 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -42,6 +42,7 @@
import android.app.admin.DevicePolicyManager;
import android.app.compat.CompatChanges;
import android.companion.AssociationInfo;
+import android.companion.AssociationRequest;
import android.companion.virtual.ActivityPolicyExemption;
import android.companion.virtual.IVirtualDevice;
import android.companion.virtual.IVirtualDeviceActivityListener;
@@ -98,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;
@@ -153,6 +155,9 @@
private static final String PERSISTENT_ID_PREFIX_CDM_ASSOCIATION = "companion:";
+ private static final List<String> DEVICE_PROFILES_ALLOWING_MIRROR_DISPLAYS = List.of(
+ AssociationRequest.DEVICE_PROFILE_APP_STREAMING);
+
/**
* Timeout until {@link #launchPendingIntent} stops waiting for an activity to be launched.
*/
@@ -199,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
@@ -209,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;
@@ -414,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(),
@@ -471,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;
@@ -498,6 +523,10 @@
return mAssociationInfo == null ? mParams.getName() : mAssociationInfo.getDisplayName();
}
+ String getDeviceProfile() {
+ return mAssociationInfo == null ? null : mAssociationInfo.getDeviceProfile();
+ }
+
/** Returns the public representation of the device. */
VirtualDevice getPublicVirtualDeviceObject() {
return mPublicVirtualDeviceObject;
@@ -568,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)) {
@@ -1034,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);
@@ -1117,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);
@@ -1294,6 +1364,11 @@
return hasCustomAudioInputSupportInternal();
}
+ @Override
+ public boolean canCreateMirrorDisplays() {
+ return DEVICE_PROFILES_ALLOWING_MIRROR_DISPLAYS.contains(getDeviceProfile());
+ }
+
private boolean hasCustomAudioInputSupportInternal() {
if (!Flags.vdmPublicApis()) {
return false;
@@ -1398,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) {
@@ -1420,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;
}
@@ -1431,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);
}
@@ -1498,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()).
@@ -1549,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.
*
@@ -1652,6 +1760,7 @@
return mInputController.getInputDeviceDescriptors().values().stream().anyMatch(
inputDeviceDescriptor -> inputDeviceDescriptor.getInputDeviceId() == inputDeviceId);
}
+
void playSoundEffect(int effectType) {
try {
mSoundEffectListener.onPlaySoundEffect(effectType);
@@ -1719,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() {
@@ -1736,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/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index 9060250..2acedd5 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -47,6 +47,7 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
+import android.util.EventLog;
import android.util.IndentingPrintWriter;
import android.util.LongArrayQueue;
import android.util.Slog;
@@ -200,6 +201,13 @@
// aborted.
private static final String METADATA_FILE = "/metadata/watchdog/mitigation_count.txt";
+ /**
+ * EventLog tags used when logging into the event log. Note the values must be sync with
+ * frameworks/base/services/core/java/com/android/server/EventLogTags.logtags to get correct
+ * name translation.
+ */
+ private static final int LOG_TAG_RESCUE_NOTE = 2900;
+
private static final Object sPackageWatchdogLock = new Object();
@GuardedBy("sPackageWatchdogLock")
private static PackageWatchdog sPackageWatchdog;
@@ -2024,7 +2032,7 @@
} else {
int count = getCount() + 1;
setCount(count);
- EventLogTags.writeRescueNote(Process.ROOT_UID, count, window);
+ EventLog.writeEvent(LOG_TAG_RESCUE_NOTE, Process.ROOT_UID, count, window);
if (Flags.recoverabilityDetection()) {
// After a reboot (e.g. by WARM_REBOOT or mainline rollback) we apply
// mitigations without waiting for DEFAULT_BOOT_LOOP_TRIGGER_COUNT.
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index ada1953..feb5775 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -42,6 +42,7 @@
import android.sysprop.CrashRecoveryProperties;
import android.text.TextUtils;
import android.util.ArraySet;
+import android.util.EventLog;
import android.util.Log;
import android.util.Slog;
@@ -154,6 +155,14 @@
private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT
| ApplicationInfo.FLAG_SYSTEM;
+ /**
+ * EventLog tags used when logging into the event log. Note the values must be sync with
+ * frameworks/base/services/core/java/com/android/server/EventLogTags.logtags to get correct
+ * name translation.
+ */
+ private static final int LOG_TAG_RESCUE_SUCCESS = 2902;
+ private static final int LOG_TAG_RESCUE_FAILURE = 2903;
+
/** Register the Rescue Party observer as a Package Watchdog health observer */
public static void registerHealthObserver(Context context) {
PackageWatchdog.getInstance(context).registerHealthObserver(
@@ -523,7 +532,7 @@
Slog.w(TAG, "Attempting rescue level " + levelToString(level));
try {
executeRescueLevelInternal(context, level, failedPackage);
- EventLogTags.writeRescueSuccess(level);
+ EventLog.writeEvent(LOG_TAG_RESCUE_SUCCESS, level);
String successMsg = "Finished rescue level " + levelToString(level);
if (!TextUtils.isEmpty(failedPackage)) {
successMsg += " for package " + failedPackage;
@@ -704,7 +713,7 @@
private static void logRescueException(int level, @Nullable String failedPackageName,
Throwable t) {
final String msg = getCompleteMessage(t);
- EventLogTags.writeRescueFailure(level, msg);
+ EventLog.writeEvent(LOG_TAG_RESCUE_FAILURE, level, msg);
String failureMsg = "Failed rescue level " + levelToString(level);
if (!TextUtils.isEmpty(failedPackageName)) {
failureMsg += " for package " + failedPackageName;
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index a459ea9..ce66dc3 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -114,6 +114,9 @@
"options": [
{
"include-filter": "android.os.storage.cts.StorageManagerTest"
+ },
+ {
+ "include-filter": "android.os.storage.cts.StorageStatsManagerTest"
}
]
}
@@ -173,15 +176,6 @@
"include-filter": "com.android.server.wm.BackgroundActivityStart*"
}
]
- },
- {
- "name": "CtsOsTestCases",
- "file_patterns": ["StorageManagerService\\.java"],
- "options": [
- {
- "include-filter": "android.os.storage.cts.StorageStatsManagerTest"
- }
- ]
}
]
}
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index 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..9219cc12 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -18398,25 +18398,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 {
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 78a0a11..796de19 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -458,7 +458,13 @@
}
void setThreadPriority(int tid, int priority) {
- Process.setThreadPriority(tid, priority);
+ if (Flags.resetOnForkEnabled()) {
+ Process.setThreadScheduler(tid,
+ Process.SCHED_OTHER | Process.SCHED_RESET_ON_FORK,
+ priority);
+ } else {
+ Process.setThreadPriority(tid, priority);
+ }
}
}
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 4f6da3b..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 {
@@ -214,3 +211,10 @@
description: "Defer submitting display events to frozen processes."
bug: "326315985"
}
+
+flag {
+ name: "reset_on_fork_enabled"
+ namespace: "system_performance"
+ description: "Set reset_on_fork flag."
+ bug: "370988407"
+}
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index c2e62d0..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;
@@ -540,11 +544,11 @@
this.uid = uid;
}
- @SuppressWarnings("GuardedBy")
public void clear() {
mAppOpsCheckingService.removeUid(uid);
for (int i = 0; i < pkgOps.size(); i++) {
- packageRemovedLocked(uid, pkgOps.keyAt(i));
+ String packageName = pkgOps.keyAt(i);
+ mAppOpsCheckingService.removePackage(packageName, UserHandle.getUserId(uid));
}
}
@@ -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 87504154..60dbf3f 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -384,11 +384,13 @@
/**
* Indicates if a Bluetooth SCO activation request owner is controlling
* the SCO audio state itself or not.
- * @param uid the UI of the SOC request owner app
+ * @param uid the UID of the SOC request owner app
* @return true if we should control SCO audio state, false otherwise
*/
private boolean shouldStartScoForUid(int uid) {
- return !(uid == Process.BLUETOOTH_UID || uid == Process.PHONE_UID);
+ return !(UserHandle.isSameApp(uid, Process.BLUETOOTH_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/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 5fd12c2..09de894 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -385,11 +385,6 @@
|| !updatedDevice.getDeviceAddress().equals(ads.getDeviceAddress())) {
continue;
}
- if (mDeviceBroker.isSADevice(updatedDevice) == mDeviceBroker.isSADevice(ads)) {
- ads.setHasHeadTracker(updatedDevice.hasHeadTracker());
- ads.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled());
- ads.setSAEnabled(updatedDevice.isSAEnabled());
- }
ads.setAudioDeviceCategory(updatedDevice.getAudioDeviceCategory());
mDeviceBroker.postUpdatedAdiDeviceState(ads, false /*initSA*/);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index f9e8392..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);
@@ -10100,9 +10100,6 @@
case MSG_INIT_SPATIALIZER:
onInitSpatializer();
- // the device inventory can only be synchronized after the
- // spatializer has been initialized
- mDeviceBroker.postSynchronizeAdiDevicesInInventory(null);
mAudioEventWakeLock.release();
break;
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/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index af9c9ac..8d96ba9 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -377,6 +377,7 @@
* </point>
* </map>
* </luxToBrightnessMapping>
+ * <idleStylusTimeoutMillis>10000</idleStylusTimeoutMillis>
* </autoBrightness>
*
* <screenBrightnessRampFastDecrease>0.01</screenBrightnessRampFastDecrease>
@@ -708,6 +709,10 @@
private static final int KEEP_CURRENT_BRIGHTNESS = -1;
+ // The default value to 0 which will signify that the stylus usage immediately stopped
+ // after it was started. This will make the system behave as if the stylus was never used
+ private static final int DEFAULT_IDLE_STYLUS_TIMEOUT_MILLIS = 0;
+
private final Context mContext;
// The details of the ambient light sensor associated with this display.
@@ -754,6 +759,9 @@
@Nullable
private DisplayBrightnessMappingConfig mDisplayBrightnessMapping;
+ private int mIdleStylusTimeoutMillis =
+ DEFAULT_IDLE_STYLUS_TIMEOUT_MILLIS;
+
private float mBacklightMinimum = Float.NaN;
private float mBacklightMaximum = Float.NaN;
private float mBrightnessDefault = Float.NaN;
@@ -1730,6 +1738,7 @@
+ ", mDisplayBrightnessMapping= " + mDisplayBrightnessMapping
+ ", mDdcAutoBrightnessAvailable= " + mDdcAutoBrightnessAvailable
+ ", mAutoBrightnessAvailable= " + mAutoBrightnessAvailable
+ + ", mIdleStylusTimeoutMillis= " + mIdleStylusTimeoutMillis
+ "\n"
+ "mDefaultLowBlockingZoneRefreshRate= " + mDefaultLowBlockingZoneRefreshRate
+ ", mDefaultHighBlockingZoneRefreshRate= " + mDefaultHighBlockingZoneRefreshRate
@@ -2389,10 +2398,19 @@
loadAutoBrightnessDarkeningLightDebounceIdle(autoBrightness);
mDisplayBrightnessMapping = new DisplayBrightnessMappingConfig(mContext, mFlags,
autoBrightness, getBacklightToBrightnessSpline());
+ loadIdleStylusTimeoutMillis(autoBrightness);
loadEnableAutoBrightness(autoBrightness);
}
/**
+ * Gets the timeout post the stylus usage after which the automatic brightness will be enabled
+ * again
+ */
+ public int getIdleStylusTimeoutMillis() {
+ return mIdleStylusTimeoutMillis;
+ }
+
+ /**
* Loads the auto-brightness brightening light debounce. Internally, this takes care of loading
* the value from the display config, and if not present, falls back to config.xml.
*/
@@ -2923,6 +2941,16 @@
return levels;
}
+ private void loadIdleStylusTimeoutMillis(AutoBrightness autoBrightness) {
+ if (autoBrightness == null) {
+ return;
+ }
+ BigInteger idleStylusTimeoutMillis = autoBrightness.getIdleStylusTimeoutMillis();
+ if (idleStylusTimeoutMillis != null) {
+ mIdleStylusTimeoutMillis = idleStylusTimeoutMillis.intValue();
+ }
+ }
+
private void loadEnableAutoBrightness(AutoBrightness autobrightness) {
// mDdcAutoBrightnessAvailable is initialised to true, so that we fallback to using the
// config.xml values if the autobrightness tag is not defined in the ddc file.
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 4152ec9..d71826f 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1661,33 +1661,49 @@
return false;
}
+ private boolean hasVideoOutputPermission(String func) {
+ return checkCallingPermission(CAPTURE_VIDEO_OUTPUT, func)
+ || hasSecureVideoOutputPermission(func);
+ }
+
+ private boolean hasSecureVideoOutputPermission(String func) {
+ return checkCallingPermission(CAPTURE_SECURE_VIDEO_OUTPUT, func);
+ }
+
+ private boolean canCreateMirrorDisplays(IVirtualDevice virtualDevice) {
+ if (virtualDevice == null) {
+ return false;
+ }
+ try {
+ return virtualDevice.canCreateMirrorDisplays();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to query virtual device for permissions", e);
+ return false;
+ }
+ }
+
private boolean canProjectVideo(IMediaProjection projection) {
- if (projection != null) {
- try {
- if (projection.canProjectVideo()) {
- return true;
- }
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to query projection service for permissions", e);
- }
+ if (projection == null) {
+ return false;
}
- if (checkCallingPermission(CAPTURE_VIDEO_OUTPUT, "canProjectVideo()")) {
- return true;
+ try {
+ return projection.canProjectVideo();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to query projection service for permissions", e);
+ return false;
}
- return canProjectSecureVideo(projection);
}
private boolean canProjectSecureVideo(IMediaProjection projection) {
- if (projection != null) {
- try {
- if (projection.canProjectSecureVideo()) {
- return true;
- }
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to query projection service for permissions", e);
- }
+ if (projection == null) {
+ return false;
}
- return checkCallingPermission(CAPTURE_SECURE_VIDEO_OUTPUT, "canProjectSecureVideo()");
+ try {
+ return projection.canProjectSecureVideo();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to query projection service for permissions", e);
+ return false;
+ }
}
private boolean checkCallingPermission(String permission, String func) {
@@ -1793,7 +1809,8 @@
&& (flags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) != 0) {
// Only a valid media projection or a virtual device can create a mirror virtual
// display.
- if (!canProjectVideo(projection) && virtualDevice == null) {
+ if (!canProjectVideo(projection) && !canCreateMirrorDisplays(virtualDevice)
+ && !hasVideoOutputPermission("createVirtualDisplayInternal")) {
throw new SecurityException("Requires CAPTURE_VIDEO_OUTPUT or "
+ "CAPTURE_SECURE_VIDEO_OUTPUT permission, or an appropriate "
+ "MediaProjection token in order to create a screen sharing virtual "
@@ -1803,7 +1820,8 @@
}
}
if (callingUid != Process.SYSTEM_UID && (flags & VIRTUAL_DISPLAY_FLAG_SECURE) != 0) {
- if (!canProjectSecureVideo(projection)) {
+ if (!canProjectSecureVideo(projection)
+ && !hasSecureVideoOutputPermission("createVirtualDisplayInternal")) {
throw new SecurityException("Requires CAPTURE_SECURE_VIDEO_OUTPUT "
+ "or an appropriate MediaProjection token to create a "
+ "secure virtual display.");
@@ -2093,16 +2111,6 @@
}
}
- private void setVirtualDisplayStateInternal(IBinder appToken, boolean isOn) {
- synchronized (mSyncRoot) {
- if (mVirtualDisplayAdapter == null) {
- return;
- }
-
- mVirtualDisplayAdapter.setVirtualDisplayStateLocked(appToken, isOn);
- }
- }
-
private void setVirtualDisplayRotationInternal(IBinder appToken,
@Surface.Rotation int rotation) {
int displayId;
@@ -2294,6 +2302,9 @@
updateLogicalDisplayState(display);
mExternalDisplayPolicy.handleLogicalDisplayAddedLocked(display);
+ if (mDisplayTopologyCoordinator != null) {
+ mDisplayTopologyCoordinator.onDisplayAdded(display.getDisplayInfoLocked());
+ }
}
private void handleLogicalDisplayChangedLocked(@NonNull LogicalDisplay display) {
@@ -2381,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());
}
@@ -4615,16 +4629,6 @@
}
@Override // Binder call
- public void setVirtualDisplayState(IVirtualDisplayCallback callback, boolean isOn) {
- final long token = Binder.clearCallingIdentity();
- try {
- setVirtualDisplayStateInternal(callback.asBinder(), isOn);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override // Binder call
public void setVirtualDisplayRotation(IVirtualDisplayCallback callback,
@Surface.Rotation int rotation) {
if (!android.companion.virtualdevice.flags.Flags.virtualDisplayRotationApi()) {
@@ -5235,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();
}
}
@@ -5648,6 +5651,21 @@
public void onPresentation(int displayId, boolean isShown) {
mExternalDisplayPolicy.onPresentation(displayId, isShown);
}
+
+ @Override
+ public void stylusGestureStarted(long eventTime) {
+ if (mFlags.isBlockAutobrightnessChangesOnStylusUsage()) {
+ DisplayPowerController displayPowerController;
+ synchronized (mSyncRoot) {
+ displayPowerController = mDisplayPowerControllers.get(
+ Display.DEFAULT_DISPLAY);
+ }
+ // We assume that the stylus is being used on the default display. This should
+ // be changed to the displayId on which it is being used once we start getting this
+ // information from the input manager service
+ displayPowerController.stylusGestureStarted(eventTime);
+ }
+ }
}
class DesiredDisplayModeSpecsObserver
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 03fec011..8f07bb3 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -167,12 +167,11 @@
private static final int MSG_SET_DWBC_LOGGING_ENABLED = 16;
private static final int MSG_SET_BRIGHTNESS_FROM_OFFLOAD = 17;
private static final int MSG_OFFLOADING_SCREEN_ON_UNBLOCKED = 18;
-
-
+ private static final int MSG_SET_STYLUS_BEING_USED = 19;
+ private static final int MSG_SET_STYLUS_USE_ENDED = 20;
private static final int BRIGHTNESS_CHANGE_STATSD_REPORT_INTERVAL_MS = 500;
-
// State machine constants for tracking initial brightness ramp skipping when enabled.
private static final int RAMP_STATE_SKIP_NONE = 0;
private static final int RAMP_STATE_SKIP_INITIAL = 1;
@@ -191,6 +190,10 @@
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30, 40, 50, 60, 70, 80,
90, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1200,
1400, 1600, 1800, 2000, 2250, 2500, 2750, 3000};
+
+ private static final int STYLUS_USAGE_DEBOUNCE_TIME = 1000;
+ private static final int NANO_SECONDS_TO_MILLI_SECONDS_RATIO = 1_000_000;
+
private static final int[] BRIGHTNESS_RANGE_INDEX = {
FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_UNKNOWN,
FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_0_1,
@@ -498,6 +501,11 @@
@GuardedBy("mLock")
private int mPendingOverrideDozeScreenStateLocked;
+ private long mLastStylusUsageEventTime = -1;
+
+ // The time of inactivity after which the stylus can be assumed to be no longer in use.
+ private long mIdleStylusTimeoutMillisConfig = 0;
+
/**
* Creates the display power controller.
*/
@@ -518,6 +526,7 @@
mSensorManager = sensorManager;
mHandler = new DisplayControllerHandler(handler.getLooper());
mDisplayDeviceConfig = mDisplayDevice.getDisplayDeviceConfig();
+ mIdleStylusTimeoutMillisConfig = mDisplayDeviceConfig.getIdleStylusTimeoutMillis();
mIsEnabled = logicalDisplay.isEnabledLocked();
mIsInTransition = logicalDisplay.isInTransitionLocked();
mIsDisplayInternal = displayDeviceInfo.type == Display.TYPE_INTERNAL;
@@ -893,6 +902,7 @@
mPhysicalDisplayName = displayName;
mDisplayStatsId = mUniqueDisplayId.hashCode();
mDisplayDeviceConfig = config;
+ mIdleStylusTimeoutMillisConfig = mDisplayDeviceConfig.getIdleStylusTimeoutMillis();
mThermalBrightnessThrottlingDataId = thermalBrightnessThrottlingDataId;
loadFromDisplayDeviceConfig(token, info, hbmMetadata);
mDisplayPowerProximityStateController.notifyDisplayDeviceChanged(config);
@@ -2971,6 +2981,18 @@
return mDisplayId == Display.DEFAULT_DISPLAY || mBootCompleted;
}
+ public void stylusGestureStarted(long eventTimeNanoSeconds) {
+ long eventTimeMs = eventTimeNanoSeconds / NANO_SECONDS_TO_MILLI_SECONDS_RATIO;
+ if (mLastStylusUsageEventTime == -1
+ || eventTimeMs > mLastStylusUsageEventTime + STYLUS_USAGE_DEBOUNCE_TIME) {
+ synchronized (mLock) {
+ // Add a message to notify the stylus usage has started
+ mHandler.sendEmptyMessageAtTime(MSG_SET_STYLUS_BEING_USED, mClock.uptimeMillis());
+ }
+ mLastStylusUsageEventTime = eventTimeMs;
+ }
+ }
+
private final class DisplayControllerHandler extends Handler {
DisplayControllerHandler(Looper looper) {
super(looper, null, true /*async*/);
@@ -3087,6 +3109,20 @@
updatePowerState();
}
break;
+ case MSG_SET_STYLUS_BEING_USED:
+ // Remove any MSG_SET_STYLUS_USE_ENDED message from the handler queue and
+ // post a delayed MSG_SET_STYLUS_USE_ENDED message to delay the stylus
+ // usage ended event processing
+ mHandler.removeMessages(MSG_SET_STYLUS_USE_ENDED);
+ Message message = mHandler.obtainMessage(MSG_SET_STYLUS_USE_ENDED);
+ mHandler.sendMessageAtTime(message,
+ mClock.uptimeMillis() + mIdleStylusTimeoutMillisConfig);
+ mDisplayBrightnessController.setStylusBeingUsed(true);
+ break;
+ case MSG_SET_STYLUS_USE_ENDED:
+ mDisplayBrightnessController.setStylusBeingUsed(false);
+ updatePowerState();
+ break;
}
}
}
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/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index bcb600d..06a9103 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -355,8 +355,8 @@
public SparseArray<int[]> getDisplayIdsByGroupIdLocked() {
SparseArray<int[]> displayIdsByGroupIds = new SparseArray<>();
for (int i = 0; i < mDisplayGroups.size(); i++) {
- int groupId = mDisplayGroups.get(i).getGroupId();
- displayIdsByGroupIds.put(groupId, getDisplayIdsForGroupLocked(groupId));
+ final int displayGroupId = mDisplayGroups.keyAt(i);
+ displayIdsByGroupIds.put(displayGroupId, getDisplayIdsForGroupLocked(displayGroupId));
}
return displayIdsByGroupIds;
}
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index 9b02f4b..e77c5ec 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -207,13 +207,6 @@
return device;
}
- void setVirtualDisplayStateLocked(IBinder appToken, boolean isOn) {
- VirtualDisplayDevice device = mVirtualDisplayDevices.get(appToken);
- if (device != null) {
- device.setDisplayState(isOn);
- }
- }
-
DisplayDevice getDisplayDevice(IBinder appToken) {
return mVirtualDisplayDevices.get(appToken);
}
@@ -273,7 +266,6 @@
private boolean mStopped;
private int mPendingChanges;
private Display.Mode mMode;
- private boolean mIsDisplayOn;
private int mDisplayIdToMirror;
private boolean mIsWindowManagerMirroring;
private DisplayCutout mDisplayCutout;
@@ -299,9 +291,8 @@
mCallback = callback;
mProjection = projection;
mMediaProjectionCallback = mediaProjectionCallback;
- mDisplayState = Display.STATE_UNKNOWN;
+ mDisplayState = Display.STATE_ON;
mPendingChanges |= PENDING_SURFACE_CHANGE;
- mIsDisplayOn = surface != null;
mDisplayIdToMirror = virtualDisplayConfig.getDisplayIdToMirror();
mIsWindowManagerMirroring = virtualDisplayConfig.isWindowManagerMirroringEnabled();
}
@@ -394,6 +385,8 @@
float sdrBrightnessState, DisplayOffloadSessionImpl displayOffloadSession) {
if (state != mDisplayState) {
mDisplayState = state;
+ mInfo = null;
+ sendDisplayDeviceEventLocked(this, DISPLAY_DEVICE_EVENT_CHANGED);
if (state == Display.STATE_OFF) {
mCallback.dispatchDisplayPaused();
} else {
@@ -416,12 +409,13 @@
public void setSurfaceLocked(Surface surface) {
if (!mStopped && mSurface != surface) {
- if ((mSurface != null) != (surface != null)) {
+ if (mDisplayState == Display.STATE_ON
+ && ((mSurface == null) != (surface == null))) {
+ mInfo = null;
sendDisplayDeviceEventLocked(this, DISPLAY_DEVICE_EVENT_CHANGED);
}
sendTraversalRequestLocked();
mSurface = surface;
- mInfo = null;
mPendingChanges |= PENDING_SURFACE_CHANGE;
}
}
@@ -439,14 +433,6 @@
}
}
- void setDisplayState(boolean isOn) {
- if (mIsDisplayOn != isOn) {
- mIsDisplayOn = isOn;
- mInfo = null;
- sendDisplayDeviceEventLocked(this, DISPLAY_DEVICE_EVENT_CHANGED);
- }
- }
-
public void stopLocked() {
Slog.d(TAG, "Virtual Display: stopping device " + mName);
setSurfaceLocked(null);
@@ -567,7 +553,11 @@
mInfo.touch = ((mFlags & VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH) == 0) ?
DisplayDeviceInfo.TOUCH_NONE : DisplayDeviceInfo.TOUCH_VIRTUAL;
- mInfo.state = mIsDisplayOn ? Display.STATE_ON : Display.STATE_OFF;
+ if (mSurface == null) {
+ mInfo.state = Display.STATE_OFF;
+ } else {
+ mInfo.state = mDisplayState;
+ }
mInfo.ownerUid = mOwnerUid;
mInfo.ownerPackageName = mOwnerPackageName;
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
index 72a91d5..71fdaf3 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
@@ -501,6 +501,13 @@
return true;
}
+ /**
+ * Notifies if the stylus is currently being used or not.
+ */
+ public void setStylusBeingUsed(boolean isEnabled) {
+ // Todo(b/369977976) - Disable the auto-brightness strategy
+ }
+
@VisibleForTesting
static class Injector {
DisplayBrightnessStrategySelector getDisplayBrightnessStrategySelector(Context context,
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index df66893..5284d1c 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -203,6 +203,10 @@
Flags.FLAG_NORMAL_BRIGHTNESS_FOR_DOZE_PARAMETER,
Flags::normalBrightnessForDozeParameter
);
+ private final FlagState mBlockAutobrightnessChangesOnStylusUsage = new FlagState(
+ Flags.FLAG_BLOCK_AUTOBRIGHTNESS_CHANGES_ON_STYLUS_USAGE,
+ Flags::blockAutobrightnessChangesOnStylusUsage
+ );
private final FlagState mEnableBatteryStatsForAllDisplays = new FlagState(
Flags.FLAG_ENABLE_BATTERY_STATS_FOR_ALL_DISPLAYS,
@@ -436,6 +440,13 @@
}
/**
+ * @return {@code true} if autobrightness is to be blocked when stylus is being used
+ */
+ public boolean isBlockAutobrightnessChangesOnStylusUsage() {
+ return mBlockAutobrightnessChangesOnStylusUsage.isEnabled();
+ }
+
+ /**
* dumps all flagstates
* @param pw printWriter
*/
@@ -479,6 +490,7 @@
pw.println(" " + mNormalBrightnessForDozeParameter);
pw.println(" " + mIdleScreenConfigInSubscribingLightSensor);
pw.println(" " + mEnableBatteryStatsForAllDisplays);
+ pw.println(" " + mBlockAutobrightnessChangesOnStylusUsage);
}
private static class FlagState {
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index e3ebe5b..252ed09 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -364,4 +364,12 @@
description: "Flag to enable battery stats for all displays."
bug: "366112793"
is_fixed_read_only: true
-}
\ No newline at end of file
+}
+
+flag {
+ name: "block_autobrightness_changes_on_stylus_usage"
+ namespace: "display_manager"
+ description: "Block the usage of ALS to control the display brightness when stylus is being used"
+ bug: "352411468"
+ is_fixed_read_only: true
+}
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/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index f045576..8acf583 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -2694,6 +2694,9 @@
@SuppressWarnings("unused")
private void notifyStylusGestureStarted(int deviceId, long eventTime) {
mBatteryController.notifyStylusGestureStarted(deviceId, eventTime);
+ if (mDisplayManagerInternal != null) {
+ mDisplayManagerInternal.stylusGestureStarted(eventTime);
+ }
}
/**
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/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 47f579d..e7e519e 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -42,6 +42,8 @@
import android.app.IProcessObserver;
import android.app.KeyguardManager;
import android.app.compat.CompatChanges;
+import android.app.role.RoleManager;
+import android.companion.AssociationRequest;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.content.ComponentName;
@@ -94,7 +96,7 @@
/**
* Manages MediaProjection sessions.
- *
+ * <p>
* The {@link MediaProjectionManagerService} manages the creation and lifetime of MediaProjections,
* as well as the capabilities they grant. Any service using MediaProjection tokens as permission
* grants <b>must</b> validate the token before use by calling {@link
@@ -137,6 +139,7 @@
private final PackageManager mPackageManager;
private final WindowManagerInternal mWmInternal;
private final KeyguardManager mKeyguardManager;
+ private final RoleManager mRoleManager;
private final MediaRouter mMediaRouter;
private final MediaRouterCallback mMediaRouterCallback;
@@ -173,6 +176,7 @@
mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
mKeyguardManager.addKeyguardLockedStateListener(
mContext.getMainExecutor(), this::onKeyguardLockedStateChanged);
+ mRoleManager = mContext.getSystemService(RoleManager.class);
Watchdog.getInstance().addMonitor(this);
}
@@ -182,6 +186,7 @@
* - be one of the bugreport allowlisted packages, or
* - hold the OP_PROJECT_MEDIA AppOp.
*/
+ @SuppressWarnings("BooleanMethodIsAlwaysInverted")
private boolean canCaptureKeyguard() {
if (!android.companion.virtualdevice.flags.Flags.mediaProjectionKeyguardRestrictions()) {
return true;
@@ -193,6 +198,9 @@
if (mPackageManager.checkPermission(RECORD_SENSITIVE_CONTENT,
mProjectionGrant.packageName)
== PackageManager.PERMISSION_GRANTED) {
+ Slog.v(TAG,
+ "Allowing keyguard capture for package with RECORD_SENSITIVE_CONTENT "
+ + "permission");
return true;
}
if (AppOpsManager.MODE_ALLOWED == mAppOps.noteOpNoThrow(AppOpsManager.OP_PROJECT_MEDIA,
@@ -200,6 +208,13 @@
"recording lockscreen")) {
// Some tools use media projection by granting the OP_PROJECT_MEDIA app
// op via a shell command. Those tools can be granted keyguard capture
+ Slog.v(TAG,
+ "Allowing keyguard capture for package with OP_PROJECT_MEDIA AppOp ");
+ return true;
+ }
+ if (isProjectionAppHoldingAppStreamingRoleLocked()) {
+ Slog.v(TAG,
+ "Allowing keyguard capture for package holding app streaming role.");
return true;
}
return SystemConfig.getInstance().getBugreportWhitelistedPackages()
@@ -698,6 +713,20 @@
}
}
+ /**
+ * Application holding the app streaming role
+ * ({@value AssociationRequest#DEVICE_PROFILE_APP_STREAMING}) are allowed to record the
+ * lockscreen.
+ *
+ * @return true if the is held by the recording application.
+ */
+ @GuardedBy("mLock")
+ private boolean isProjectionAppHoldingAppStreamingRoleLocked() {
+ return mRoleManager.getRoleHoldersAsUser(AssociationRequest.DEVICE_PROFILE_APP_STREAMING,
+ mContext.getUser())
+ .contains(mProjectionGrant.packageName);
+ }
+
private void dump(final PrintWriter pw) {
pw.println("MEDIA PROJECTION MANAGER (dumpsys media_projection)");
synchronized (mLock) {
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/notification/ZenModeFiltering.java b/services/core/java/com/android/server/notification/ZenModeFiltering.java
index ff263d1..bdca555 100644
--- a/services/core/java/com/android/server/notification/ZenModeFiltering.java
+++ b/services/core/java/com/android/server/notification/ZenModeFiltering.java
@@ -37,7 +37,6 @@
import android.util.ArraySet;
import android.util.Slog;
-import com.android.internal.messages.nano.SystemMessageProto;
import com.android.internal.util.NotificationMessagingUtil;
import java.io.PrintWriter;
@@ -173,13 +172,6 @@
maybeLogInterceptDecision(record, false, "criticalNotification");
return false;
}
- // Make an exception to policy for the notification saying that policy has changed
- if (NotificationManager.Policy.areAllVisualEffectsSuppressed(policy.suppressedVisualEffects)
- && "android".equals(record.getSbn().getPackageName())
- && SystemMessageProto.SystemMessage.NOTE_ZEN_UPGRADE == record.getSbn().getId()) {
- maybeLogInterceptDecision(record, false, "systemDndChangedNotification");
- return false;
- }
switch (zen) {
case Global.ZEN_MODE_NO_INTERRUPTIONS:
// #notevenalarms
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 626c3dd..ea211a9 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -54,10 +54,8 @@
import android.app.AppOpsManager;
import android.app.AutomaticZenRule;
import android.app.Flags;
-import android.app.Notification;
import android.app.NotificationManager;
import android.app.NotificationManager.Policy;
-import android.app.PendingIntent;
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
@@ -74,7 +72,6 @@
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.database.ContentObserver;
-import android.graphics.drawable.Icon;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.AudioManagerInternal;
@@ -90,7 +87,6 @@
import android.os.Process;
import android.os.SystemClock;
import android.os.UserHandle;
-import android.provider.Settings;
import android.provider.Settings.Global;
import android.service.notification.Condition;
import android.service.notification.ConditionProviderService;
@@ -117,8 +113,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
import com.android.internal.logging.MetricsLogger;
-import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
-import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
@@ -309,7 +303,6 @@
mHandler.postMetricsTimer();
cleanUpZenRules();
mIsSystemServicesReady = true;
- showZenUpgradeNotification(mZenMode);
}
/**
@@ -485,7 +478,7 @@
populateZenRule(pkg, automaticZenRule, rule, origin, /* isNew= */ true);
rule = maybeRestoreRemovedRule(newConfig, pkg, rule, automaticZenRule, origin);
newConfig.automaticRules.put(rule.id, rule);
- maybeReplaceDefaultRule(newConfig, automaticZenRule);
+ maybeReplaceDefaultRule(newConfig, null, automaticZenRule);
if (setConfigLocked(newConfig, origin, reason, rule.component, true, callingUid)) {
return rule.id;
@@ -535,13 +528,24 @@
return ruleToRestore;
}
- private static void maybeReplaceDefaultRule(ZenModeConfig config, AutomaticZenRule addedRule) {
+ /**
+ * Possibly delete built-in rules if a more suitable rule is added or updated.
+ *
+ * <p>Today, this is done in one case: delete a disabled "Sleeping" rule if a Bedtime Mode is
+ * added (or an existing mode is turned into {@link AutomaticZenRule#TYPE_BEDTIME}, when
+ * upgrading). Because only the {@code config_systemWellbeing} package is allowed to use rules
+ * of this type, this will not trigger wantonly.
+ *
+ * @param oldRule If non-null, {@code rule} is updating {@code oldRule}. Otherwise,
+ * {@code rule} is being added.
+ */
+ private static void maybeReplaceDefaultRule(ZenModeConfig config, @Nullable ZenRule oldRule,
+ AutomaticZenRule rule) {
if (!Flags.modesApi()) {
return;
}
- if (addedRule.getType() == AutomaticZenRule.TYPE_BEDTIME) {
- // Delete a built-in disabled "Sleeping" rule when a BEDTIME rule is added; it may have
- // smarter triggers and it will prevent confusion about which one to use.
+ if (rule.getType() == AutomaticZenRule.TYPE_BEDTIME
+ && (oldRule == null || oldRule.type != rule.getType())) {
// Note: we must not verify canManageAutomaticZenRule here, since most likely they
// won't have the same owner (sleeping - system; bedtime - DWB).
ZenRule sleepingRule = config.automaticRules.get(
@@ -589,6 +593,10 @@
// condition) when no changes happen.
return true;
}
+
+ if (Flags.modesUi()) {
+ maybeReplaceDefaultRule(newConfig, oldRule, automaticZenRule);
+ }
return setConfigLocked(newConfig, origin, reason,
newRule.component, true, callingUid);
}
@@ -1584,8 +1592,6 @@
String reason, @Nullable String caller, int callingUid) {
setManualZenMode(zenMode, conditionId, origin, reason, caller, true /*setRingerMode*/,
callingUid);
- Settings.Secure.putInt(mContext.getContentResolver(),
- Settings.Secure.SHOW_ZEN_SETTINGS_SUGGESTION, 0);
}
private void setManualZenMode(int zenMode, Uri conditionId, @ConfigOrigin int origin,
@@ -1783,17 +1789,6 @@
SystemZenRules.maybeUpgradeRules(mContext, config);
}
- // Resolve user id for settings.
- userId = userId == UserHandle.USER_ALL ? UserHandle.USER_SYSTEM : userId;
- if (config.version < ZenModeConfig.XML_VERSION_ZEN_UPGRADE) {
- Settings.Secure.putIntForUser(mContext.getContentResolver(),
- Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 1, userId);
- } else {
- // devices not restoring/upgrading already have updated zen settings
- Settings.Secure.putIntForUser(mContext.getContentResolver(),
- Settings.Secure.ZEN_SETTINGS_UPDATED, 1, userId);
- }
-
if (Flags.modesApi() && forRestore) {
// Note: forBackup doesn't write deletedRules, but just in case.
config.deletedRules.clear();
@@ -2062,7 +2057,6 @@
Global.putInt(mContext.getContentResolver(), Global.ZEN_MODE, zen);
ZenLog.traceSetZenMode(Global.getInt(mContext.getContentResolver(), Global.ZEN_MODE, -1),
"updated setting");
- showZenUpgradeNotification(zen);
}
private int getPreviousRingerModeSetting() {
@@ -2117,12 +2111,6 @@
for (ZenRule automaticRule : mConfig.automaticRules.values()) {
if (automaticRule.isActive()) {
if (zenSeverity(automaticRule.zenMode) > zenSeverity(zen)) {
- // automatic rule triggered dnd and user hasn't seen update dnd dialog
- if (Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.ZEN_SETTINGS_SUGGESTION_VIEWED, 1) == 0) {
- Settings.Secure.putInt(mContext.getContentResolver(),
- Settings.Secure.SHOW_ZEN_SETTINGS_SUGGESTION, 1);
- }
zen = automaticRule.zenMode;
}
}
@@ -2702,62 +2690,6 @@
}
}
- private void showZenUpgradeNotification(int zen) {
- final boolean isWatch = mContext.getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_WATCH);
- final boolean showNotification = mIsSystemServicesReady
- && zen != Global.ZEN_MODE_OFF
- && !isWatch
- && Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 0) != 0
- && Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.ZEN_SETTINGS_UPDATED, 0) != 1;
-
- if (isWatch) {
- Settings.Secure.putInt(mContext.getContentResolver(),
- Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 0);
- }
-
- if (showNotification) {
- mNotificationManager.notify(TAG, SystemMessage.NOTE_ZEN_UPGRADE,
- createZenUpgradeNotification());
- Settings.Secure.putInt(mContext.getContentResolver(),
- Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 0);
- }
- }
-
- @VisibleForTesting
- protected Notification createZenUpgradeNotification() {
- final Bundle extras = new Bundle();
- extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
- mContext.getResources().getString(R.string.global_action_settings));
- int title = R.string.zen_upgrade_notification_title;
- int content = R.string.zen_upgrade_notification_content;
- int drawable = R.drawable.ic_zen_24dp;
- if (NotificationManager.Policy.areAllVisualEffectsSuppressed(
- getConsolidatedNotificationPolicy().suppressedVisualEffects)) {
- title = R.string.zen_upgrade_notification_visd_title;
- content = R.string.zen_upgrade_notification_visd_content;
- drawable = R.drawable.ic_dnd_block_notifications;
- }
-
- Intent onboardingIntent = new Intent(Settings.ZEN_MODE_ONBOARDING);
- onboardingIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
- return new Notification.Builder(mContext, SystemNotificationChannels.DO_NOT_DISTURB)
- .setAutoCancel(true)
- .setSmallIcon(R.drawable.ic_settings_24dp)
- .setLargeIcon(Icon.createWithResource(mContext, drawable))
- .setContentTitle(mContext.getResources().getString(title))
- .setContentText(mContext.getResources().getString(content))
- .setContentIntent(PendingIntent.getActivity(mContext, 0, onboardingIntent,
- PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
- .setAutoCancel(true)
- .setLocalOnly(true)
- .addExtras(extras)
- .setStyle(new Notification.BigTextStyle())
- .build();
- }
-
private int drawableResNameToResId(String packageName, String resourceName) {
if (TextUtils.isEmpty(resourceName)) {
return 0;
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/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 89ced12..4665a72 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -2208,10 +2208,10 @@
return true;
}
boolean permissionGranted = requireFullPermission ? hasPermission(
- Manifest.permission.INTERACT_ACROSS_USERS_FULL, callingUid)
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL)
: (hasPermission(
- android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, callingUid)
- || hasPermission(Manifest.permission.INTERACT_ACROSS_USERS, callingUid));
+ android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ || hasPermission(Manifest.permission.INTERACT_ACROSS_USERS));
if (!permissionGranted) {
if (Process.isIsolatedUid(callingUid) && isKnownIsolatedComputeApp(callingUid)) {
return checkIsolatedOwnerHasPermission(callingUid, requireFullPermission);
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 8657de2..5653da0 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -716,7 +716,7 @@
visiblePackages.add(info.getActivityInfo().packageName);
}
final List<ApplicationInfo> installedPackages =
- mPackageManagerInternal.getInstalledApplicationsCrossUser(
+ mPackageManagerInternal.getInstalledApplications(
/* flags= */ 0, user.getIdentifier(), callingUid);
for (ApplicationInfo applicationInfo : installedPackages) {
if (!visiblePackages.contains(applicationInfo.packageName)) {
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/PackageMonitorCallbackHelper.java b/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
index cf5de89..a28e3c1 100644
--- a/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
+++ b/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
@@ -244,7 +244,7 @@
return;
}
int registerUid = registerUser.getUid();
- if (allowUids != null && registerUid != Process.SYSTEM_UID
+ if (allowUids != null && !UserHandle.isSameApp(registerUid, Process.SYSTEM_UID)
&& !ArrayUtils.contains(allowUids, registerUid)) {
if (DEBUG) {
Slog.w(TAG, "Skip invoke PackageMonitorCallback for " + intent.getAction()
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/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 dc48242..2c0ce25 100644
--- a/services/core/java/com/android/server/power/hint/HintManagerService.java
+++ b/services/core/java/com/android/server/power/hint/HintManagerService.java
@@ -21,6 +21,7 @@
import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
import static com.android.server.power.hint.Flags.adpfSessionTag;
import static com.android.server.power.hint.Flags.powerhintThreadCleanup;
+import static com.android.server.power.hint.Flags.resetOnForkEnabled;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -1057,6 +1058,25 @@
Slogf.w(TAG, errMsg);
throw new SecurityException(errMsg);
}
+ if (resetOnForkEnabled()){
+ 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 (adpfSessionTag() && tag == SessionTag.APP) {
// If the category of the app is a game,
@@ -1447,6 +1467,25 @@
Slogf.w(TAG, errMsg);
throw new SecurityException(errMsg);
}
+ if (resetOnForkEnabled()){
+ 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()) {
synchronized (mNonIsolatedTidsLock) {
for (int i = nonIsolated.size() - 1; i >= 0; i--) {
diff --git a/services/core/java/com/android/server/power/hint/flags.aconfig b/services/core/java/com/android/server/power/hint/flags.aconfig
index 55afa05..e56b68c 100644
--- a/services/core/java/com/android/server/power/hint/flags.aconfig
+++ b/services/core/java/com/android/server/power/hint/flags.aconfig
@@ -14,3 +14,10 @@
description: "Feature flag for adding session tag to hint session atom"
bug: "345011125"
}
+
+flag {
+ name: "reset_on_fork_enabled"
+ namespace: "game"
+ description: "Set reset_on_fork flag."
+ bug: "370988407"
+}
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/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 5372abe..0b36c7e 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -230,6 +230,7 @@
import static com.android.server.wm.IdentifierProto.TITLE;
import static com.android.server.wm.IdentifierProto.USER_ID;
import static com.android.server.wm.StartingData.AFTER_TRANSACTION_COPY_TO_CLIENT;
+import static com.android.server.wm.StartingData.AFTER_TRANSACTION_IDLE;
import static com.android.server.wm.StartingData.AFTER_TRANSACTION_REMOVE_DIRECTLY;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_PREDICT_BACK;
@@ -286,6 +287,9 @@
import android.app.servertransaction.TopResumedActivityChangeItem;
import android.app.servertransaction.TransferSplashScreenViewStateItem;
import android.app.usage.UsageEvents.Event;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
+import android.compat.annotation.Overridable;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -465,6 +469,11 @@
// finished destroying itself.
private static final int DESTROY_TIMEOUT = 10 * 1000;
+ @ChangeId
+ @Overridable
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ static final long UNIVERSAL_RESIZABLE_BY_DEFAULT = 357141415;
+
final ActivityTaskManagerService mAtmService;
final ActivityCallerState mCallerState;
@NonNull
@@ -2635,8 +2644,10 @@
if (finishing || !mHandleExitSplashScreen || mStartingSurface == null
|| mStartingWindow == null
|| mTransferringSplashScreenState == TRANSFER_SPLASH_SCREEN_FINISH
- // skip copy splash screen to client if it was resized
- || (mStartingData != null && mStartingData.mResizedFromTransfer)
+ // Skip copy splash screen to client if it was resized, or the starting data already
+ // requested to be removed after transaction commit.
+ || (mStartingData != null && (mStartingData.mResizedFromTransfer
+ || mStartingData.mRemoveAfterTransaction != AFTER_TRANSACTION_IDLE))
|| isRelaunching()) {
return false;
}
@@ -3179,11 +3190,20 @@
* will be ignored.
*/
boolean isUniversalResizeable() {
- return mWmService.mConstants.mIgnoreActivityOrientationRequest
- && info.applicationInfo.category != ApplicationInfo.CATEGORY_GAME
- // If the user preference respects aspect ratio, then it becomes non-resizable.
- && !mAppCompatController.getAppCompatOverrides().getAppCompatAspectRatioOverrides()
- .shouldApplyUserMinAspectRatioOverride();
+ if (info.applicationInfo.category == ApplicationInfo.CATEGORY_GAME) {
+ return false;
+ }
+ final boolean compatEnabled = Flags.universalResizableByDefault()
+ && mDisplayContent != null && mDisplayContent.getConfiguration()
+ .smallestScreenWidthDp >= WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP
+ && mDisplayContent.getIgnoreOrientationRequest()
+ && info.isChangeEnabled(UNIVERSAL_RESIZABLE_BY_DEFAULT);
+ if (!compatEnabled && !mWmService.mConstants.mIgnoreActivityOrientationRequest) {
+ return false;
+ }
+ // If the user preference respects aspect ratio, then it becomes non-resizable.
+ return !mAppCompatController.getAppCompatOverrides().getAppCompatAspectRatioOverrides()
+ .shouldApplyUserMinAspectRatioOverride();
}
boolean isResizeable() {
@@ -6409,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) {
@@ -8179,7 +8195,7 @@
@ActivityInfo.ScreenOrientation
protected int getOverrideOrientation() {
int candidateOrientation = super.getOverrideOrientation();
- if (isUniversalResizeable() && ActivityInfo.isFixedOrientation(candidateOrientation)) {
+ if (ActivityInfo.isFixedOrientation(candidateOrientation) && isUniversalResizeable()) {
candidateOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
}
return mAppCompatController.getOrientationPolicy()
@@ -9456,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/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 49380d4..87fa62a 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2759,10 +2759,7 @@
mInTask = null;
// Launch ResolverActivity in the source task, so that it stays in the task bounds
// when in freeform workspace.
- // Also put noDisplay activities in the source task. These by itself can be placed
- // in any task/root-task, however it could launch other activities like
- // ResolverActivity, and we want those to stay in the original task.
- if ((mStartActivity.isResolverOrDelegateActivity() || mStartActivity.noDisplay)
+ if (mStartActivity.isResolverOrDelegateActivity()
&& mSourceRecord != null && mSourceRecord.inFreeformWindowingMode()) {
mAddingToTask = true;
}
diff --git a/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
index 0e66629..1c23212 100644
--- a/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
@@ -62,8 +62,8 @@
private final ActivityRecord mActivityRecord;
@NonNull
private final AppCompatConfiguration mAppCompatConfiguration;
- @NonNull
- private final UserAspectRatioState mUserAspectRatioState;
+ @PackageManager.UserMinAspectRatio
+ final int mUserAspectRatioType;
@NonNull
private final OptPropFactory.OptProp mAllowMinAspectRatioOverrideOptProp;
@@ -86,7 +86,7 @@
mActivityRecord = activityRecord;
mAppCompatConfiguration = appCompatConfiguration;
mAppCompatDeviceStateQuery = appCompatDeviceStateQuery;
- mUserAspectRatioState = new UserAspectRatioState();
+ mUserAspectRatioType = getUserMinAspectRatioOverrideType();
mAppCompatReachabilityOverrides = appCompatReachabilityOverrides;
mAllowMinAspectRatioOverrideOptProp = optPropBuilder.create(
PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE);
@@ -122,41 +122,28 @@
* current app.
*/
boolean shouldApplyUserMinAspectRatioOverride() {
- if (!shouldEnableUserAspectRatioSettings()) {
- return false;
- }
-
- mUserAspectRatioState.mUserAspectRatio = getUserMinAspectRatioOverrideCode();
-
- return mUserAspectRatioState.mUserAspectRatio != USER_MIN_ASPECT_RATIO_UNSET
- && mUserAspectRatioState.mUserAspectRatio != USER_MIN_ASPECT_RATIO_APP_DEFAULT
- && mUserAspectRatioState.mUserAspectRatio != USER_MIN_ASPECT_RATIO_FULLSCREEN;
+ return shouldEnableUserAspectRatioSettings()
+ && mUserAspectRatioType != USER_MIN_ASPECT_RATIO_UNSET
+ && mUserAspectRatioType != USER_MIN_ASPECT_RATIO_APP_DEFAULT
+ && mUserAspectRatioType != USER_MIN_ASPECT_RATIO_FULLSCREEN;
}
boolean shouldApplyUserFullscreenOverride() {
- if (isUserFullscreenOverrideEnabled()) {
- mUserAspectRatioState.mUserAspectRatio = getUserMinAspectRatioOverrideCode();
-
- return mUserAspectRatioState.mUserAspectRatio == USER_MIN_ASPECT_RATIO_FULLSCREEN;
- }
-
- return false;
+ return isUserFullscreenOverrideEnabled()
+ && mUserAspectRatioType == USER_MIN_ASPECT_RATIO_FULLSCREEN;
}
boolean isUserFullscreenOverrideEnabled() {
- if (mAllowUserAspectRatioOverrideOptProp.isFalse()
- || mAllowUserAspectRatioFullscreenOverrideOptProp.isFalse()
- || !mAppCompatConfiguration.isUserAppAspectRatioFullscreenEnabled()) {
- return false;
- }
- return true;
+ return !mAllowUserAspectRatioOverrideOptProp.isFalse()
+ && !mAllowUserAspectRatioFullscreenOverrideOptProp.isFalse()
+ && mAppCompatConfiguration.isUserAppAspectRatioFullscreenEnabled();
}
boolean isSystemOverrideToFullscreenEnabled() {
return isChangeEnabled(mActivityRecord, OVERRIDE_ANY_ORIENTATION_TO_USER)
&& !mAllowOrientationOverrideOptProp.isFalse()
- && (mUserAspectRatioState.mUserAspectRatio == USER_MIN_ASPECT_RATIO_UNSET
- || mUserAspectRatioState.mUserAspectRatio == USER_MIN_ASPECT_RATIO_FULLSCREEN);
+ && (mUserAspectRatioType == USER_MIN_ASPECT_RATIO_UNSET
+ || mUserAspectRatioType == USER_MIN_ASPECT_RATIO_FULLSCREEN);
}
/**
@@ -173,12 +160,11 @@
}
boolean hasFullscreenOverride() {
- // `mUserAspectRatio` is always initialized first in `shouldApplyUserFullscreenOverride()`.
return shouldApplyUserFullscreenOverride() || isSystemOverrideToFullscreenEnabled();
}
float getUserMinAspectRatio() {
- switch (mUserAspectRatioState.mUserAspectRatio) {
+ switch (mUserAspectRatioType) {
case USER_MIN_ASPECT_RATIO_DISPLAY_SIZE:
return getDisplaySizeMinAspectRatio();
case USER_MIN_ASPECT_RATIO_SPLIT_SCREEN:
@@ -191,7 +177,7 @@
return 3 / 2f;
default:
throw new AssertionError("Unexpected user min aspect ratio override: "
- + mUserAspectRatioState.mUserAspectRatio);
+ + mUserAspectRatioType);
}
}
@@ -255,13 +241,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);
}
/**
@@ -271,14 +254,15 @@
return !mAllowUserAspectRatioOverrideOptProp.isFalse();
}
- int getUserMinAspectRatioOverrideCode() {
+ // TODO(b/359217664): make this private.
+ int getUserMinAspectRatioOverrideType() {
try {
return mActivityRecord.mAtmService.getPackageManager()
.getUserMinAspectRatio(mActivityRecord.packageName, mActivityRecord.mUserId);
} catch (RemoteException e) {
Slog.w(TAG, "Exception thrown retrieving aspect ratio user override " + this, e);
}
- return mUserAspectRatioState.mUserAspectRatio;
+ return USER_MIN_ASPECT_RATIO_UNSET;
}
private float getDefaultMinAspectRatioForUnresizableApps() {
@@ -302,13 +286,6 @@
return getDisplaySizeMinAspectRatio();
}
- private static class UserAspectRatioState {
- // TODO(b/315140179): Make mUserAspectRatio final
- // The min aspect ratio override set by user
- @PackageManager.UserMinAspectRatio
- private int mUserAspectRatio = USER_MIN_ASPECT_RATIO_UNSET;
- }
-
private Resources getResources() {
return mActivityRecord.mWmService.mContext.getResources();
}
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..af9e1fd 100644
--- a/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
@@ -56,13 +56,11 @@
final DisplayContent displayContent = mActivityRecord.mDisplayContent;
final boolean isIgnoreOrientationRequestEnabled = displayContent != null
&& displayContent.getIgnoreOrientationRequest();
- final boolean shouldApplyUserFullscreenOverride = mAppCompatOverrides
- .getAppCompatAspectRatioOverrides().shouldApplyUserFullscreenOverride();
- final AppCompatCameraPolicy cameraPolicy = AppCompatCameraPolicy
- .getAppCompatCameraPolicy(mActivityRecord);
- final boolean shouldCameraCompatControlOrientation = cameraPolicy != null
- && cameraPolicy.shouldCameraCompatControlOrientation(mActivityRecord);
- if (shouldApplyUserFullscreenOverride && isIgnoreOrientationRequestEnabled
+ final boolean hasFullscreenOverride = mAppCompatOverrides
+ .getAppCompatAspectRatioOverrides().hasFullscreenOverride();
+ final boolean shouldCameraCompatControlOrientation =
+ AppCompatCameraPolicy.shouldCameraCompatControlOrientation(mActivityRecord);
+ if (hasFullscreenOverride && isIgnoreOrientationRequestEnabled
// Do not override orientation to fullscreen for camera activities.
// Fixed-orientation activities are rarely tested in other orientations, and it
// often results in sideways or stretched previews. As the camera compat treatment
@@ -98,29 +96,11 @@
if (displayContent != null
&& mAppCompatOverrides.getAppCompatCameraOverrides()
.isOverrideOrientationOnlyForCameraEnabled()
- && !displayContent.mAppCompatCameraPolicy
+ && !AppCompatCameraPolicy
.isActivityEligibleForOrientationOverride(mActivityRecord)) {
return candidate;
}
- // mUserAspectRatio is always initialized first in shouldApplyUserFullscreenOverride(),
- // which will always come first before this check as user override > device
- // manufacturer override.
- final boolean isSystemOverrideToFullscreenEnabled = mAppCompatOverrides
- .getAppCompatAspectRatioOverrides().isSystemOverrideToFullscreenEnabled();
- if (isSystemOverrideToFullscreenEnabled && isIgnoreOrientationRequestEnabled
- // Do not override orientation to fullscreen for camera activities.
- // Fixed-orientation activities are rarely tested in other orientations, and it
- // often results in sideways or stretched previews. As the camera compat treatment
- // targets fixed-orientation activities, overriding the orientation disables the
- // treatment.
- && !shouldCameraCompatControlOrientation) {
- Slog.v(TAG, "Requested orientation " + screenOrientationToString(candidate)
- + " for " + mActivityRecord + " is overridden to "
- + screenOrientationToString(SCREEN_ORIENTATION_USER));
- return SCREEN_ORIENTATION_USER;
- }
-
final AppCompatOrientationOverrides.OrientationOverridesState capabilityState =
mAppCompatOverrides.getAppCompatOrientationOverrides()
.mOrientationOverridesState;
@@ -213,5 +193,4 @@
}
return false;
}
-
}
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..b9db5d3 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;
}
@@ -269,7 +265,7 @@
}
final int userAspectRatioCode = mAppCompatOverrides.getAppCompatAspectRatioOverrides()
- .getUserMinAspectRatioOverrideCode();
+ .getUserMinAspectRatioOverrideType();
return userAspectRatioCode != USER_MIN_ASPECT_RATIO_UNSET
&& userAspectRatioCode != USER_MIN_ASPECT_RATIO_APP_DEFAULT
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/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java
index a74b006..4824c16 100644
--- a/services/core/java/com/android/server/wm/Dimmer.java
+++ b/services/core/java/com/android/server/wm/Dimmer.java
@@ -21,6 +21,7 @@
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.graphics.Rect;
import android.util.Log;
import android.view.Surface;
@@ -128,7 +129,7 @@
/**
* Set the parameters to prepare the dim to be relative parented to the dimming container
*/
- void prepareReparent(@NonNull WindowContainer<?> geometryParent,
+ void prepareReparent(@Nullable WindowContainer<?> geometryParent,
@NonNull WindowState relativeParent) {
mAnimationHelper.setRequestedRelativeParent(relativeParent);
mAnimationHelper.setRequestedGeometryParent(geometryParent);
@@ -221,7 +222,7 @@
* @param dimmingContainer The container that is dimming. The dim layer will be rel-z
* parented below it
*/
- public void adjustPosition(@NonNull WindowContainer<?> geometryParent,
+ public void adjustPosition(@Nullable WindowContainer<?> geometryParent,
@NonNull WindowState dimmingContainer) {
if (mDimState != null) {
mDimState.prepareReparent(geometryParent, dimmingContainer);
diff --git a/services/core/java/com/android/server/wm/DimmerAnimationHelper.java b/services/core/java/com/android/server/wm/DimmerAnimationHelper.java
index 298edae..3999e03 100644
--- a/services/core/java/com/android/server/wm/DimmerAnimationHelper.java
+++ b/services/core/java/com/android/server/wm/DimmerAnimationHelper.java
@@ -108,7 +108,7 @@
}
// Sets the requested layer to reparent the dim to without applying it immediately
- void setRequestedGeometryParent(WindowContainer<?> geometryParent) {
+ void setRequestedGeometryParent(@Nullable WindowContainer<?> geometryParent) {
if (geometryParent != null) {
mRequestedProperties.mGeometryParent = geometryParent;
}
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/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 0878912..66f9230 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -5215,7 +5215,7 @@
// but not window manager visible (!isVisibleNow()), it can still be the parent of the
// dim, but can not create a new surface or continue a dim alone.
Dimmer dimmer;
- WindowContainer<?> geometryParent = task;
+ WindowContainer<?> geometryParent = null;
if (Flags.useTasksDimOnly()) {
geometryParent = getDimParent();
dimmer = getDimController();
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/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index a07facf..776de2e 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -712,6 +712,12 @@
minOccurs="0" maxOccurs="unbounded">
<xs:annotation name="final"/>
</xs:element>
+ <!-- The time after which the stylus is to be assumed to be not under use. This will
+ enable the logic of changing the brightness with ambient light changes -->
+ <xs:element name="idleStylusTimeoutMillis" type="xs:nonNegativeInteger"
+ minOccurs="0" maxOccurs="1">
+ <xs:annotation name="final"/>
+ </xs:element>
</xs:sequence>
</xs:complexType>
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index 5309263..110a5a2 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -8,12 +8,14 @@
method public final java.math.BigInteger getDarkeningLightDebounceIdleMillis();
method public final java.math.BigInteger getDarkeningLightDebounceMillis();
method public boolean getEnabled();
+ method public final java.math.BigInteger getIdleStylusTimeoutMillis();
method public final java.util.List<com.android.server.display.config.LuxToBrightnessMapping> getLuxToBrightnessMapping();
method public final void setBrighteningLightDebounceIdleMillis(java.math.BigInteger);
method public final void setBrighteningLightDebounceMillis(java.math.BigInteger);
method public final void setDarkeningLightDebounceIdleMillis(java.math.BigInteger);
method public final void setDarkeningLightDebounceMillis(java.math.BigInteger);
method public void setEnabled(boolean);
+ method public final void setIdleStylusTimeoutMillis(java.math.BigInteger);
}
public enum AutoBrightnessModeName {
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/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
index 7d5532f..5c4716d 100644
--- a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
+++ b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
@@ -57,7 +57,6 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
-import org.mockito.ArgumentMatchers.eq
import org.mockito.Mockito.any
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.doReturn
@@ -384,10 +383,6 @@
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)) {
PackageManager.PERMISSION_GRANTED
}
- whenever(this.checkPermission(
- eq(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL), anyInt(), anyInt())) {
- PackageManager.PERMISSION_GRANTED
- }
}
val mockSharedLibrariesImpl: SharedLibrariesImpl = mock {
whenever(this.snapshot()) { this@mock }
diff --git a/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java b/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java
index e33ca77..70a2d48 100644
--- a/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java
+++ b/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java
@@ -45,7 +45,7 @@
import com.android.bedstead.harrier.BedsteadJUnit4;
import com.android.bedstead.harrier.DeviceState;
-import com.android.bedstead.harrier.annotations.EnsureHasSecondaryUser;
+import com.android.bedstead.multiuser.annotations.EnsureHasSecondaryUser;
import com.android.bedstead.nene.users.UserReference;
import com.android.compatibility.common.util.PollingCheck;
import com.android.compatibility.common.util.TestUtils;
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/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
index 8e1be9a..3976ea4 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -157,6 +157,7 @@
.getIdleScreenRefreshRateTimeoutLuxThresholdPoint());
assertNull(mDisplayDeviceConfig.getTempSensor().name);
assertTrue(mDisplayDeviceConfig.isAutoBrightnessAvailable());
+ assertEquals(0, mDisplayDeviceConfig.getIdleStylusTimeoutMillis());
}
@Test
@@ -253,6 +254,7 @@
.getLux().intValue());
assertEquals(800, idleScreenRefreshRateTimeoutLuxThresholdPoints.get(1)
.getTimeout().intValue());
+ assertEquals(1000, mDisplayDeviceConfig.getIdleStylusTimeoutMillis());
}
@Test
@@ -1479,6 +1481,7 @@
+ "</point>\n"
+ "</map>\n"
+ "</luxToBrightnessMapping>\n"
+ + "<idleStylusTimeoutMillis>1000</idleStylusTimeoutMillis>\n"
+ "</autoBrightness>\n"
+ getPowerThrottlingConfig()
+ "<highBrightnessMode enabled=\"true\">\n"
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index 342c87a..6093a67 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -1309,6 +1309,38 @@
}
/**
+ * Tests that it's not allowed to create an auto-mirror virtual display without
+ * CAPTURE_VIDEO_OUTPUT permission or a virtual device that can mirror displays
+ */
+ @Test
+ public void createAutoMirrorDisplay_withoutPermissionOrAllowedVirtualDevice_throwsException()
+ throws Exception {
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ DisplayManagerInternal localService = displayManager.new LocalService();
+ registerDefaultDisplays(displayManager);
+ when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+ IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
+ when(virtualDevice.getDeviceId()).thenReturn(1);
+ when(virtualDevice.canCreateMirrorDisplays()).thenReturn(false);
+ when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
+ when(mContext.checkCallingPermission(CAPTURE_VIDEO_OUTPUT)).thenReturn(
+ PackageManager.PERMISSION_DENIED);
+
+ final VirtualDisplayConfig.Builder builder =
+ new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
+ .setFlags(VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR)
+ .setUniqueId("uniqueId --- mirror display");
+ assertThrows(SecurityException.class, () -> {
+ localService.createVirtualDisplay(
+ builder.build(),
+ mMockAppToken /* callback */,
+ virtualDevice /* virtualDeviceToken */,
+ mock(DisplayWindowPolicyController.class),
+ PACKAGE_NAME);
+ });
+ }
+
+ /**
* Tests that the virtual display is added to the default display group when created with
* VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR using a virtual device.
*/
@@ -1320,6 +1352,7 @@
when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
when(virtualDevice.getDeviceId()).thenReturn(1);
+ when(virtualDevice.canCreateMirrorDisplays()).thenReturn(true);
when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
// Create an auto-mirror virtual display using a virtual device.
@@ -1352,6 +1385,7 @@
when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
when(virtualDevice.getDeviceId()).thenReturn(1);
+ when(virtualDevice.canCreateMirrorDisplays()).thenReturn(true);
when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
// Create an auto-mirror virtual display using a virtual device.
@@ -1418,6 +1452,7 @@
when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
when(virtualDevice.getDeviceId()).thenReturn(1);
+ when(virtualDevice.canCreateMirrorDisplays()).thenReturn(true);
when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
when(mContext.checkCallingPermission(ADD_ALWAYS_UNLOCKED_DISPLAY))
.thenReturn(PackageManager.PERMISSION_GRANTED);
@@ -1453,6 +1488,7 @@
when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
when(virtualDevice.getDeviceId()).thenReturn(1);
+ when(virtualDevice.canCreateMirrorDisplays()).thenReturn(true);
when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
// Create an auto-mirror virtual display using a virtual device.
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/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/PackageMonitorCallbackHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java
index 24e7242..54ee2a3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java
@@ -33,6 +33,7 @@
import android.os.IRemoteCallback;
import android.os.Looper;
import android.os.Process;
+import android.os.UserHandle;
import android.util.SparseArray;
import org.junit.After;
@@ -340,6 +341,24 @@
verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(any());
}
+ @Test
+ public void registerPackageMonitor_callbackNotInAllowListSystemUidSecondUser_callbackIsCalled()
+ throws Exception {
+ IRemoteCallback callback = createMockPackageMonitorCallback();
+ int userId = 10;
+ int fakeAppId = 12345;
+ SparseArray<int[]> broadcastAllowList = new SparseArray<>();
+ broadcastAllowList.put(userId, new int[]{UserHandle.getUid(userId, fakeAppId)});
+
+ mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, userId,
+ UserHandle.getUid(userId, Process.SYSTEM_UID));
+ mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
+ FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{userId},
+ null /* instantUserIds */, broadcastAllowList, mHandler, null /* filterExtras */);
+
+ verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(any());
+ }
+
private IRemoteCallback createMockPackageMonitorCallback() {
return spy(new IRemoteCallback.Stub() {
@Override
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/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
index ee63d5d..425bb15 100644
--- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
@@ -33,6 +33,7 @@
import static android.view.Display.INVALID_DISPLAY;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -51,11 +52,15 @@
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertThrows;
+import android.Manifest;
import android.annotation.SuppressLint;
import android.app.ActivityManagerInternal;
import android.app.ActivityOptions.LaunchCookie;
import android.app.AppOpsManager;
+import android.app.Instrumentation;
import android.app.KeyguardManager;
+import android.app.role.RoleManager;
+import android.companion.AssociationRequest;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
@@ -68,6 +73,7 @@
import android.os.Binder;
import android.os.IBinder;
import android.os.Looper;
+import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.test.TestLooper;
@@ -88,6 +94,7 @@
import com.android.server.wm.WindowManagerInternal;
import org.junit.After;
+import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -98,6 +105,7 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -312,7 +320,6 @@
assertThat(mService.getActiveProjectionInfo()).isNotNull();
}
- @SuppressLint("MissingPermission")
@EnableFlags(android.companion.virtualdevice.flags
.Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
@Test
@@ -335,6 +342,36 @@
assertThat(mService.getActiveProjectionInfo()).isNotNull();
}
+ @EnableFlags(android.companion.virtualdevice.flags
+ .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
+ @Test
+ public void testCreateProjection_keyguardLocked_RoleHeld() {
+ runWithRole(AssociationRequest.DEVICE_PROFILE_APP_STREAMING, () -> {
+ try {
+ mAppInfo.privateFlags |= PRIVATE_FLAG_PRIVILEGED;
+ doReturn(mAppInfo).when(mPackageManager).getApplicationInfoAsUser(anyString(),
+ any(ApplicationInfoFlags.class), any(UserHandle.class));
+ MediaProjectionManagerService.MediaProjection projection =
+ mService.createProjectionInternal(Process.myUid(),
+ mContext.getPackageName(),
+ TYPE_MIRRORING, /* isPermanentGrant= */ false, UserHandle.CURRENT);
+ doReturn(true).when(mKeyguardManager).isKeyguardLocked();
+ doReturn(PackageManager.PERMISSION_DENIED).when(
+ mPackageManager).checkPermission(
+ RECORD_SENSITIVE_CONTENT, projection.packageName);
+
+ projection.start(mIMediaProjectionCallback);
+ projection.notifyVirtualDisplayCreated(10);
+
+ // The projection was started because it was allowed to capture the keyguard.
+ assertWithMessage("Failed to run projection")
+ .that(mService.getActiveProjectionInfo()).isNotNull();
+ } catch (NameNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ }
+
@Test
public void testCreateProjection_attemptReuse_noPriorProjectionGrant()
throws NameNotFoundException {
@@ -1202,6 +1239,47 @@
return mService.getProjectionInternal(UID, PACKAGE_NAME);
}
+ /**
+ * Run the provided block giving the current context's package the provided role.
+ */
+ @SuppressWarnings("SameParameterValue")
+ private void runWithRole(String role, Runnable block) {
+ Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ String packageName = mContext.getPackageName();
+ UserHandle user = instrumentation.getTargetContext().getUser();
+ RoleManager roleManager = Objects.requireNonNull(
+ mContext.getSystemService(RoleManager.class));
+ try {
+ CountDownLatch latch = new CountDownLatch(1);
+ instrumentation.getUiAutomation().adoptShellPermissionIdentity(
+ Manifest.permission.MANAGE_ROLE_HOLDERS,
+ Manifest.permission.BYPASS_ROLE_QUALIFICATION);
+
+ roleManager.setBypassingRoleQualification(true);
+ roleManager.addRoleHolderAsUser(role, packageName, /* flags = */ 0, user,
+ mContext.getMainExecutor(), success -> {
+ if (success) {
+ latch.countDown();
+ } else {
+ Assert.fail("Couldn't set role for test (failure) " + role);
+ }
+ });
+ assertWithMessage("Couldn't set role for test (timeout) : " + role)
+ .that(latch.await(1, TimeUnit.SECONDS)).isTrue();
+ block.run();
+
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ } finally {
+ roleManager.removeRoleHolderAsUser(role, packageName, 0, user,
+ mContext.getMainExecutor(), (aBool) -> {
+ });
+ roleManager.setBypassingRoleQualification(false);
+ instrumentation.getUiAutomation()
+ .dropShellPermissionIdentity();
+ }
+ }
+
private static class FakeIMediaProjectionCallback extends IMediaProjectionCallback.Stub {
CountDownLatch mLatch = new CountDownLatch(1);
@Override
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/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
index b997f5d..a49f5a8 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
@@ -26,8 +26,6 @@
import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_MESSAGES;
import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_REPEAT_CALLERS;
import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_ANY;
-import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
-import static android.provider.Settings.Global.ZEN_MODE_ALARMS;
import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
import static android.provider.Settings.Global.ZEN_MODE_NO_INTERRUPTIONS;
import static android.provider.Settings.Global.ZEN_MODE_OFF;
@@ -57,7 +55,6 @@
import androidx.test.filters.SmallTest;
-import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.util.NotificationMessagingUtil;
import com.android.server.UiServiceTestCase;
@@ -188,49 +185,6 @@
}
@Test
- public void testSuppressDNDInfo_yes_VisEffectsAllowed() {
- NotificationRecord r = getNotificationRecord();
- when(r.getSbn().getPackageName()).thenReturn("android");
- when(r.getSbn().getId()).thenReturn(SystemMessage.NOTE_ZEN_UPGRADE);
- Policy policy = new Policy(0, 0, 0, Policy.getAllSuppressedVisualEffects()
- - SUPPRESSED_EFFECT_STATUS_BAR, 0);
-
- assertTrue(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r));
- }
-
- @Test
- public void testSuppressDNDInfo_yes_WrongId() {
- NotificationRecord r = getNotificationRecord();
- when(r.getSbn().getPackageName()).thenReturn("android");
- when(r.getSbn().getId()).thenReturn(SystemMessage.NOTE_ACCOUNT_CREDENTIAL_PERMISSION);
- Policy policy = new Policy(0, 0, 0, Policy.getAllSuppressedVisualEffects(), 0);
-
- assertTrue(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r));
- }
-
- @Test
- public void testSuppressDNDInfo_yes_WrongPackage() {
- NotificationRecord r = getNotificationRecord();
- when(r.getSbn().getPackageName()).thenReturn("android2");
- when(r.getSbn().getId()).thenReturn(SystemMessage.NOTE_ZEN_UPGRADE);
- Policy policy = new Policy(0, 0, 0, Policy.getAllSuppressedVisualEffects(), 0);
-
- assertTrue(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r));
- }
-
- @Test
- public void testSuppressDNDInfo_no() {
- NotificationRecord r = getNotificationRecord();
- when(r.getSbn().getPackageName()).thenReturn("android");
- when(r.getSbn().getId()).thenReturn(SystemMessage.NOTE_ZEN_UPGRADE);
- Policy policy = new Policy(0, 0, 0, Policy.getAllSuppressedVisualEffects(), 0);
-
- assertFalse(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r));
- assertFalse(mZenModeFiltering.shouldIntercept(ZEN_MODE_ALARMS, policy, r));
- assertFalse(mZenModeFiltering.shouldIntercept(ZEN_MODE_NO_INTERRUPTIONS, policy, r));
- }
-
- @Test
public void testSuppressAnything_yes_ZenModeOff() {
NotificationRecord r = getNotificationRecord();
when(r.getSbn().getPackageName()).thenReturn("bananas");
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index d4cba8d..294027b 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -169,7 +169,6 @@
import com.android.internal.R;
import com.android.internal.config.sysui.TestableFlagResolver;
-import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.os.AtomsProto;
@@ -747,54 +746,6 @@
}
@Test
- public void testZenUpgradeNotification() {
- /**
- * Commit a485ec65b5ba947d69158ad90905abf3310655cf disabled DND status change
- * notification on watches. So, assume that the device is not watch.
- */
- when(mContext.getPackageManager()).thenReturn(mPackageManager);
- when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)).thenReturn(false);
-
- // shows zen upgrade notification if stored settings says to shows,
- // zen has not been updated, boot is completed
- // and we're setting zen mode on
- Settings.Secure.putInt(mContentResolver, Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 1);
- Settings.Secure.putInt(mContentResolver, Settings.Secure.ZEN_SETTINGS_UPDATED, 0);
- mZenModeHelper.mIsSystemServicesReady = true;
- mZenModeHelper.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0);
- mZenModeHelper.setZenModeSetting(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
-
- verify(mNotificationManager, times(1)).notify(eq(ZenModeHelper.TAG),
- eq(SystemMessage.NOTE_ZEN_UPGRADE), any());
- assertEquals(0, Settings.Secure.getInt(mContentResolver,
- Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, -1));
- }
-
- @Test
- public void testNoZenUpgradeNotification() {
- // doesn't show upgrade notification if stored settings says don't show
- Settings.Secure.putInt(mContentResolver, Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 0);
- Settings.Secure.putInt(mContentResolver, Settings.Secure.ZEN_SETTINGS_UPDATED, 0);
- mZenModeHelper.mIsSystemServicesReady = true;
- mZenModeHelper.setZenModeSetting(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
-
- verify(mNotificationManager, never()).notify(eq(ZenModeHelper.TAG),
- eq(SystemMessage.NOTE_ZEN_UPGRADE), any());
- }
-
- @Test
- public void testNoZenUpgradeNotificationZenUpdated() {
- // doesn't show upgrade notification since zen was already updated
- Settings.Secure.putInt(mContentResolver, Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 0);
- Settings.Secure.putInt(mContentResolver, Settings.Secure.ZEN_SETTINGS_UPDATED, 1);
- mZenModeHelper.mIsSystemServicesReady = true;
- mZenModeHelper.setZenModeSetting(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
-
- verify(mNotificationManager, never()).notify(eq(ZenModeHelper.TAG),
- eq(SystemMessage.NOTE_ZEN_UPGRADE), any());
- }
-
- @Test
public void testZenSetInternalRinger_AllPriorityNotificationSoundsMuted() {
AudioManagerInternal mAudioManager = mock(AudioManagerInternal.class);
mZenModeHelper.mAudioManager = mAudioManager;
@@ -3032,6 +2983,33 @@
}
@Test
+ @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ public void updateAutomaticZenRule_withTypeBedtime_replacesDisabledSleeping() {
+ ZenRule sleepingRule = createCustomAutomaticRule(ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID);
+ sleepingRule.enabled = false;
+ sleepingRule.userModifiedFields = 0;
+ sleepingRule.name = "ZZZZZZZ...";
+ mZenModeHelper.mConfig.automaticRules.clear();
+ mZenModeHelper.mConfig.automaticRules.put(sleepingRule.id, sleepingRule);
+
+ AutomaticZenRule futureBedtime = new AutomaticZenRule.Builder("Bedtime (?)", CONDITION_ID)
+ .build();
+ String bedtimeRuleId = mZenModeHelper.addAutomaticZenRule(mPkg, futureBedtime,
+ ORIGIN_APP, "reason", CUSTOM_PKG_UID);
+ assertThat(mZenModeHelper.mConfig.automaticRules.keySet())
+ .containsExactly(sleepingRule.id, bedtimeRuleId);
+
+ AutomaticZenRule bedtime = new AutomaticZenRule.Builder("Bedtime (!)", CONDITION_ID)
+ .setType(TYPE_BEDTIME)
+ .build();
+ mZenModeHelper.updateAutomaticZenRule(bedtimeRuleId, bedtime, ORIGIN_APP, "reason",
+ CUSTOM_PKG_UID);
+
+ assertThat(mZenModeHelper.mConfig.automaticRules.keySet()).containsExactly(bedtimeRuleId);
+ }
+
+ @Test
@EnableFlags(FLAG_MODES_API)
public void testSetManualZenMode() {
setupZenConfig();
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/wm/AppCompatActivityRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
index 65736cb..049e5cf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
@@ -25,6 +25,7 @@
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.BackgroundActivityStartControllerTests.setViaReflection;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -179,17 +180,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()));
@@ -219,7 +228,13 @@
void setGetUserMinAspectRatioOverrideCode(@UserMinAspectRatio int overrideCode) {
doReturn(overrideCode).when(mActivityStack.top().mAppCompatController
- .getAppCompatAspectRatioOverrides()).getUserMinAspectRatioOverrideCode();
+ .getAppCompatAspectRatioOverrides()).getUserMinAspectRatioOverrideType();
+ }
+
+ void setUserAspectRatioType(@UserMinAspectRatio int aspectRatio) {
+ final AppCompatAspectRatioOverrides aspectRatioOverrides = mActivityStack.top()
+ .mAppCompatController.getAppCompatAspectRatioOverrides();
+ setViaReflection(aspectRatioOverrides, "mUserAspectRatioType", aspectRatio);
}
void setGetUserMinAspectRatioOverrideValue(float overrideValue) {
@@ -508,8 +523,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..b051aaf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java
@@ -75,7 +75,7 @@
robot.activity().setIgnoreOrientationRequest(/* enabled */ true);
robot.prop().disable(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE);
robot.activity().createActivityWithComponent();
- robot.activity().setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_FULLSCREEN);
+ robot.activity().setUserAspectRatioType(USER_MIN_ASPECT_RATIO_FULLSCREEN);
robot.checkShouldApplyUserFullscreenOverride(/* expected */ false);
});
@@ -88,7 +88,7 @@
robot.activity().setIgnoreOrientationRequest(/* enabled */ true);
robot.prop().disable(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE);
robot.activity().createActivityWithComponent();
- robot.activity().setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_FULLSCREEN);
+ robot.activity().setUserAspectRatioType(USER_MIN_ASPECT_RATIO_FULLSCREEN);
robot.checkShouldApplyUserFullscreenOverride(/* expected */ false);
});
}
@@ -100,7 +100,7 @@
robot.conf().enableUserAppAspectRatioFullscreen(/* enabled */ true);
robot.activity().setIgnoreOrientationRequest(/* enabled */ true);
robot.activity().createActivityWithComponent();
- robot.activity().setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_FULLSCREEN);
+ robot.activity().setUserAspectRatioType(USER_MIN_ASPECT_RATIO_FULLSCREEN);
robot.checkShouldApplyUserFullscreenOverride(/* expected */ true);
});
@@ -113,7 +113,7 @@
robot.activity().setIgnoreOrientationRequest(/* enabled */ true);
robot.prop().disable(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE);
robot.activity().createActivityWithComponent();
- robot.activity().setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_3_2);
+ robot.activity().setUserAspectRatioType(USER_MIN_ASPECT_RATIO_3_2);
robot.checkShouldEnableUserAspectRatioSettings(/* expected */ false);
});
@@ -126,7 +126,7 @@
robot.activity().setIgnoreOrientationRequest(/* enabled */ true);
robot.prop().enable(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE);
robot.activity().createActivityWithComponent();
- robot.activity().setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_3_2);
+ robot.activity().setUserAspectRatioType(USER_MIN_ASPECT_RATIO_3_2);
robot.checkShouldEnableUserAspectRatioSettings(/* expected */ true);
});
@@ -139,7 +139,7 @@
robot.activity().setIgnoreOrientationRequest(/* enabled */ true);
robot.prop().enable(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE);
robot.activity().createActivityWithComponent();
- robot.activity().setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_3_2);
+ robot.activity().setUserAspectRatioType(USER_MIN_ASPECT_RATIO_3_2);
robot.checkShouldEnableUserAspectRatioSettings(/* expected */ false);
});
@@ -152,7 +152,7 @@
robot.activity().setIgnoreOrientationRequest(/* enabled */ true);
robot.prop().disable(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE);
robot.activity().createActivityWithComponent();
- robot.activity().setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_3_2);
+ robot.activity().setUserAspectRatioType(USER_MIN_ASPECT_RATIO_3_2);
robot.checkShouldEnableUserAspectRatioSettings(/* expected */ false);
});
@@ -175,7 +175,7 @@
robot.conf().enableUserAppAspectRatioSettings(/* enabled */ true);
robot.activity().setIgnoreOrientationRequest(/* enabled */ false);
robot.activity().createActivityWithComponent();
- robot.activity().setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_3_2);
+ robot.activity().setUserAspectRatioType(USER_MIN_ASPECT_RATIO_3_2);
robot.checkShouldApplyUserMinAspectRatioOverride(/* expected */ false);
});
@@ -187,7 +187,7 @@
robot.conf().enableUserAppAspectRatioSettings(/* enabled */ true);
robot.activity().setIgnoreOrientationRequest(/* enabled */ true);
robot.activity().createActivityWithComponent();
- robot.activity().setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_3_2);
+ robot.activity().setUserAspectRatioType(USER_MIN_ASPECT_RATIO_3_2);
robot.checkShouldApplyUserMinAspectRatioOverride(/* expected */ true);
});
@@ -199,7 +199,7 @@
robot.conf().enableUserAppAspectRatioSettings(/* enabled */ false);
robot.activity().setIgnoreOrientationRequest(/* enabled */ true);
robot.activity().createActivityWithComponent();
- robot.activity().setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_3_2);
+ robot.activity().setUserAspectRatioType(USER_MIN_ASPECT_RATIO_3_2);
robot.checkShouldApplyUserMinAspectRatioOverride(/* expected */ false);
});
@@ -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..c462922 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;
@@ -114,7 +116,7 @@
robot.applyOnActivity((a) -> {
a.createActivityWithComponent();
a.setIgnoreOrientationRequest(true);
- a.setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_FULLSCREEN);
+ a.setUserAspectRatioType(USER_MIN_ASPECT_RATIO_FULLSCREEN);
});
robot.checkOverrideOrientation(/* candidate */ SCREEN_ORIENTATION_PORTRAIT,
@@ -131,7 +133,7 @@
robot.applyOnActivity((a) -> {
a.createActivityWithComponent();
a.setIgnoreOrientationRequest(true);
- a.setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_FULLSCREEN);
+ a.setUserAspectRatioType(USER_MIN_ASPECT_RATIO_FULLSCREEN);
});
robot.checkOverrideOrientation(/* candidate */ SCREEN_ORIENTATION_PORTRAIT,
@@ -160,7 +162,7 @@
robot.applyOnActivity((a) -> {
a.createActivityWithComponent();
a.setIgnoreOrientationRequest(true);
- a.setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_3_2);
+ a.setUserAspectRatioType(USER_MIN_ASPECT_RATIO_3_2);
});
robot.checkOverrideOrientation(/* candidate */ SCREEN_ORIENTATION_PORTRAIT,
@@ -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/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
index 6d508ea..3c0d83b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
@@ -1322,7 +1322,7 @@
spyOn(appCompatAspectRatioOverrides);
doReturn(overrideValue).when(appCompatAspectRatioOverrides).getUserMinAspectRatio();
doReturn(overrideCode).when(appCompatAspectRatioOverrides)
- .getUserMinAspectRatioOverrideCode();
+ .getUserMinAspectRatioOverrideType();
}
private TestDisplayContent createDisplayContent(int orientation, Rect displayBounds) {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SystemGestureExclusionInteractorKosmos.kt b/services/tests/wmtests/src/com/android/server/wm/DesktopWindowingRobot.java
similarity index 61%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SystemGestureExclusionInteractorKosmos.kt
copy to services/tests/wmtests/src/com/android/server/wm/DesktopWindowingRobot.java
index 3e46c3f..285a5e2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SystemGestureExclusionInteractorKosmos.kt
+++ b/services/tests/wmtests/src/com/android/server/wm/DesktopWindowingRobot.java
@@ -14,12 +14,15 @@
* limitations under the License.
*/
-package com.android.systemui.scene.domain.interactor
+package com.android.server.wm;
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.scene.data.repository.systemGestureExclusionRepository
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-val Kosmos.systemGestureExclusionInteractor by Fixture {
- SystemGestureExclusionInteractor(repository = systemGestureExclusionRepository)
+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/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index adc969c..c2ef6ea 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -63,9 +63,10 @@
import static com.android.server.wm.ActivityRecord.State.RESTARTING_PROCESS;
import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static com.android.server.wm.ActivityRecord.State.STOPPED;
-import static com.android.server.wm.AppCompatUtils.computeAspectRatio;
-import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_POSITION_MULTIPLIER_CENTER;
+import static com.android.server.wm.AppCompatUtils.computeAspectRatio;
+import static com.android.server.wm.BackgroundActivityStartControllerTests.setViaReflection;
+import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static com.google.common.truth.Truth.assertThat;
@@ -95,13 +96,11 @@
import android.content.ComponentName;
import android.content.pm.ActivityInfo;
import android.content.pm.ActivityInfo.ScreenOrientation;
-import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.Insets;
import android.graphics.Rect;
import android.os.Binder;
-import android.os.RemoteException;
import android.os.UserHandle;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
@@ -1087,9 +1086,7 @@
spyOn(activity.mAppCompatController.getAppCompatAspectRatioOverrides());
doReturn(true).when(activity.mWmService.mAppCompatConfiguration)
.isUserAppAspectRatioFullscreenEnabled();
- doReturn(USER_MIN_ASPECT_RATIO_FULLSCREEN)
- .when(activity.mAppCompatController.getAppCompatAspectRatioOverrides())
- .getUserMinAspectRatioOverrideCode();
+ setUserAspectRatioType(activity, USER_MIN_ASPECT_RATIO_FULLSCREEN);
assertFalse(activity.shouldCreateAppCompatDisplayInsets());
}
@@ -2210,11 +2207,9 @@
doReturn(true).when(mActivity.mWmService.mAppCompatConfiguration)
.isUserAppAspectRatioFullscreenEnabled();
- // Set user aspect ratio override
+ // Set user aspect ratio override.
spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides());
- doReturn(USER_MIN_ASPECT_RATIO_FULLSCREEN)
- .when(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides())
- .getUserMinAspectRatioOverrideCode();
+ setUserAspectRatioType(mActivity, USER_MIN_ASPECT_RATIO_FULLSCREEN);
prepareMinAspectRatio(mActivity, 16 / 9f, SCREEN_ORIENTATION_PORTRAIT);
@@ -2237,10 +2232,7 @@
// Set user aspect ratio override
spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides());
- doReturn(USER_MIN_ASPECT_RATIO_FULLSCREEN)
- .when(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides())
- .getUserMinAspectRatioOverrideCode();
-
+ setUserAspectRatioType(mActivity, USER_MIN_ASPECT_RATIO_FULLSCREEN);
prepareMinAspectRatio(mActivity, 16 / 9f, SCREEN_ORIENTATION_LANDSCAPE);
final Rect bounds = mActivity.getBounds();
@@ -2423,7 +2415,7 @@
true);
}
- private void testUserOverrideAspectRatio(boolean isUnresizable, int screenOrientation,
+ private void testUserOverrideAspectRatio(boolean isUnresizeable, int screenOrientation,
float expectedAspectRatio, @PackageManager.UserMinAspectRatio int aspectRatio,
boolean enabled) {
final ActivityRecord activity = getActivityBuilderWithoutTask().build();
@@ -2437,15 +2429,10 @@
spyOn(activity.mWmService.mAppCompatConfiguration);
doReturn(enabled).when(activity.mWmService.mAppCompatConfiguration)
.isUserAppAspectRatioSettingsEnabled();
- // Set user aspect ratio override
- final IPackageManager pm = mAtm.getPackageManager();
- try {
- doReturn(aspectRatio).when(pm)
- .getUserMinAspectRatio(activity.packageName, activity.mUserId);
- } catch (RemoteException ignored) {
- }
+ // Set user aspect ratio override.
+ setUserAspectRatioType(activity, aspectRatio);
- prepareLimitedBounds(activity, screenOrientation, isUnresizable);
+ prepareLimitedBounds(activity, screenOrientation, isUnresizeable);
final Rect afterBounds = activity.getBounds();
final int width = afterBounds.width();
@@ -4881,6 +4868,25 @@
assertNotEquals(SCREEN_ORIENTATION_UNSPECIFIED, mActivity.getOverrideOrientation());
}
+
+ @Test
+ @EnableCompatChanges({ActivityRecord.UNIVERSAL_RESIZABLE_BY_DEFAULT})
+ public void testUniversalResizeableByDefault() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_UNIVERSAL_RESIZABLE_BY_DEFAULT);
+ mDisplayContent.setIgnoreOrientationRequest(false);
+ setUpApp(mDisplayContent);
+ assertFalse(mActivity.isUniversalResizeable());
+
+ mDisplayContent.setIgnoreOrientationRequest(true);
+ final int swDp = mDisplayContent.getConfiguration().smallestScreenWidthDp;
+ if (swDp < WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP) {
+ final int height = 100 + (int) (mDisplayContent.getDisplayMetrics().density
+ * WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP);
+ resizeDisplay(mDisplayContent, 100 + height, height);
+ }
+ assertTrue(mActivity.isUniversalResizeable());
+ }
+
@Test
public void testClearSizeCompat_resetOverrideConfig() {
final int origDensity = 480;
@@ -5183,4 +5189,11 @@
DeviceConfig.setProperty(NAMESPACE_CONSTRAIN_DISPLAY_APIS,
CONFIG_ALWAYS_CONSTRAIN_DISPLAY_APIS, value, makeDefault);
}
+
+ private void setUserAspectRatioType(ActivityRecord activity,
+ @PackageManager.UserMinAspectRatio int aspectRatio) {
+ final AppCompatAspectRatioOverrides aspectRatioOverrides = activity.mAppCompatController
+ .getAppCompatAspectRatioOverrides();
+ setViaReflection(aspectRatioOverrides, "mUserAspectRatioType", aspectRatio);
+ }
}
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/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index bd5c759..49ca6f3 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -249,6 +249,13 @@
public static final String KEY_PROVISION_SATELLITE_TOKENS = "provision_satellite";
/**
+ * Bundle key to get the response from
+ * {@link #deprovisionSatellite(List, Executor, OutcomeReceiver)}.
+ * @hide
+ */
+ public static final String KEY_DEPROVISION_SATELLITE_TOKENS = "deprovision_satellite";
+
+ /**
* The request was successfully processed.
*/
@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
@@ -2791,6 +2798,61 @@
}
}
+ /**
+ * Deliver the list of deprovisioned satellite subscriber infos.
+ *
+ * @param list The list of deprovisioned satellite subscriber infos.
+ * @param executor The executor on which the callback will be called.
+ * @param callback The callback object to which the result will be delivered.
+ *
+ * @throws SecurityException if the caller doesn't have required permission.
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+ public void deprovisionSatellite(@NonNull List<SatelliteSubscriberInfo> list,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ ResultReceiver receiver = new ResultReceiver(null) {
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ if (resultCode == SATELLITE_RESULT_SUCCESS) {
+ if (resultData.containsKey(KEY_DEPROVISION_SATELLITE_TOKENS)) {
+ boolean isUpdated =
+ resultData.getBoolean(KEY_DEPROVISION_SATELLITE_TOKENS);
+ executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+ callback.onResult(isUpdated)));
+ } else {
+ loge("KEY_DEPROVISION_SATELLITE_TOKENS does not exist.");
+ executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+ callback.onError(new SatelliteException(
+ SATELLITE_RESULT_REQUEST_FAILED))));
+ }
+ } else {
+ executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+ callback.onError(new SatelliteException(resultCode))));
+ }
+ }
+ };
+ telephony.deprovisionSatellite(list, receiver);
+ } else {
+ loge("deprovisionSatellite() invalid telephony");
+ executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+ new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
+ }
+ } catch (RemoteException ex) {
+ loge("deprovisionSatellite() RemoteException: " + ex);
+ executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+ new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
+ }
+ }
+
@Nullable
private static ITelephony getITelephony() {
ITelephony binder = ITelephony.Stub.asInterface(TelephonyFrameworkInitializer
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 3161d17..61f0146 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -3444,4 +3444,15 @@
*/
boolean overrideCarrierRoamingNtnEligibilityChanged(
in boolean status, in boolean resetRequired);
+
+ /**
+ * Deliver the list of deprovisioned satellite subscriber infos.
+ *
+ * @param list The list of deprovisioned satellite subscriber infos.
+ * @param result The result receiver that returns whether deliver success or fail.
+ * @hide
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+ void deprovisionSatellite(in List<SatelliteSubscriberInfo> list, in ResultReceiver result);
}
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/aapt/StringPool.cpp b/tools/aapt/StringPool.cpp
index 1af8d6f..b2e48bd 100644
--- a/tools/aapt/StringPool.cpp
+++ b/tools/aapt/StringPool.cpp
@@ -40,7 +40,7 @@
void printStringPool(const ResStringPool* pool)
{
if (pool->getError() == NO_INIT) {
- printf("String pool is unitialized.\n");
+ printf("String pool is uninitialized.\n");
return;
} else if (pool->getError() != NO_ERROR) {
printf("String pool is corrupt/invalid.\n");
diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp
index 064b461..2527dcd 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -445,7 +445,7 @@
using namespace android;
if (pool->getError() == NO_INIT) {
- printer->Print("String pool is unitialized.\n");
+ printer->Print("String pool is uninitialized.\n");
return;
} else if (pool->getError() != NO_ERROR) {
printer->Print("String pool is corrupt/invalid.\n");
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();
+ }
+}